Merge branch 'develop' of https://github.com/nordeck/matrix-react-sdk into feature_confetti#14676
Conflicts: src/SlashCommands.tsx src/i18n/strings/de_DE.json
80
CHANGELOG.md
|
@ -1,3 +1,83 @@
|
|||
Changes in [3.2.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.2.0) (2020-08-17)
|
||||
===================================================================================================
|
||||
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.2.0-rc.1...v3.2.0)
|
||||
|
||||
* Upgrade to JS SDK 8.1.0
|
||||
* [Release] Fix corner rounding on images not always affecting right side
|
||||
[\#5122](https://github.com/matrix-org/matrix-react-sdk/pull/5122)
|
||||
* [Release] Message Action Bar watch for event send changes
|
||||
[\#5116](https://github.com/matrix-org/matrix-react-sdk/pull/5116)
|
||||
* Fix /op slash command to release
|
||||
[\#5114](https://github.com/matrix-org/matrix-react-sdk/pull/5114)
|
||||
* Fix action bar safe area regression
|
||||
[\#5112](https://github.com/matrix-org/matrix-react-sdk/pull/5112)
|
||||
|
||||
Changes in [3.2.0-rc.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.2.0-rc.1) (2020-08-13)
|
||||
=============================================================================================================
|
||||
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.1.0...v3.2.0-rc.1)
|
||||
|
||||
* Upgrade to JS SDK 8.1.0-rc.1
|
||||
* Update from Weblate
|
||||
[\#5105](https://github.com/matrix-org/matrix-react-sdk/pull/5105)
|
||||
* padding the timeline so that its scrollbar has its own space from the
|
||||
resizer
|
||||
[\#5103](https://github.com/matrix-org/matrix-react-sdk/pull/5103)
|
||||
* Try to close notification on all platforms which support it, not just
|
||||
electron
|
||||
[\#5102](https://github.com/matrix-org/matrix-react-sdk/pull/5102)
|
||||
* Fix exception when stripping replies from an event with a non-string body
|
||||
[\#5101](https://github.com/matrix-org/matrix-react-sdk/pull/5101)
|
||||
* Quick win session 24/07/2020
|
||||
[\#5056](https://github.com/matrix-org/matrix-react-sdk/pull/5056)
|
||||
* Remove rebranding toast
|
||||
[\#5100](https://github.com/matrix-org/matrix-react-sdk/pull/5100)
|
||||
* Generate previews for rooms when the option changes
|
||||
[\#5098](https://github.com/matrix-org/matrix-react-sdk/pull/5098)
|
||||
* Fix Bridge Settings tab
|
||||
[\#5095](https://github.com/matrix-org/matrix-react-sdk/pull/5095)
|
||||
* get screen type from app prop
|
||||
[\#5081](https://github.com/matrix-org/matrix-react-sdk/pull/5081)
|
||||
* Update rageshake app name
|
||||
[\#5093](https://github.com/matrix-org/matrix-react-sdk/pull/5093)
|
||||
* Factor out Iconized Context menu for reusability
|
||||
[\#5085](https://github.com/matrix-org/matrix-react-sdk/pull/5085)
|
||||
* Decouple Audible notifications from Desktop notifications
|
||||
[\#5088](https://github.com/matrix-org/matrix-react-sdk/pull/5088)
|
||||
* Make the room sublist show more/less buttons treeitems
|
||||
[\#5087](https://github.com/matrix-org/matrix-react-sdk/pull/5087)
|
||||
* Share and debug master cross-signing key
|
||||
[\#5092](https://github.com/matrix-org/matrix-react-sdk/pull/5092)
|
||||
* Create Map comparison utilities and convert Hooks to Typescript
|
||||
[\#5086](https://github.com/matrix-org/matrix-react-sdk/pull/5086)
|
||||
* Fix room list scrolling in Safari
|
||||
[\#5090](https://github.com/matrix-org/matrix-react-sdk/pull/5090)
|
||||
* Replace Riot with Element in docs and comments
|
||||
[\#5083](https://github.com/matrix-org/matrix-react-sdk/pull/5083)
|
||||
* When the room view isn't active don't highlight it in room list
|
||||
[\#5027](https://github.com/matrix-org/matrix-react-sdk/pull/5027)
|
||||
* remove emoji icons in autocomplete/reply by designer request
|
||||
[\#5073](https://github.com/matrix-org/matrix-react-sdk/pull/5073)
|
||||
* Add title and icon to empty state of file and notification panel
|
||||
[\#5079](https://github.com/matrix-org/matrix-react-sdk/pull/5079)
|
||||
* Mass redact ignore room creation events
|
||||
[\#5045](https://github.com/matrix-org/matrix-react-sdk/pull/5045)
|
||||
* Replace all chevrons with a single icon
|
||||
[\#5067](https://github.com/matrix-org/matrix-react-sdk/pull/5067)
|
||||
* Replace i18n generation script with something matching our project
|
||||
[\#5077](https://github.com/matrix-org/matrix-react-sdk/pull/5077)
|
||||
* Handle tag changes in sticky room updates
|
||||
[\#5078](https://github.com/matrix-org/matrix-react-sdk/pull/5078)
|
||||
* Remove leftover bits of TSLint
|
||||
[\#5075](https://github.com/matrix-org/matrix-react-sdk/pull/5075)
|
||||
* Clean up documentation of Whenable + fix other code concerns
|
||||
[\#5076](https://github.com/matrix-org/matrix-react-sdk/pull/5076)
|
||||
* Center the jump down/up icon, looks misaligned
|
||||
[\#5074](https://github.com/matrix-org/matrix-react-sdk/pull/5074)
|
||||
* [WIP] Support a new settings structure
|
||||
[\#5058](https://github.com/matrix-org/matrix-react-sdk/pull/5058)
|
||||
* Convert SettingsStore to TypeScript
|
||||
[\#5062](https://github.com/matrix-org/matrix-react-sdk/pull/5062)
|
||||
|
||||
Changes in [3.1.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.1.0) (2020-08-05)
|
||||
===================================================================================================
|
||||
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.1.0-rc.1...v3.1.0)
|
||||
|
|
|
@ -9,7 +9,7 @@ of dealing with the different levels and exposes easy to use getters and setters
|
|||
## Levels
|
||||
|
||||
Granular Settings rely on a series of known levels in order to use the correct value for the scenario. These levels, in
|
||||
order of prioirty, are:
|
||||
order of priority, are:
|
||||
* `device` - The current user's device
|
||||
* `room-device` - The current user's device, but only when in a specific room
|
||||
* `room-account` - The current user's account, but only when in a specific room
|
||||
|
@ -25,33 +25,10 @@ that room administrators cannot force account-only settings upon participants.
|
|||
## Settings
|
||||
|
||||
Settings are the different options a user may set or experience in the application. These are pre-defined in
|
||||
`src/settings/Settings.js` under the `SETTINGS` constant and have the following minimum requirements:
|
||||
```
|
||||
// The ID is used to reference the setting throughout the application. This must be unique.
|
||||
"theSettingId": {
|
||||
// The levels this setting supports is required. In `src/settings/Settings.js` there are various pre-set arrays
|
||||
// for this option - they should be used where possible to avoid copy/pasting arrays across settings.
|
||||
supportedLevels: [...],
|
||||
`src/settings/Settings.ts` under the `SETTINGS` constant, and match the `ISetting` interface as defined there.
|
||||
|
||||
// The default for this setting serves two purposes: It provides a value if the setting is not defined at other
|
||||
// levels, and it serves to demonstrate the expected type to other developers. The value isn't enforced, but it
|
||||
// should be respected throughout the code. The default may be any data type.
|
||||
default: false,
|
||||
|
||||
// The display name has two notations: string and object. The object notation allows for different translatable
|
||||
// strings to be used for different levels, while the string notation represents the string for all levels.
|
||||
|
||||
displayName: _td("Change something"), // effectively `displayName: { "default": _td("Change something") }`
|
||||
displayName: {
|
||||
"room": _td("Change something for participants of this room"),
|
||||
|
||||
// Note: the default will be used if the level requested (such as `device`) does not have a string defined here.
|
||||
"default": _td("Change something"),
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Settings that support the config level can be set in the config file under the `settingDefaults` key (note that some settings, like the "theme" setting, are special cased in the config file):
|
||||
Settings that support the config level can be set in the config file under the `settingDefaults` key (note that some
|
||||
settings, like the "theme" setting, are special cased in the config file):
|
||||
```json
|
||||
{
|
||||
...
|
||||
|
@ -119,38 +96,29 @@ for you. If a display name cannot be found, it will return `null`.
|
|||
|
||||
## Features
|
||||
|
||||
Occasionally some parts of the application may be undergoing testing and are not quite production ready. These are
|
||||
commonly known to be behind a "labs flag". Features behind lab flags must go through the granular settings system, and
|
||||
look and act very much normal settings. The exception is that they must supply `isFeature: true` as part of the setting
|
||||
definition and should go through the helper functions on `SettingsStore`.
|
||||
Feature flags are just like regular settings with some underlying semantics for how they are meant to be used. Usually
|
||||
a feature flag is used when a portion of the application is under development or not ready for full release yet, such
|
||||
as new functionality or experimental ideas. In these cases, the feature name *should* be named with the `feature_*`
|
||||
convention and must be tagged with `isFeature: true` in the setting definition. By doing so, the feature will automatically
|
||||
appear in the "labs" section of the user's settings.
|
||||
|
||||
Although features have levels and a default value, the calculation of those options is blocked by the feature's state.
|
||||
A feature's state is determined from the `SdkConfig` and is a little complex. If `enableLabs` (a legacy flag) is `true`
|
||||
then the feature's state is `labs`, if it is `false`, the state is `disable`. If `enableLabs` is not set then the state
|
||||
is determined from the `features` config, such as in the following:
|
||||
Features can be controlled at the config level using the following structure:
|
||||
```json
|
||||
"features": {
|
||||
"feature_lazyloading": "labs"
|
||||
"feature_lazyloading": true
|
||||
}
|
||||
```
|
||||
In this example, `feature_lazyloading` is in the `labs` state. It may also be in the `enable` or `disable` state with a
|
||||
similar approach. If the state is invalid, the feature is in the `disable` state. A feature's levels are only calculated
|
||||
if it is in the `labs` state, therefore the default only applies in that scenario. If the state is `enable`, the feature
|
||||
is always-on.
|
||||
|
||||
Once a feature flag has served its purpose, it is generally recommended to remove it and the associated feature flag
|
||||
checks. This would enable the feature implicitly as it is part of the application now.
|
||||
When `true`, the user will see the feature as enabled. Similarly, when `false` the user will see the feature as disabled.
|
||||
The user will only be able to change/see these states if `showLabsSettings: true` is in the config.
|
||||
|
||||
### Determining if a feature is enabled
|
||||
|
||||
A simple call to `SettingsStore.isFeatureEnabled` will tell you if the feature is enabled. This will perform all the
|
||||
required calculations to determine if the feature is enabled based upon the configuration and user selection.
|
||||
Call `SettingsStore.getValue()` as you would for any other setting.
|
||||
|
||||
### Enabling a feature
|
||||
|
||||
Features can only be enabled if the feature is in the `labs` state, otherwise this is a no-op. To find the current set
|
||||
of features in the `labs` state, call `SettingsStore.getLabsFeatures`. To set the value, call
|
||||
`SettingsStore.setFeatureEnabled`.
|
||||
Call `SettingsStore.setValue("feature_name", null, SettingLevel.DEVICE, true)`.
|
||||
|
||||
|
||||
## Setting controllers
|
||||
|
@ -162,7 +130,7 @@ kept up to date with the setting where it is otherwise not possible. An example
|
|||
they can only be considered enabled if the platform supports notifications, and enabling notifications requires
|
||||
additional steps to actually enable notifications.
|
||||
|
||||
For more information, see `src/settings/controllers/SettingController.js`.
|
||||
For more information, see `src/settings/controllers/SettingController.ts`.
|
||||
|
||||
|
||||
## Local echo
|
||||
|
@ -222,7 +190,7 @@ The `SettingsStore` uses the hardcoded `LEVEL_ORDER` constant to ensure that it
|
|||
The array is checked from left to right, simulating the behaviour of overriding values from the higher levels. Each
|
||||
level should be defined in this array, including `default`.
|
||||
|
||||
Handlers (`src/settings/handlers/SettingsHandler.js`) represent a single level and are responsible for getting and
|
||||
Handlers (`src/settings/handlers/SettingsHandler.ts`) represent a single level and are responsible for getting and
|
||||
setting values at that level. Handlers also provide additional information to the `SettingsStore` such as if the level
|
||||
is supported or if the current user may set values at the level. The `SettingsStore` will use the handler to enforce
|
||||
checks and manipulate settings. Handlers are also responsible for dealing with migration patterns or legacy settings for
|
||||
|
@ -230,7 +198,7 @@ their level (for example, a setting being renamed or using a different key from
|
|||
Handlers are provided to the `SettingsStore` via the `LEVEL_HANDLERS` constant. `SettingsStore` will optimize lookups by
|
||||
only considering handlers that are supported on the platform.
|
||||
|
||||
Local echo is achieved through `src/settings/handlers/LocalEchoWrapper.js` which acts as a wrapper around a given
|
||||
Local echo is achieved through `src/settings/handlers/LocalEchoWrapper.ts` which acts as a wrapper around a given
|
||||
handler. This is automatically applied to all defined `LEVEL_HANDLERS` and proxies the calls to the wrapped handler
|
||||
where possible. The echo is achieved by a simple object cache stored within the class itself. The cache is invalidated
|
||||
immediately upon the proxied save call succeeding or failing.
|
||||
|
@ -240,20 +208,7 @@ Controllers are notified of changes by the `SettingsStore`, and are given the op
|
|||
|
||||
### Features
|
||||
|
||||
Features automatically get considered as `disabled` if they are not listed in the `SdkConfig` or `enableLabs` is
|
||||
false/not set. Features are always checked against the configuration before going through the level order as they have
|
||||
the option of being forced-on or forced-off for the application. This is done by the `features` section and looks
|
||||
something like this:
|
||||
|
||||
```
|
||||
"features": {
|
||||
"feature_groups": "enable",
|
||||
"feature_pinning": "disable", // the default
|
||||
"feature_presence": "labs"
|
||||
}
|
||||
```
|
||||
|
||||
If `enableLabs` is true in the configuration, the default for features becomes `"labs"`.
|
||||
See above for feature reference.
|
||||
|
||||
### Watchers
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "matrix-react-sdk",
|
||||
"version": "3.1.0",
|
||||
"version": "3.2.0",
|
||||
"description": "SDK for matrix.org using React",
|
||||
"author": "matrix.org",
|
||||
"repository": {
|
||||
|
@ -96,6 +96,7 @@
|
|||
"react-transition-group": "^4.4.1",
|
||||
"resize-observer-polyfill": "^1.5.1",
|
||||
"sanitize-html": "^1.27.1",
|
||||
"tar-js": "^0.3.0",
|
||||
"text-encoding-utf-8": "^1.0.2",
|
||||
"url": "^0.11.0",
|
||||
"velocity-animate": "^1.5.2",
|
||||
|
@ -127,6 +128,7 @@
|
|||
"@types/lodash": "^4.14.158",
|
||||
"@types/modernizr": "^3.5.3",
|
||||
"@types/node": "^12.12.51",
|
||||
"@types/pako": "^1.0.1",
|
||||
"@types/qrcode": "^1.3.4",
|
||||
"@types/react": "^16.9",
|
||||
"@types/react-dom": "^16.9.8",
|
||||
|
|
|
@ -605,3 +605,15 @@ input[type=text]:focus, input[type=password]:focus, textarea:focus {
|
|||
border-radius: $radius;
|
||||
}
|
||||
}
|
||||
|
||||
@define-mixin unreal-focus {
|
||||
outline-width: 2px;
|
||||
outline-style: solid;
|
||||
outline-color: Highlight;
|
||||
|
||||
/* WebKit gets its native focus styles. */
|
||||
@media (-webkit-min-device-pixel-ratio: 0) {
|
||||
outline-color: -webkit-focus-ring-color;
|
||||
outline-style: auto;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
// autogenerated by rethemendex.sh
|
||||
@import "./_common.scss";
|
||||
@import "./_font-sizes.scss";
|
||||
@import "./_font-weights.scss";
|
||||
@import "./structures/_AutoHideScrollbar.scss";
|
||||
@import "./structures/_CompatibilityPage.scss";
|
||||
@import "./structures/_ContextualMenu.scss";
|
||||
|
@ -52,13 +53,12 @@
|
|||
@import "./views/avatars/_PulsedAvatar.scss";
|
||||
@import "./views/context_menus/_IconizedContextMenu.scss";
|
||||
@import "./views/context_menus/_MessageContextMenu.scss";
|
||||
@import "./views/context_menus/_RoomTileContextMenu.scss";
|
||||
@import "./views/context_menus/_StatusMessageContextMenu.scss";
|
||||
@import "./views/context_menus/_TagTileContextMenu.scss";
|
||||
@import "./views/context_menus/_TopLeftMenu.scss";
|
||||
@import "./views/context_menus/_WidgetContextMenu.scss";
|
||||
@import "./views/dialogs/_AddressPickerDialog.scss";
|
||||
@import "./views/dialogs/_Analytics.scss";
|
||||
@import "./views/dialogs/_BugReportDialog.scss";
|
||||
@import "./views/dialogs/_ChangelogDialog.scss";
|
||||
@import "./views/dialogs/_ChatCreateOrReuseChatDialog.scss";
|
||||
@import "./views/dialogs/_ConfirmUserActionDialog.scss";
|
||||
|
@ -72,7 +72,6 @@
|
|||
@import "./views/dialogs/_KeyboardShortcutsDialog.scss";
|
||||
@import "./views/dialogs/_MessageEditHistoryDialog.scss";
|
||||
@import "./views/dialogs/_NewSessionReviewDialog.scss";
|
||||
@import "./views/dialogs/_RebrandDialog.scss";
|
||||
@import "./views/dialogs/_RoomSettingsDialog.scss";
|
||||
@import "./views/dialogs/_RoomSettingsDialogBridges.scss";
|
||||
@import "./views/dialogs/_RoomUpgradeDialog.scss";
|
||||
|
@ -156,7 +155,6 @@
|
|||
@import "./views/right_panel/_UserInfo.scss";
|
||||
@import "./views/right_panel/_VerificationPanel.scss";
|
||||
@import "./views/room_settings/_AliasSettings.scss";
|
||||
@import "./views/room_settings/_ColorSettings.scss";
|
||||
@import "./views/rooms/_AppsDrawer.scss";
|
||||
@import "./views/rooms/_Autocomplete.scss";
|
||||
@import "./views/rooms/_AuxPanel.scss";
|
||||
|
@ -185,7 +183,6 @@
|
|||
@import "./views/rooms/_RoomRecoveryReminder.scss";
|
||||
@import "./views/rooms/_RoomSublist.scss";
|
||||
@import "./views/rooms/_RoomTile.scss";
|
||||
@import "./views/rooms/_RoomTileIcon.scss";
|
||||
@import "./views/rooms/_RoomUpgradeWarningBar.scss";
|
||||
@import "./views/rooms/_SearchBar.scss";
|
||||
@import "./views/rooms/_SendMessageComposer.scss";
|
||||
|
|
17
res/css/_font-weights.scss
Normal file
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
$font-semi-bold: 600;
|
|
@ -41,13 +41,19 @@ limitations under the License.
|
|||
|
||||
.mx_FilePanel .mx_EventTile {
|
||||
word-break: break-word;
|
||||
margin-top: 32px;
|
||||
}
|
||||
|
||||
.mx_FilePanel .mx_EventTile .mx_MImageBody {
|
||||
margin-right: 0px;
|
||||
}
|
||||
|
||||
.mx_FilePanel .mx_EventTile .mx_MFileBody {
|
||||
line-height: 2.4rem;
|
||||
}
|
||||
|
||||
.mx_FilePanel .mx_EventTile .mx_MFileBody_download {
|
||||
padding-top: 8px;
|
||||
display: flex;
|
||||
font-size: $font-14px;
|
||||
color: $event-timestamp-color;
|
||||
|
@ -60,7 +66,7 @@ limitations under the License.
|
|||
|
||||
.mx_FilePanel .mx_EventTile .mx_MImageBody_size {
|
||||
flex: 1 0 0;
|
||||
font-size: $font-11px;
|
||||
font-size: $font-14px;
|
||||
text-align: right;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
@ -80,7 +86,7 @@ limitations under the License.
|
|||
flex: 1 1 auto;
|
||||
line-height: initial;
|
||||
padding: 0px;
|
||||
font-size: $font-11px;
|
||||
font-size: $font-14px;
|
||||
opacity: 1.0;
|
||||
color: $event-timestamp-color;
|
||||
}
|
||||
|
@ -90,7 +96,7 @@ limitations under the License.
|
|||
text-align: right;
|
||||
visibility: visible;
|
||||
position: initial;
|
||||
font-size: $font-11px;
|
||||
font-size: $font-14px;
|
||||
opacity: 1.0;
|
||||
color: $event-timestamp-color;
|
||||
}
|
||||
|
|
|
@ -97,7 +97,8 @@ $tagPanelWidth: 56px; // only applies in this file, used for calculations
|
|||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.mx_RoomSearch_expanded + .mx_LeftPanel_exploreButton {
|
||||
.mx_RoomSearch_focused, .mx_RoomSearch_hasQuery {
|
||||
& + .mx_LeftPanel_exploreButton {
|
||||
// Cheaty way to return the occupied space to the filter input
|
||||
flex-basis: 0;
|
||||
margin: 0;
|
||||
|
@ -109,11 +110,12 @@ $tagPanelWidth: 56px; // only applies in this file, used for calculations
|
|||
content: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mx_LeftPanel_exploreButton {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border-radius: 20px;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 8px;
|
||||
background-color: $roomlist-button-bg-color;
|
||||
position: relative;
|
||||
margin-left: 8px;
|
||||
|
@ -121,19 +123,27 @@ $tagPanelWidth: 56px; // only applies in this file, used for calculations
|
|||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 6px;
|
||||
left: 6px;
|
||||
top: 8px;
|
||||
left: 8px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
mask-image: url('$(res)/img/feather-customised/compass.svg');
|
||||
mask-image: url('$(res)/img/element-icons/roomlist/explore.svg');
|
||||
mask-position: center;
|
||||
mask-size: contain;
|
||||
mask-repeat: no-repeat;
|
||||
background: $primary-fg-color;
|
||||
background: $secondary-fg-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mx_LeftPanel_roomListFilterCount {
|
||||
font-size: $font-13px;
|
||||
font-weight: $font-semi-bold;
|
||||
margin-left: 12px;
|
||||
margin-top: 14px;
|
||||
margin-bottom: -4px; // to counteract the normal roomListWrapper margin-top
|
||||
}
|
||||
|
||||
.mx_LeftPanel_roomListWrapper {
|
||||
overflow: hidden;
|
||||
margin-top: 10px; // so we're not up against the search/filter
|
||||
|
|
|
@ -23,6 +23,8 @@ limitations under the License.
|
|||
|
||||
.mx_MainSplit > .mx_RightPanel_ResizeWrapper {
|
||||
padding: 5px;
|
||||
// margin left to not allow the handle to not encroach on the space for the scrollbar
|
||||
margin-left: 8px;
|
||||
|
||||
&:hover .mx_RightPanel_ResizeHandle {
|
||||
// Need to use important to override element style attributes
|
||||
|
|
|
@ -64,7 +64,7 @@ limitations under the License.
|
|||
left: 4px; // center with parent of 32px
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
background-color: $rightpanel-button-color;
|
||||
background-color: $icon-button-color;
|
||||
mask-repeat: no-repeat;
|
||||
mask-size: contain;
|
||||
}
|
||||
|
@ -99,7 +99,7 @@ limitations under the License.
|
|||
background: rgba($accent-color, 0.25);
|
||||
// make the icon the accent color too
|
||||
&::before {
|
||||
background-color: $accent-color;
|
||||
background-color: $accent-color !important;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -17,8 +17,10 @@ limitations under the License.
|
|||
// Note: this component expects to be contained within a flexbox
|
||||
.mx_RoomSearch {
|
||||
flex: 1;
|
||||
border-radius: 20px;
|
||||
border-radius: 8px;
|
||||
background-color: $roomlist-button-bg-color;
|
||||
// keep border thickness consistent to prevent movement
|
||||
border: 1px solid transparent;
|
||||
height: 28px;
|
||||
padding: 2px;
|
||||
|
||||
|
@ -29,9 +31,9 @@ limitations under the License.
|
|||
.mx_RoomSearch_icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
mask: url('$(res)/img/feather-customised/search-input.svg');
|
||||
mask: url('$(res)/img/element-icons/roomlist/search.svg');
|
||||
mask-repeat: no-repeat;
|
||||
background: $primary-fg-color;
|
||||
background-color: $secondary-fg-color;
|
||||
margin-left: 7px;
|
||||
}
|
||||
|
||||
|
@ -46,19 +48,30 @@ limitations under the License.
|
|||
line-height: $font-16px;
|
||||
|
||||
&:not(.mx_RoomSearch_inputExpanded)::placeholder {
|
||||
color: $primary-fg-color !important; // !important to override default app-wide styles
|
||||
color: $tertiary-fg-color !important; // !important to override default app-wide styles
|
||||
}
|
||||
}
|
||||
|
||||
&.mx_RoomSearch_expanded {
|
||||
&.mx_RoomSearch_hasQuery {
|
||||
border-color: $secondary-fg-color;
|
||||
}
|
||||
|
||||
&.mx_RoomSearch_focused {
|
||||
box-shadow: 0 0 4px 4px rgba(0, 132, 255, 0.5);
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
&.mx_RoomSearch_focused, &.mx_RoomSearch_hasQuery {
|
||||
background-color: $roomlist-filter-active-bg-color;
|
||||
|
||||
.mx_RoomSearch_clearButton {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
mask-image: url('$(res)/img/feather-customised/x.svg');
|
||||
mask-image: url('$(res)/img/element-icons/roomlist/search-clear.svg');
|
||||
mask-position: center;
|
||||
mask-size: contain;
|
||||
mask-repeat: no-repeat;
|
||||
background: $primary-fg-color;
|
||||
background-color: $secondary-fg-color;
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -108,13 +108,12 @@ limitations under the License.
|
|||
|
||||
.mx_TagPanel .mx_TagTile.mx_TagTile_selected::before {
|
||||
content: '';
|
||||
height: calc(100% + 16px);
|
||||
height: 100%;
|
||||
background-color: $accent-color;
|
||||
width: 5px;
|
||||
width: 4px;
|
||||
position: absolute;
|
||||
left: -15px;
|
||||
left: -12px;
|
||||
border-radius: 0 3px 3px 0;
|
||||
top: -8px; // (16px from height / 2)
|
||||
}
|
||||
|
||||
.mx_TagPanel .mx_TagTile.mx_AccessibleButton:focus {
|
||||
|
|
|
@ -80,10 +80,6 @@ limitations under the License.
|
|||
}
|
||||
}
|
||||
|
||||
&.mx_Toast_icon_element_logo::after {
|
||||
background-image: url("$(res)/img/element-logo.svg");
|
||||
}
|
||||
|
||||
.mx_Toast_title, .mx_Toast_body {
|
||||
grid-column: 2;
|
||||
}
|
||||
|
|
|
@ -18,10 +18,49 @@ limitations under the License.
|
|||
.mx_DecoratedRoomAvatar, .mx_TemporaryTile {
|
||||
position: relative;
|
||||
|
||||
.mx_RoomTileIcon {
|
||||
&.mx_DecoratedRoomAvatar_cutout .mx_BaseAvatar {
|
||||
mask-image: url('$(res)/img/element-icons/roomlist/decorated-avatar-mask.svg');
|
||||
mask-position: center;
|
||||
mask-size: contain;
|
||||
mask-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.mx_DecoratedRoomAvatar_icon {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
bottom: -2px;
|
||||
right: -2px;
|
||||
margin: 4px;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.mx_DecoratedRoomAvatar_icon::before {
|
||||
content: '';
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
position: absolute;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.mx_DecoratedRoomAvatar_icon_globe::before {
|
||||
mask-position: center;
|
||||
mask-size: contain;
|
||||
mask-repeat: no-repeat;
|
||||
background: $secondary-fg-color;
|
||||
mask-image: url('$(res)/img/globe.svg');
|
||||
}
|
||||
|
||||
.mx_DecoratedRoomAvatar_icon_offline::before {
|
||||
background-color: $presence-offline;
|
||||
}
|
||||
|
||||
.mx_DecoratedRoomAvatar_icon_online::before {
|
||||
background-color: $presence-online;
|
||||
}
|
||||
|
||||
.mx_DecoratedRoomAvatar_icon_away::before {
|
||||
background-color: $presence-away;
|
||||
}
|
||||
|
||||
.mx_NotificationBadge, .mx_RoomTile_badgeContainer {
|
||||
|
|
|
@ -1,114 +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_RoomTileContextMenu {
|
||||
padding: 6px;
|
||||
}
|
||||
|
||||
.mx_RoomTileContextMenu_tag_icon {
|
||||
padding-right: 8px;
|
||||
padding-left: 4px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.mx_RoomTileContextMenu_tag_icon_set {
|
||||
padding-right: 8px;
|
||||
padding-left: 4px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.mx_RoomTileContextMenu_tag_field, .mx_RoomTileContextMenu_leave {
|
||||
padding-top: 8px;
|
||||
padding-right: 20px;
|
||||
padding-bottom: 8px;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
line-height: $font-16px;
|
||||
}
|
||||
|
||||
.mx_RoomTileContextMenu_tag_field.mx_RoomTileContextMenu_tag_fieldSet {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.mx_RoomTileContextMenu_tag_field.mx_RoomTileContextMenu_tag_fieldSet .mx_RoomTileContextMenu_tag_icon {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.mx_RoomTileContextMenu_tag_field.mx_RoomTileContextMenu_tag_fieldSet .mx_RoomTileContextMenu_tag_icon_set {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.mx_RoomTileContextMenu_tag_field.mx_RoomTileContextMenu_tag_fieldDisabled {
|
||||
color: rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.mx_RoomTileContextMenu_separator {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
border-bottom-style: none;
|
||||
border-left-style: none;
|
||||
border-right-style: none;
|
||||
border-top-style: solid;
|
||||
border-top-width: 1px;
|
||||
border-color: $menu-border-color;
|
||||
}
|
||||
|
||||
.mx_RoomTileContextMenu_leave {
|
||||
color: $warning-color;
|
||||
}
|
||||
|
||||
.mx_RoomTileContextMenu_notif_picker {
|
||||
position: absolute;
|
||||
top: 16px;
|
||||
left: 5px;
|
||||
}
|
||||
|
||||
.mx_RoomTileContextMenu_notif_field {
|
||||
padding-top: 4px;
|
||||
padding-right: 6px;
|
||||
padding-bottom: 10px;
|
||||
padding-left: 8px; /* 20px */
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.mx_RoomTileContextMenu_notif_field.mx_RoomTileContextMenu_notif_fieldSet {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.mx_RoomTileContextMenu_notif_field.mx_RoomTileContextMenu_notif_fieldDisabled {
|
||||
color: rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.mx_RoomTileContextMenu_notif_icon {
|
||||
padding-right: 4px;
|
||||
padding-left: 4px;
|
||||
}
|
||||
|
||||
.mx_RoomTileContextMenu_notif_activeIcon {
|
||||
display: inline-block;
|
||||
opacity: 0;
|
||||
position: relative;
|
||||
left: -5px;
|
||||
}
|
||||
|
||||
.mx_RoomTileContextMenu_notif_fieldSet .mx_RoomTileContextMenu_notif_activeIcon {
|
||||
opacity: 1;
|
||||
}
|
|
@ -1,96 +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_TopLeftMenu {
|
||||
min-width: 210px;
|
||||
border-radius: 4px;
|
||||
|
||||
.mx_TopLeftMenu_greyedText {
|
||||
font-size: $font-12px;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.mx_TopLeftMenu_upgradeLink {
|
||||
font-size: $font-12px;
|
||||
|
||||
img {
|
||||
margin-left: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.mx_TopLeftMenu_section:not(:last-child) {
|
||||
border-bottom: 1px solid $menu-border-color;
|
||||
}
|
||||
|
||||
.mx_TopLeftMenu_section_noIcon {
|
||||
margin: 5px 0;
|
||||
padding: 5px 20px 5px 15px;
|
||||
|
||||
div:not(:first-child) {
|
||||
margin-top: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.mx_TopLeftMenu_section_withIcon {
|
||||
margin: 5px 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
|
||||
.mx_TopLeftMenu_icon_home::after {
|
||||
mask-image: url('$(res)/img/feather-customised/home.svg');
|
||||
}
|
||||
|
||||
.mx_TopLeftMenu_icon_help::after {
|
||||
mask-image: url('$(res)/img/feather-customised/life-buoy.svg');
|
||||
}
|
||||
|
||||
.mx_TopLeftMenu_icon_settings::after {
|
||||
mask-image: url('$(res)/img/feather-customised/settings.svg');
|
||||
}
|
||||
|
||||
.mx_TopLeftMenu_icon_signin::after {
|
||||
mask-image: url('$(res)/img/feather-customised/sign-in.svg');
|
||||
}
|
||||
|
||||
.mx_TopLeftMenu_icon_signout::after {
|
||||
mask-image: url('$(res)/img/feather-customised/sign-out.svg');
|
||||
}
|
||||
|
||||
.mx_AccessibleButton::after {
|
||||
mask-repeat: no-repeat;
|
||||
mask-position: 0 center;
|
||||
mask-size: $font-16px;
|
||||
position: absolute;
|
||||
width: $font-16px;
|
||||
height: $font-16px;
|
||||
content: "";
|
||||
top: 5px;
|
||||
left: 14px;
|
||||
background-color: $primary-fg-color;
|
||||
}
|
||||
|
||||
.mx_AccessibleButton {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
padding: 5px 20px 5px 43px;
|
||||
}
|
||||
|
||||
.mx_AccessibleButton:hover {
|
||||
background-color: $menu-selected-color;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2019 New Vector Ltd.
|
||||
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -14,26 +14,10 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
.mx_ColorSettings_roomColor {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
width: 37px;
|
||||
height: 37px;
|
||||
border: 1px solid #979797;
|
||||
margin-right: 13px;
|
||||
cursor: pointer;
|
||||
.mx_BugReportDialog {
|
||||
.mx_BugReportDialog_download {
|
||||
.mx_AccessibleButton_kind_link {
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.mx_ColorSettings_roomColor_selected {
|
||||
position: absolute;
|
||||
left: 10px;
|
||||
top: 4px;
|
||||
cursor: default !important;
|
||||
}
|
||||
|
||||
.mx_ColorSettings_roomColorPrimary {
|
||||
height: 10px;
|
||||
position: absolute;
|
||||
bottom: 0px;
|
||||
width: 100%;
|
||||
}
|
|
@ -1,64 +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.
|
||||
*/
|
||||
|
||||
.mx_RebrandDialog {
|
||||
text-align: center;
|
||||
|
||||
a:link,
|
||||
a:hover,
|
||||
a:visited {
|
||||
@mixin mx_Dialog_link;
|
||||
}
|
||||
|
||||
.mx_Dialog_buttons {
|
||||
margin-top: 43px;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.mx_RebrandDialog_body {
|
||||
width: 550px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.mx_RebrandDialog_logoContainer {
|
||||
margin-top: 35px;
|
||||
margin-bottom: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.mx_RebrandDialog_logo {
|
||||
margin-left: 28px;
|
||||
margin-right: 28px;
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
}
|
||||
|
||||
.mx_RebrandDialog_chevron::after {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
mask-position: center;
|
||||
mask-size: contain;
|
||||
mask-repeat: no-repeat;
|
||||
background-color: $muted-fg-color;
|
||||
mask-image: url('$(res)/img/feather-customised/chevron-down.svg');
|
||||
transform: rotate(-90deg);
|
||||
}
|
|
@ -51,7 +51,8 @@ limitations under the License.
|
|||
display: inherit;
|
||||
}
|
||||
.mx_ShareDialog_matrixto_copy > div {
|
||||
background-image: url($copy-button-url);
|
||||
mask-image: url($copy-button-url);
|
||||
background-color: $message-action-bar-fg-color;
|
||||
margin-left: 5px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
|
|
|
@ -80,5 +80,11 @@ limitations under the License.
|
|||
background-color: $accent-color;
|
||||
border-color: $accent-color;
|
||||
}
|
||||
|
||||
&.focus-visible {
|
||||
& + label .mx_Checkbox_background {
|
||||
@mixin unreal-focus;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -63,6 +63,7 @@ limitations under the License.
|
|||
box-sizing: border-box;
|
||||
height: $font-16px;
|
||||
width: $font-16px;
|
||||
margin-left: 2px; // For the highlight on focus
|
||||
|
||||
border: $font-1-5px solid $radio-circle-color;
|
||||
border-radius: $font-16px;
|
||||
|
@ -77,6 +78,12 @@ limitations under the License.
|
|||
}
|
||||
}
|
||||
|
||||
&.focus-visible {
|
||||
& + div {
|
||||
@mixin unreal-focus;
|
||||
}
|
||||
}
|
||||
|
||||
&:checked {
|
||||
& + div {
|
||||
border-color: $active-radio-circle-color;
|
||||
|
|
|
@ -25,6 +25,7 @@ limitations under the License.
|
|||
height: 100%;
|
||||
left: 0;
|
||||
top: 0;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.mx_MImageBody_thumbnail_container {
|
||||
|
|
|
@ -24,7 +24,7 @@ limitations under the License.
|
|||
line-height: $font-24px;
|
||||
border-radius: 4px;
|
||||
background: $message-action-bar-bg-color;
|
||||
top: -18px;
|
||||
top: -26px;
|
||||
right: 8px;
|
||||
user-select: none;
|
||||
// Ensure the action bar appears above over things, like the read marker.
|
||||
|
@ -41,7 +41,7 @@ limitations under the License.
|
|||
width: calc(10px + 48px + 100% + 8px);
|
||||
// safe area + action bar
|
||||
height: calc(20px + 100%);
|
||||
top: -20px;
|
||||
top: -12px;
|
||||
left: -58px;
|
||||
z-index: -1;
|
||||
cursor: initial;
|
||||
|
|
|
@ -536,11 +536,12 @@ $left-gutter: 64px;
|
|||
display: inline-block;
|
||||
visibility: hidden;
|
||||
cursor: pointer;
|
||||
top: 8px;
|
||||
top: 6px;
|
||||
right: 6px;
|
||||
width: 19px;
|
||||
height: 19px;
|
||||
background-image: url($copy-button-url);
|
||||
mask-image: url($copy-button-url);
|
||||
background-color: $message-action-bar-fg-color;
|
||||
}
|
||||
|
||||
.mx_EventTile_body .mx_EventTile_pre_container:focus-within .mx_EventTile_copyButton,
|
||||
|
|
|
@ -54,7 +54,7 @@ $irc-line-height: $font-18px;
|
|||
flex-shrink: 0;
|
||||
width: var(--name-width);
|
||||
text-overflow: ellipsis;
|
||||
text-align: right;
|
||||
text-align: left;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
overflow: visible;
|
||||
|
|
|
@ -21,6 +21,7 @@ limitations under the License.
|
|||
border-top: 1px solid $primary-hairline-color;
|
||||
position: relative;
|
||||
padding-left: 82px;
|
||||
padding-right: 6px;
|
||||
}
|
||||
|
||||
.mx_MessageComposer_replaced_wrapper {
|
||||
|
@ -178,25 +179,44 @@ limitations under the License.
|
|||
color: $accent-color;
|
||||
}
|
||||
|
||||
.mx_MessageComposer_button_highlight {
|
||||
background: rgba($accent-color, 0.25);
|
||||
// make the icon the accent color too
|
||||
&::before {
|
||||
background-color: $accent-color !important;
|
||||
}
|
||||
}
|
||||
|
||||
.mx_MessageComposer_button {
|
||||
position: relative;
|
||||
margin-right: 12px;
|
||||
margin-right: 6px;
|
||||
cursor: pointer;
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
height: 26px;
|
||||
width: 26px;
|
||||
border-radius: 100%;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
|
||||
top: 3px;
|
||||
left: 3px;
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
background-color: $composer-button-color;
|
||||
background-color: $icon-button-color;
|
||||
mask-repeat: no-repeat;
|
||||
mask-size: contain;
|
||||
mask-position: center;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: rgba($accent-color, 0.1);
|
||||
|
||||
&::before {
|
||||
background-color: $accent-color;
|
||||
}
|
||||
}
|
||||
|
||||
&.mx_MessageComposer_hangup::before {
|
||||
background-color: $warning-color;
|
||||
}
|
||||
|
@ -288,7 +308,7 @@ limitations under the License.
|
|||
mask-size: contain;
|
||||
mask-position: center;
|
||||
mask-repeat: no-repeat;
|
||||
background-color: $composer-button-color;
|
||||
background-color: $icon-button-color;
|
||||
|
||||
&.mx_MessageComposer_markdownDisabled {
|
||||
opacity: 0.2;
|
||||
|
|
|
@ -15,10 +15,6 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
.mx_ReplyPreview {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
z-index: 1000;
|
||||
width: 100%;
|
||||
border: 1px solid $primary-hairline-color;
|
||||
background: $primary-bg-color;
|
||||
border-bottom: none;
|
||||
|
|
|
@ -222,7 +222,7 @@ limitations under the License.
|
|||
left: 4px; // center with parent of 32px
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
background-color: $roomheader-button-color;
|
||||
background-color: $icon-button-color;
|
||||
mask-repeat: no-repeat;
|
||||
mask-size: contain;
|
||||
}
|
||||
|
|
|
@ -17,3 +17,43 @@ limitations under the License.
|
|||
.mx_RoomList {
|
||||
padding-right: 7px; // width of the scrollbar, to line things up
|
||||
}
|
||||
|
||||
.mx_RoomList_iconPlus::before {
|
||||
mask-image: url('$(res)/img/element-icons/roomlist/plus.svg');
|
||||
}
|
||||
.mx_RoomList_iconExplore::before {
|
||||
mask-image: url('$(res)/img/element-icons/roomlist/explore.svg');
|
||||
}
|
||||
|
||||
.mx_RoomList_explorePrompt {
|
||||
margin: 4px 12px 4px;
|
||||
padding-top: 12px;
|
||||
border-top: 1px solid $tertiary-fg-color;
|
||||
font-size: $font-13px;
|
||||
|
||||
div:first-child {
|
||||
font-weight: $font-semi-bold;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.mx_AccessibleButton {
|
||||
color: $secondary-fg-color;
|
||||
position: relative;
|
||||
padding: 0 0 0 24px;
|
||||
font-size: inherit;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
background: $secondary-fg-color;
|
||||
mask-position: center;
|
||||
mask-size: contain;
|
||||
mask-repeat: no-repeat;
|
||||
mask-image: url('$(res)/img/element-icons/roomlist/explore.svg');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -120,7 +120,7 @@ limitations under the License.
|
|||
}
|
||||
|
||||
.mx_RoomSublist_auxButton::before {
|
||||
mask-image: url('$(res)/img/feather-customised/plus.svg');
|
||||
mask-image: url('$(res)/img/element-icons/roomlist/plus.svg');
|
||||
}
|
||||
|
||||
.mx_RoomSublist_menuButton::before {
|
||||
|
|
|
@ -1,69 +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.
|
||||
*/
|
||||
|
||||
.mx_RoomTileIcon {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 12px;
|
||||
background-color: $roomlist-bg-color; // to match the room list itself
|
||||
}
|
||||
|
||||
.mx_RoomTileIcon_globe::before {
|
||||
content: '';
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
top: 2px;
|
||||
left: 2px;
|
||||
position: absolute;
|
||||
mask-position: center;
|
||||
mask-size: contain;
|
||||
mask-repeat: no-repeat;
|
||||
background: $primary-fg-color;
|
||||
mask-image: url('$(res)/img/globe.svg');
|
||||
}
|
||||
|
||||
.mx_RoomTileIcon_offline::before {
|
||||
content: '';
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
top: 2px;
|
||||
left: 2px;
|
||||
position: absolute;
|
||||
border-radius: 8px;
|
||||
background-color: $presence-offline;
|
||||
}
|
||||
|
||||
.mx_RoomTileIcon_online::before {
|
||||
content: '';
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
top: 2px;
|
||||
left: 2px;
|
||||
position: absolute;
|
||||
border-radius: 8px;
|
||||
background-color: $presence-online;
|
||||
}
|
||||
|
||||
.mx_RoomTileIcon_away::before {
|
||||
content: '';
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
top: 2px;
|
||||
left: 2px;
|
||||
position: absolute;
|
||||
border-radius: 8px;
|
||||
background-color: $presence-away;
|
||||
}
|
|
@ -44,10 +44,5 @@ limitations under the License.
|
|||
overflow-y: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.mx_SendMessageComposer_overlayWrapper {
|
||||
position: relative;
|
||||
height: 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -14,10 +14,6 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
.mx_SecurityRoomSettingsTab label {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.mx_SecurityRoomSettingsTab_warning {
|
||||
display: block;
|
||||
|
||||
|
|
3
res/img/element-icons/roomlist/decorated-avatar-mask.svg
Normal file
|
@ -0,0 +1,3 @@
|
|||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M30.7161 22.2903C31.5425 20.3595 32 18.2332 32 16C32 7.16344 24.8366 0 16 0C7.16344 0 0 7.16344 0 16C0 24.8366 7.16344 32 16 32C18.2332 32 20.3595 31.5425 22.2903 30.7161C20.8956 29.6174 20 27.9133 20 26C20 22.6863 22.6863 20 26 20C27.9133 20 29.6174 20.8956 30.7161 22.2903Z" fill="black"/>
|
||||
</svg>
|
After Width: | Height: | Size: 444 B |
4
res/img/element-icons/roomlist/explore.svg
Normal file
|
@ -0,0 +1,4 @@
|
|||
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M17 9C17 13.4183 13.4183 17 9 17C4.58172 17 1 13.4183 1 9C1 4.58172 4.58172 1 9 1C13.4183 1 17 4.58172 17 9ZM13.375 5.3266C13.5583 4.92826 13.0716 4.44152 12.6733 4.62491L7.66968 6.9285C7.33893 7.08077 7.08014 7.33956 6.92787 7.67031L4.62428 12.6739C4.44089 13.0722 4.92763 13.559 5.32597 13.3756L10.3295 11.072C10.6603 10.9197 10.9191 10.6609 11.0714 10.3302L13.375 5.3266Z" fill="black"/>
|
||||
<path d="M9.8835 9.88413C9.39534 10.3723 8.60389 10.3723 8.11573 9.88413C7.62757 9.39597 7.62757 8.60452 8.11573 8.11636C8.60389 7.62821 9.39534 7.62821 9.8835 8.11636C10.3717 8.60452 10.3717 9.39597 9.8835 9.88413Z" fill="black"/>
|
||||
</svg>
|
After Width: | Height: | Size: 775 B |
3
res/img/element-icons/roomlist/plus.svg
Normal file
|
@ -0,0 +1,3 @@
|
|||
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.75 4C9.75 3.58579 9.41421 3.25 9 3.25C8.58579 3.25 8.25 3.58579 8.25 4V8.25L4 8.25C3.58579 8.25 3.25 8.58579 3.25 9C3.25 9.41421 3.58579 9.75 4 9.75L8.25 9.75V14C8.25 14.4142 8.58579 14.75 9 14.75C9.41421 14.75 9.75 14.4142 9.75 14V9.75L14 9.75C14.4142 9.75 14.75 9.41421 14.75 9C14.75 8.58579 14.4142 8.25 14 8.25L9.75 8.25V4Z" fill="black"/>
|
||||
</svg>
|
After Width: | Height: | Size: 499 B |
3
res/img/element-icons/roomlist/search-clear.svg
Normal file
|
@ -0,0 +1,3 @@
|
|||
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M9 16C12.866 16 16 12.866 16 9C16 5.13401 12.866 2 9 2C5.13401 2 2 5.13401 2 9C2 12.866 5.13401 16 9 16ZM5.96967 5.96967C6.26256 5.67678 6.73744 5.67678 7.03033 5.96967L9 7.93934L10.9697 5.96967C11.2626 5.67678 11.7374 5.67678 12.0303 5.96967C12.3232 6.26256 12.3232 6.73744 12.0303 7.03033L10.0607 9L12.0303 10.9697C12.3232 11.2626 12.3232 11.7374 12.0303 12.0303C11.7374 12.3232 11.2626 12.3232 10.9697 12.0303L9 10.0607L7.03033 12.0303C6.73744 12.3232 6.26256 12.3232 5.96967 12.0303C5.67678 11.7374 5.67678 11.2626 5.96967 10.9697L7.93934 9L5.96967 7.03033C5.67678 6.73744 5.67678 6.26256 5.96967 5.96967Z" fill="black"/>
|
||||
</svg>
|
After Width: | Height: | Size: 778 B |
3
res/img/element-icons/roomlist/search.svg
Normal file
|
@ -0,0 +1,3 @@
|
|||
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.1333 8.06667C12.1333 10.3126 10.3126 12.1333 8.06667 12.1333C5.82071 12.1333 4 10.3126 4 8.06667C4 5.82071 5.82071 4 8.06667 4C10.3126 4 12.1333 5.82071 12.1333 8.06667ZM12.9992 11.5994C13.7131 10.6044 14.1333 9.38463 14.1333 8.06667C14.1333 4.71614 11.4172 2 8.06667 2C4.71614 2 2 4.71614 2 8.06667C2 11.4172 4.71614 14.1333 8.06667 14.1333C9.38457 14.1333 10.6043 13.7131 11.5992 12.9993C11.6274 13.0369 11.6586 13.0729 11.6928 13.1071L14.2928 15.7071C14.6833 16.0977 15.3165 16.0977 15.707 15.7071C16.0975 15.3166 16.0975 14.6834 15.707 14.2929L13.107 11.6929C13.0728 11.6587 13.0368 11.6276 12.9992 11.5994Z" fill="black"/>
|
||||
</svg>
|
After Width: | Height: | Size: 784 B |
|
@ -1,6 +0,0 @@
|
|||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.64001 1.44C8.64001 0.64471 9.28472 0 10.08 0C15.3819 0 19.68 4.29807 19.68 9.6C19.68 10.3953 19.0353 11.04 18.24 11.04C17.4447 11.04 16.8 10.3953 16.8 9.6C16.8 5.88865 13.7914 2.88 10.08 2.88C9.28472 2.88 8.64001 2.23529 8.64001 1.44Z" fill="#0DBD8B"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M15.36 22.56C15.36 23.3553 14.7153 24 13.92 24C8.61805 24 4.31999 19.7019 4.31999 14.4C4.31999 13.6047 4.9647 12.96 5.75999 12.96C6.55528 12.96 7.19999 13.6047 7.19999 14.4C7.19999 18.1114 10.2086 21.12 13.92 21.12C14.7153 21.12 15.36 21.7647 15.36 22.56Z" fill="#0DBD8B"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M1.44 15.36C0.64471 15.36 -1.40906e-08 14.7153 -3.14722e-08 13.92C-1.4735e-07 8.61805 4.29807 4.31999 9.6 4.31998C10.3953 4.31998 11.04 4.96469 11.04 5.75998C11.04 6.55527 10.3953 7.19998 9.6 7.19998C5.88865 7.19998 2.88 10.2086 2.88 13.92C2.88 14.7153 2.23529 15.36 1.44 15.36Z" fill="#0DBD8B"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M22.56 8.64001C23.3553 8.64001 24 9.28472 24 10.08C24 15.3819 19.7019 19.68 14.4 19.68C13.6047 19.68 12.96 19.0353 12.96 18.24C12.96 17.4447 13.6047 16.8 14.4 16.8C18.1114 16.8 21.12 13.7914 21.12 10.08C21.12 9.28472 21.7647 8.64001 22.56 8.64001Z" fill="#0DBD8B"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 1.4 KiB |
4
res/img/feather-customised/clipboard.svg
Normal file
|
@ -0,0 +1,4 @@
|
|||
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M10.75 3.75H3.75V10.75" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<rect x="7" y="7" width="7" height="7" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 341 B |
|
@ -1 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-compass"><circle cx="12" cy="12" r="10"></circle><polygon points="16.24 7.76 14.12 14.12 7.76 16.24 9.88 9.88 16.24 7.76"></polygon></svg>
|
Before Width: | Height: | Size: 342 B |
|
@ -1,6 +1,3 @@
|
|||
<svg width="8" height="8" viewBox="0 0 8 8" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="icon">
|
||||
<path id="sea" fill-rule="evenodd" clip-rule="evenodd" d="M4 8C6.20914 8 8 6.20914 8 4C8 1.79086 6.20914 0 4 0C1.79086 0 0 1.79086 0 4C0 6.20914 1.79086 8 4 8ZM4.6693 2.43613C4.8306 2.64728 4.94732 2.80007 4.45289 2.80007C4.14732 2.80007 3.84175 2.74171 3.58076 2.69186C3.15847 2.61121 2.85289 2.55285 2.85289 2.80007C2.85289 3.00007 3.65289 3.40007 4.45289 3.80007C5.25289 4.20007 6.05289 4.60007 6.05289 4.80007C6.05289 5.20007 6.05289 7.60007 5.25289 7.20007C4.45289 6.80007 2.45289 5.20007 2.45289 4.80007C2.45289 4.65277 2.18168 4.39698 1.85897 4.09263C1.30535 3.57051 0.600192 2.90547 0.852893 2.40007C1.25289 1.60007 2.85289 6.51479e-05 5.25289 0.800065C4.98623 1.06673 4.45289 1.68007 4.45289 2.00007C4.45289 2.15285 4.56961 2.30564 4.6693 2.43613Z" fill="#2E2F32"/>
|
||||
<path id="earth" d="M4.45294 2.80007C5.25294 2.80007 4.45294 2.40007 4.45294 2.00007C4.45294 1.68007 4.98627 1.06673 5.25294 0.800065C2.85294 6.51479e-05 1.25294 1.60007 0.852941 2.40007C0.452941 3.20007 2.45294 4.40007 2.45294 4.80007C2.45294 5.20007 4.45294 6.80007 5.25294 7.20007C6.05294 7.60007 6.05294 5.20007 6.05294 4.80007C6.05294 4.40007 2.85294 3.20007 2.85294 2.80007C2.85294 2.40007 3.65294 2.80007 4.45294 2.80007Z" />
|
||||
</g>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M4 8C6.20914 8 8 6.20914 8 4C8 1.79086 6.20914 0 4 0C1.79086 0 0 1.79086 0 4C0 6.20914 1.79086 8 4 8ZM6.53332 5.3468L6.24528 6.23562C6.23346 6.27208 6.21342 6.30534 6.1867 6.33282L5.90913 6.61831L5.59095 6.94559L5.52111 7.01743C5.4003 7.14169 5.19279 7.10832 5.11702 6.95246L4.97216 6.65447C4.96052 6.63052 4.94513 6.60859 4.92657 6.58949L4.63641 6.29104L4.39185 6.0395C4.34478 5.99108 4.28013 5.96377 4.2126 5.96377H3.83831C3.74261 5.96377 3.6553 5.90914 3.61347 5.82307L3.38884 5.36098C3.37228 5.32692 3.36368 5.28955 3.36368 5.25168V4.47996C3.36368 4.38629 3.31132 4.30048 3.22802 4.25764L2.78114 4.02781C2.74577 4.00962 2.70657 4.00013 2.6668 4.00013H2.19658C2.12905 4.00013 2.0644 3.97282 2.01733 3.9244L1.53085 3.42402C1.48223 3.37402 1.45672 3.30599 1.46046 3.23635L1.49316 2.62739C1.4977 2.54285 1.51357 2.45929 1.54034 2.37897L1.65227 2.0432C1.87098 1.38707 2.38792 0.873558 3.0455 0.659224L3.05974 0.654815C3.66901 0.466234 4.32388 0.487134 4.91987 0.714181C4.94274 0.722892 4.96343 0.736497 4.98049 0.754043L5.42152 1.20768C5.51586 1.30471 5.51586 1.45919 5.42152 1.55622L4.70716 2.291C4.66179 2.33766 4.63641 2.40018 4.63641 2.46527V2.71165C4.63641 2.86997 4.49111 2.98843 4.33603 2.95652L3.34587 2.75283C3.1908 2.72093 3.0455 2.83938 3.0455 2.99771V3.09559C3.0455 3.23366 3.15743 3.34559 3.2955 3.34559H3.75004C3.88812 3.34559 4.00004 3.45752 4.00004 3.59559V3.75013C4.00004 3.8882 4.11197 4.00013 4.25004 4.00013H5.16715C5.23467 4.00013 5.29933 4.02745 5.3464 4.07586L5.56243 4.29807C5.58132 4.3175 5.60324 4.33373 5.62734 4.34612L6.19093 4.63596C6.21503 4.64836 6.23695 4.66459 6.25584 4.68402L6.47475 4.90918C6.52012 4.95584 6.5455 5.01836 6.5455 5.08345V5.26973C6.5455 5.2959 6.54139 5.3219 6.53332 5.3468Z" fill="#A9B2BC"/>
|
||||
</svg>
|
||||
|
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.8 KiB |
|
@ -1,86 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="19px"
|
||||
height="19px"
|
||||
viewBox="0 0 19 19"
|
||||
version="1.1"
|
||||
id="svg3734"
|
||||
sodipodi:docname="icon_copy_message.svg"
|
||||
inkscape:version="0.92.1 r">
|
||||
<metadata
|
||||
id="metadata3738">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title>ED5D3E59-2561-4AC1-9B43-82FBC51767FC</dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1596"
|
||||
inkscape:window-height="846"
|
||||
id="namedview3736"
|
||||
showgrid="false"
|
||||
inkscape:zoom="12.421053"
|
||||
inkscape:cx="3.4935767"
|
||||
inkscape:cy="2.469644"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="Symbols" />
|
||||
<!-- Generator: sketchtool 39.1 (31720) - http://www.bohemiancoding.com/sketch -->
|
||||
<title
|
||||
id="title3722">ED5D3E59-2561-4AC1-9B43-82FBC51767FC</title>
|
||||
<desc
|
||||
id="desc3724">Created with sketchtool.</desc>
|
||||
<defs
|
||||
id="defs3726" />
|
||||
<g
|
||||
id="Symbols"
|
||||
stroke="none"
|
||||
stroke-width="1"
|
||||
fill="none"
|
||||
fill-rule="evenodd">
|
||||
<path
|
||||
d="M 9.5,19 C 14.746705,19 19,14.746705 19,9.5 19,4.2532949 14.746705,0 9.5,0 4.2532949,0 0,4.2532949 0,9.5 0,14.746705 4.2532949,19 9.5,19 Z"
|
||||
id="Oval-69"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#ececec" />
|
||||
<g
|
||||
id="g4632"
|
||||
transform="translate(-2.3841858e-7,-1)">
|
||||
<rect
|
||||
style="stroke:#9b9b9b;stroke-width:0.91585475;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
y="4.3017478"
|
||||
x="4.6289611"
|
||||
height="10.396504"
|
||||
width="7.7420783"
|
||||
id="rect3745-3" />
|
||||
<rect
|
||||
style="fill:#ececec;fill-opacity:1;stroke:#9b9b9b;stroke-width:0.91585475;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
y="6.3017478"
|
||||
x="6.6289611"
|
||||
height="10.396504"
|
||||
width="7.7420783"
|
||||
id="rect3745" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 2.7 KiB |
|
@ -1,6 +0,0 @@
|
|||
<svg width="42" height="50" viewBox="0 0 42 50" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M1.04021 42.174C1.04021 45.944 4.27657 49 8.26822 49C12.2603 49 15.4962 45.944 15.4962 42.174V35.2587L22.8119 35.2518C23.2234 35.2518 23.6386 35.2385 24.0415 35.2122C33.5209 34.6096 40.9465 27.1046 40.9465 18.1261C40.9465 8.68273 32.8114 1 22.8119 1H8.26822C4.27657 1 1.04021 4.05637 1.04021 7.82645V27.7141C1.01327 27.9548 0.999582 28.1987 1.00001 28.4459C1.00001 28.6887 1.01412 28.9286 1.04021 29.1645V42.174ZM15.4963 21.6066V14.6525H22.812C24.8405 14.6525 26.4901 16.2103 26.4901 18.1261C26.4901 19.9469 24.9881 21.4692 23.0665 21.5916C22.9822 21.5969 22.8975 21.5993 22.8047 21.5993L15.4963 21.6066Z" fill="#A2DDEF"/>
|
||||
<path d="M15.4963 14.6525V14.0405H14.8844V14.6525H15.4963ZM15.4963 21.6066H14.8844V22.2191L15.4969 22.2185L15.4963 21.6066ZM22.8047 21.5993V20.9874H22.8041L22.8047 21.5993ZM23.0665 21.5916L23.1045 22.2024L23.1053 22.2023L23.0665 21.5916ZM1.04021 29.1645H1.65214V29.1308L1.64843 29.0972L1.04021 29.1645ZM1.00001 28.4459H1.61194L1.61193 28.4449L1.00001 28.4459ZM1.04021 27.7141L1.64834 27.7821L1.65214 27.7482V27.7141H1.04021ZM24.0415 35.2122L24.0027 34.6015L24.0017 34.6016L24.0415 35.2122ZM22.8119 35.2518V34.6399H22.8113L22.8119 35.2518ZM15.4962 35.2587L15.4957 34.6467L14.8843 34.6473V35.2587H15.4962ZM14.8844 14.6525V21.6066H16.1082V14.6525H14.8844ZM15.4969 22.2185L22.8053 22.2112L22.8041 20.9874L15.4957 20.9946L15.4969 22.2185ZM22.8047 22.2112C22.9085 22.2112 23.006 22.2085 23.1045 22.2024L23.0284 20.9809C22.9584 20.9852 22.8865 20.9874 22.8047 20.9874V22.2112ZM23.1053 22.2023C25.3203 22.0612 27.1021 20.2979 27.1021 18.1261H25.8782C25.8782 19.5959 24.6559 20.8772 23.0276 20.9809L23.1053 22.2023ZM27.1021 18.1261C27.1021 15.8399 25.145 14.0405 22.812 14.0405V15.2644C24.536 15.2644 25.8782 16.5808 25.8782 18.1261H27.1021ZM22.812 14.0405H15.4963V15.2644H22.812V14.0405ZM8.26822 48.3881C4.58104 48.3881 1.65214 45.5735 1.65214 42.174H0.428288C0.428288 46.3145 3.97209 49.6119 8.26822 49.6119V48.3881ZM1.65214 42.174V29.1645H0.428288V42.174H1.65214ZM1.64843 29.0972C1.62467 28.8824 1.61193 28.665 1.61193 28.4459H0.388085C0.388085 28.7124 0.403576 28.9748 0.431997 29.2318L1.64843 29.0972ZM1.61193 28.4449C1.61155 28.221 1.62394 28.0001 1.64834 27.7821L0.432085 27.646C0.402599 27.9094 0.387617 28.1765 0.388085 28.447L1.61193 28.4449ZM1.65214 27.7141V7.82645H0.428288V27.7141H1.65214ZM1.65214 7.82645C1.65214 4.42682 4.58109 1.61193 8.26822 1.61193V0.388075C3.97204 0.388075 0.428288 3.68592 0.428288 7.82645H1.65214ZM8.26822 1.61193H22.8119V0.388075H8.26822V1.61193ZM22.8119 1.61193C32.5069 1.61193 40.3346 9.05321 40.3346 18.1261H41.5584C41.5584 8.31226 33.1159 0.388075 22.8119 0.388075V1.61193ZM40.3346 18.1261C40.3346 26.7525 33.1898 34.0175 24.0027 34.6015L24.0804 35.8229C33.8521 35.2017 41.5584 27.4566 41.5584 18.1261H40.3346ZM24.0017 34.6016C23.6124 34.6269 23.2104 34.6399 22.8119 34.6399V35.8637C23.2363 35.8637 23.6649 35.85 24.0813 35.8228L24.0017 34.6016ZM22.8113 34.6399L15.4957 34.6467L15.4968 35.8706L22.8125 35.8637L22.8113 34.6399ZM14.8843 35.2587V42.174H16.1082V35.2587H14.8843ZM14.8843 42.174C14.8843 45.5736 11.9558 48.3881 8.26822 48.3881V49.6119C12.5648 49.6119 16.1082 46.3145 16.1082 42.174H14.8843Z" fill="#368BD6"/>
|
||||
<path d="M8.26823 42.174V7.82642H22.8119C28.8351 7.82642 33.7181 12.4378 33.7181 18.1261C33.7181 23.5784 29.232 28.0412 23.5561 28.4019C23.3098 28.4176 23.0621 28.4257 22.8119 28.4257L8.22803 28.4395" stroke="#368BD6" stroke-width="1.22385" stroke-linecap="round"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M15.4227 9.0113C15.758 7.21673 15.3325 5.4048 14.2243 3.91155C11.9366 0.828522 7.41753 0.0768486 4.15037 2.23574C2.56747 3.28105 1.51106 4.84579 1.17575 6.64116C0.84001 8.43653 1.26557 10.2481 2.37372 11.7413C4.66146 14.8243 9.18092 15.576 12.4481 13.4171C14.0306 12.3714 15.087 10.8071 15.4227 9.0113ZM27.852 46.0868C29.2587 47.9824 31.4998 48.9962 33.7777 48.9962C35.21 48.9962 36.6569 48.5951 37.9195 47.7594C41.1883 45.5965 41.9817 41.3397 39.6905 38.2522L29.4751 24.4846C27.1843 21.3972 22.6769 20.6475 19.408 22.8121C16.1392 24.975 15.3458 29.2318 17.6365 32.3192L27.852 46.0868Z" fill="#368BD6"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 4.1 KiB |
|
@ -46,7 +46,7 @@ $inverted-bg-color: $base-color;
|
|||
$selected-color: $room-highlight-color;
|
||||
|
||||
// selected for hoverover & selected event tiles
|
||||
$event-selected-color: $header-panel-bg-color;
|
||||
$event-selected-color: #21262c;
|
||||
|
||||
// used for the hairline dividers in RoomView
|
||||
$primary-hairline-color: transparent;
|
||||
|
@ -100,10 +100,9 @@ $roomheader-bg-color: $bg-color;
|
|||
$roomheader-addroom-bg-color: rgba(92, 100, 112, 0.3);
|
||||
$roomheader-addroom-fg-color: $text-primary-color;
|
||||
$tagpanel-button-color: $header-panel-text-primary-color;
|
||||
$roomheader-button-color: $header-panel-text-primary-color;
|
||||
$groupheader-button-color: $header-panel-text-primary-color;
|
||||
$rightpanel-button-color: $header-panel-text-primary-color;
|
||||
$composer-button-color: $header-panel-text-primary-color;
|
||||
$icon-button-color: #8E99A4;
|
||||
$roomtopic-color: $text-secondary-color;
|
||||
$eventtile-meta-color: $roomtopic-color;
|
||||
|
||||
|
@ -115,6 +114,7 @@ $composer-e2e-icon-color: $header-panel-text-primary-color;
|
|||
$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-filter-active-bg-color: $bg-color;
|
||||
$roomlist-bg-color: rgba(33, 38, 44, 0.90);
|
||||
$roomlist-header-color: $tertiary-fg-color;
|
||||
$roomsublist-divider-color: $primary-fg-color;
|
||||
|
|
|
@ -15,6 +15,8 @@ $room-highlight-color: #343a46;
|
|||
|
||||
// typical text (dark-on-white in light skin)
|
||||
$primary-fg-color: $text-primary-color;
|
||||
$secondary-fg-color: $primary-fg-color;
|
||||
$tertiary-fg-color: $primary-fg-color;
|
||||
$primary-bg-color: $bg-color;
|
||||
$muted-fg-color: $header-panel-text-primary-color;
|
||||
|
||||
|
@ -95,10 +97,9 @@ $roomheader-color: $text-primary-color;
|
|||
$roomheader-addroom-bg-color: #3c4556; // $search-placeholder-color at 0.5 opacity
|
||||
$roomheader-addroom-fg-color: $text-primary-color;
|
||||
$tagpanel-button-color: $header-panel-text-primary-color;
|
||||
$roomheader-button-color: $header-panel-text-primary-color;
|
||||
$groupheader-button-color: $header-panel-text-primary-color;
|
||||
$rightpanel-button-color: $header-panel-text-primary-color;
|
||||
$composer-button-color: $header-panel-text-primary-color;
|
||||
$icon-button-color: $header-panel-text-primary-color;
|
||||
$roomtopic-color: $text-secondary-color;
|
||||
$eventtile-meta-color: $roomtopic-color;
|
||||
|
||||
|
@ -110,6 +111,7 @@ $composer-e2e-icon-color: $header-panel-text-primary-color;
|
|||
$theme-button-bg-color: #e3e8f0;
|
||||
|
||||
$roomlist-button-bg-color: #1A1D23; // Buttons include the filter box, explore button, and sublist buttons
|
||||
$roomlist-filter-active-bg-color: $roomlist-button-bg-color;
|
||||
$roomlist-bg-color: $header-panel-bg-color;
|
||||
|
||||
$roomsublist-divider-color: $primary-fg-color;
|
||||
|
|
|
@ -23,6 +23,8 @@ $header-panel-bg-color: #f3f8fd;
|
|||
|
||||
// typical text (dark-on-white in light skin)
|
||||
$primary-fg-color: #2e2f32;
|
||||
$secondary-fg-color: $primary-fg-color;
|
||||
$tertiary-fg-color: $primary-fg-color;
|
||||
$primary-bg-color: #ffffff;
|
||||
$muted-fg-color: #61708b; // Commonly used in headings and relevant alt text
|
||||
|
||||
|
@ -162,10 +164,9 @@ $roomheader-bg-color: $primary-bg-color;
|
|||
$roomheader-addroom-bg-color: #91a1c0;
|
||||
$roomheader-addroom-fg-color: $accent-fg-color;
|
||||
$tagpanel-button-color: #91a1c0;
|
||||
$roomheader-button-color: #91a1c0;
|
||||
$groupheader-button-color: #91a1c0;
|
||||
$rightpanel-button-color: #91a1c0;
|
||||
$composer-button-color: #91a1c0;
|
||||
$icon-button-color: #91a1c0;
|
||||
$roomtopic-color: #9e9e9e;
|
||||
$eventtile-meta-color: $roomtopic-color;
|
||||
|
||||
|
@ -177,6 +178,7 @@ $header-divider-color: #91a1c0;
|
|||
$theme-button-bg-color: #e3e8f0;
|
||||
|
||||
$roomlist-button-bg-color: #fff; // Buttons include the filter box, explore button, and sublist buttons
|
||||
$roomlist-filter-active-bg-color: $roomlist-button-bg-color;
|
||||
$roomlist-bg-color: $header-panel-bg-color;
|
||||
$roomlist-header-color: $primary-fg-color;
|
||||
$roomsublist-divider-color: $primary-fg-color;
|
||||
|
@ -228,7 +230,8 @@ $event-redacted-border-color: #cccccc;
|
|||
// event timestamp
|
||||
$event-timestamp-color: #acacac;
|
||||
|
||||
$copy-button-url: "$(res)/img/icon_copy_message.svg";
|
||||
$copy-button-url: "$(res)/img/feather-customised/clipboard.svg";
|
||||
|
||||
|
||||
// e2e
|
||||
$e2e-verified-color: #76cfa5; // N.B. *NOT* the same as $accent-color
|
||||
|
|
|
@ -74,7 +74,7 @@ $droptarget-bg-color: rgba(255,255,255,0.5);
|
|||
$selected-color: $secondary-accent-color;
|
||||
|
||||
// selected for hoverover & selected event tiles
|
||||
$event-selected-color: $header-panel-bg-color;
|
||||
$event-selected-color: #f6f7f8;
|
||||
|
||||
// used for the hairline dividers in RoomView
|
||||
$primary-hairline-color: transparent;
|
||||
|
@ -158,10 +158,9 @@ $roomheader-bg-color: $primary-bg-color;
|
|||
$roomheader-addroom-bg-color: rgba(92, 100, 112, 0.2);
|
||||
$roomheader-addroom-fg-color: #5c6470;
|
||||
$tagpanel-button-color: #91A1C0;
|
||||
$roomheader-button-color: #91A1C0;
|
||||
$groupheader-button-color: #91A1C0;
|
||||
$rightpanel-button-color: #91A1C0;
|
||||
$composer-button-color: #91A1C0;
|
||||
$icon-button-color: #C1C6CD;
|
||||
$roomtopic-color: #9e9e9e;
|
||||
$eventtile-meta-color: $roomtopic-color;
|
||||
|
||||
|
@ -172,7 +171,8 @@ $header-divider-color: #91A1C0;
|
|||
|
||||
$theme-button-bg-color: #e3e8f0;
|
||||
|
||||
$roomlist-button-bg-color: #fff; // Buttons include the filter box, explore button, and sublist buttons
|
||||
$roomlist-button-bg-color: rgba(141, 151, 165, 0.2); // Buttons include the filter box, explore button, and sublist buttons
|
||||
$roomlist-filter-active-bg-color: #ffffff;
|
||||
$roomlist-bg-color: rgba(245, 245, 245, 0.90);
|
||||
$roomlist-header-color: $tertiary-fg-color;
|
||||
$roomsublist-divider-color: $primary-fg-color;
|
||||
|
@ -230,7 +230,7 @@ $event-redacted-border-color: #cccccc;
|
|||
// event timestamp
|
||||
$event-timestamp-color: #acacac;
|
||||
|
||||
$copy-button-url: "$(res)/img/icon_copy_message.svg";
|
||||
$copy-button-url: "$(res)/img/feather-customised/clipboard.svg";
|
||||
|
||||
// e2e
|
||||
$e2e-verified-color: #76cfa5; // N.B. *NOT* the same as $accent-color
|
||||
|
|
7
src/@types/global.d.ts
vendored
|
@ -19,7 +19,6 @@ import ContentMessages from "../ContentMessages";
|
|||
import { IMatrixClientPeg } from "../MatrixClientPeg";
|
||||
import ToastStore from "../stores/ToastStore";
|
||||
import DeviceListener from "../DeviceListener";
|
||||
import RebrandListener from "../RebrandListener";
|
||||
import { RoomListStoreClass } from "../stores/room-list/RoomListStore";
|
||||
import { PlatformPeg } from "../PlatformPeg";
|
||||
import RoomListLayoutStore from "../stores/room-list/RoomListLayoutStore";
|
||||
|
@ -40,7 +39,6 @@ declare global {
|
|||
mxContentMessages: ContentMessages;
|
||||
mxToastStore: ToastStore;
|
||||
mxDeviceListener: DeviceListener;
|
||||
mxRebrandListener: RebrandListener;
|
||||
mxRoomListStore: RoomListStoreClass;
|
||||
mxRoomListLayoutStore: RoomListLayoutStore;
|
||||
mxActiveRoomObserver: ActiveRoomObserver;
|
||||
|
@ -51,11 +49,6 @@ declare global {
|
|||
mxNotifier: typeof Notifier;
|
||||
}
|
||||
|
||||
// workaround for https://github.com/microsoft/TypeScript/issues/30933
|
||||
interface ObjectConstructor {
|
||||
fromEntries?(xs: [string|number|symbol, any][]): object;
|
||||
}
|
||||
|
||||
interface Document {
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/Document/hasStorageAccess
|
||||
hasStorageAccess?: () => Promise<boolean>;
|
||||
|
|
|
@ -155,7 +155,13 @@ export default abstract class BasePlatform {
|
|||
|
||||
loudNotification(ev: Event, room: Object) {
|
||||
}
|
||||
|
||||
clearNotification(notif: Notification) {
|
||||
// Some browsers don't support this, e.g Safari on iOS
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/Notification/close
|
||||
if (notif.close) {
|
||||
notif.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -197,7 +197,7 @@ export default class FromWidgetPostMessageApi {
|
|||
const integId = (data && data.integId) ? data.integId : null;
|
||||
|
||||
// TODO: Open the right integration manager for the widget
|
||||
if (SettingsStore.isFeatureEnabled("feature_many_integration_managers")) {
|
||||
if (SettingsStore.getValue("feature_many_integration_managers")) {
|
||||
IntegrationManagers.sharedInstance().openAll(
|
||||
MatrixClientPeg.get().getRoom(RoomViewStore.getRoomId()),
|
||||
`type_${integType}`,
|
||||
|
|
|
@ -421,7 +421,7 @@ export function bodyToHtml(content: IContent, highlights: string[], opts: IOpts
|
|||
}
|
||||
|
||||
let formattedBody = typeof content.formatted_body === 'string' ? content.formatted_body : null;
|
||||
const plainBody = typeof content.body === 'string' ? content.body : null;
|
||||
const plainBody = typeof content.body === 'string' ? content.body : "";
|
||||
|
||||
if (opts.stripReplyFallback && formattedBody) formattedBody = ReplyThread.stripHTMLReply(formattedBody);
|
||||
strippedBody = opts.stripReplyFallback ? ReplyThread.stripPlainReply(plainBody) : plainBody;
|
||||
|
|
|
@ -40,7 +40,6 @@ import ToastStore from "./stores/ToastStore";
|
|||
import {IntegrationManagers} from "./integrations/IntegrationManagers";
|
||||
import {Mjolnir} from "./mjolnir/Mjolnir";
|
||||
import DeviceListener from "./DeviceListener";
|
||||
import RebrandListener from "./RebrandListener";
|
||||
import {Jitsi} from "./widgets/Jitsi";
|
||||
import {SSO_HOMESERVER_URL_KEY, SSO_ID_SERVER_URL_KEY} from "./BasePlatform";
|
||||
|
||||
|
@ -647,8 +646,6 @@ async function startMatrixClient(startSyncing=true) {
|
|||
// Now that we have a MatrixClientPeg, update the Jitsi info
|
||||
await Jitsi.getInstance().start();
|
||||
|
||||
RebrandListener.sharedInstance().start();
|
||||
|
||||
// dispatch that we finished starting up to wire up any other bits
|
||||
// of the matrix client that cannot be set prior to starting up.
|
||||
dis.dispatch({action: 'client_started'});
|
||||
|
@ -710,7 +707,6 @@ export function stopMatrixClient(unsetClient=true) {
|
|||
IntegrationManagers.sharedInstance().stopWatching();
|
||||
Mjolnir.sharedInstance().stop();
|
||||
DeviceListener.sharedInstance().stop();
|
||||
RebrandListener.sharedInstance().stop();
|
||||
if (DMRoomMap.shared()) DMRoomMap.shared().stop();
|
||||
EventIndexPeg.stop();
|
||||
const cli = MatrixClientPeg.get();
|
||||
|
|
|
@ -1,184 +0,0 @@
|
|||
/*
|
||||
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import SdkConfig from "./SdkConfig";
|
||||
import ToastStore from "./stores/ToastStore";
|
||||
import GenericToast from "./components/views/toasts/GenericToast";
|
||||
import RebrandDialog from "./components/views/dialogs/RebrandDialog";
|
||||
import { RebrandDialogKind } from "./components/views/dialogs/RebrandDialog";
|
||||
import Modal from './Modal';
|
||||
import { _t } from './languageHandler';
|
||||
|
||||
const TOAST_KEY = 'rebrand';
|
||||
const NAG_INTERVAL = 24 * 60 * 60 * 1000;
|
||||
|
||||
function getRedirectUrl(url): string {
|
||||
const redirectUrl = new URL(url);
|
||||
redirectUrl.hash = '';
|
||||
|
||||
if (SdkConfig.get()['redirectToNewBrandUrl']) {
|
||||
const newUrl = new URL(SdkConfig.get()['redirectToNewBrandUrl']);
|
||||
if (url.hostname !== newUrl.hostname || url.pathname !== newUrl.pathname) {
|
||||
redirectUrl.hostname = newUrl.hostname;
|
||||
redirectUrl.pathname = newUrl.pathname;
|
||||
return redirectUrl.toString();
|
||||
}
|
||||
return null;
|
||||
} else if (url.hostname === 'riot.im') {
|
||||
if (url.pathname.startsWith('/app')) {
|
||||
redirectUrl.hostname = 'app.element.io';
|
||||
redirectUrl.pathname = '/';
|
||||
} else if (url.pathname.startsWith('/staging')) {
|
||||
redirectUrl.hostname = 'staging.element.io';
|
||||
redirectUrl.pathname = '/';
|
||||
} else if (url.pathname.startsWith('/develop')) {
|
||||
redirectUrl.hostname = 'develop.element.io';
|
||||
redirectUrl.pathname = '/';
|
||||
}
|
||||
|
||||
return redirectUrl.href;
|
||||
} else if (url.hostname.endsWith('.riot.im')) {
|
||||
redirectUrl.hostname = url.hostname.substr(0, url.hostname.length - '.riot.im'.length) + '.element.io';
|
||||
return redirectUrl.href;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows toasts informing the user that the name of the app has changed and,
|
||||
* potentially, that they should head to a different URL and log in there
|
||||
*/
|
||||
export default class RebrandListener {
|
||||
private _reshowTimer?: number;
|
||||
private nagAgainAt?: number = null;
|
||||
|
||||
static sharedInstance() {
|
||||
if (!window.mxRebrandListener) window.mxRebrandListener = new RebrandListener();
|
||||
return window.mxRebrandListener;
|
||||
}
|
||||
|
||||
constructor() {
|
||||
this._reshowTimer = null;
|
||||
}
|
||||
|
||||
start() {
|
||||
this.recheck();
|
||||
}
|
||||
|
||||
stop() {
|
||||
if (this._reshowTimer) {
|
||||
clearTimeout(this._reshowTimer);
|
||||
this._reshowTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
onNagToastLearnMore = async () => {
|
||||
const [doneClicked] = await Modal.createDialog(RebrandDialog, {
|
||||
kind: RebrandDialogKind.NAG,
|
||||
targetUrl: getRedirectUrl(window.location),
|
||||
}).finished;
|
||||
if (doneClicked) {
|
||||
// open in new tab: they should come back here & log out
|
||||
window.open(getRedirectUrl(window.location), '_blank');
|
||||
}
|
||||
|
||||
// whatever the user clicks, we go away & nag again after however long:
|
||||
// If they went to the new URL, we want to nag them to log out if they
|
||||
// come back to this tab, and if they clicked, 'remind me later' we want
|
||||
// to, well, remind them later.
|
||||
this.nagAgainAt = Date.now() + NAG_INTERVAL;
|
||||
this.recheck();
|
||||
};
|
||||
|
||||
onOneTimeToastLearnMore = async () => {
|
||||
const [doneClicked] = await Modal.createDialog(RebrandDialog, {
|
||||
kind: RebrandDialogKind.ONE_TIME,
|
||||
}).finished;
|
||||
if (doneClicked) {
|
||||
localStorage.setItem('mx_rename_dialog_dismissed', 'true');
|
||||
this.recheck();
|
||||
}
|
||||
};
|
||||
|
||||
onOneTimeToastDismiss = async () => {
|
||||
localStorage.setItem('mx_rename_dialog_dismissed', 'true');
|
||||
this.recheck();
|
||||
};
|
||||
|
||||
onNagTimerFired = () => {
|
||||
this._reshowTimer = null;
|
||||
this.nagAgainAt = null;
|
||||
this.recheck();
|
||||
};
|
||||
|
||||
private async recheck() {
|
||||
// There are two types of toast/dialog we show: a 'one time' informing the user that
|
||||
// the app is now called a different thing but no action is required from them (they
|
||||
// may need to look for a different name name/icon to launch the app but don't need to
|
||||
// log in again) and a nag toast where they need to log in to the app on a different domain.
|
||||
let nagToast = false;
|
||||
let oneTimeToast = false;
|
||||
|
||||
if (getRedirectUrl(window.location)) {
|
||||
if (!this.nagAgainAt) {
|
||||
// if we have redirectUrl, show the nag toast
|
||||
nagToast = true;
|
||||
}
|
||||
} else {
|
||||
// otherwise we show the 'one time' toast / dialog
|
||||
const renameDialogDismissed = localStorage.getItem('mx_rename_dialog_dismissed');
|
||||
if (renameDialogDismissed !== 'true') {
|
||||
oneTimeToast = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (nagToast || oneTimeToast) {
|
||||
let description;
|
||||
let rejectLabel = null;
|
||||
let onReject = null;
|
||||
if (nagToast) {
|
||||
description = _t("Use your account to sign in to the latest version");
|
||||
} else {
|
||||
description = _t("We’re excited to announce Riot is now Element");
|
||||
rejectLabel = _t("Dismiss");
|
||||
onReject = this.onOneTimeToastDismiss;
|
||||
}
|
||||
|
||||
ToastStore.sharedInstance().addOrReplaceToast({
|
||||
key: TOAST_KEY,
|
||||
title: _t("Riot is now Element!"),
|
||||
icon: 'element_logo',
|
||||
props: {
|
||||
description,
|
||||
acceptLabel: _t("Learn More"),
|
||||
onAccept: nagToast ? this.onNagToastLearnMore : this.onOneTimeToastLearnMore,
|
||||
rejectLabel,
|
||||
onReject,
|
||||
},
|
||||
component: GenericToast,
|
||||
priority: 20,
|
||||
});
|
||||
} else {
|
||||
ToastStore.sharedInstance().dismissToast(TOAST_KEY);
|
||||
}
|
||||
|
||||
if (!this._reshowTimer && this.nagAgainAt) {
|
||||
// XXX: Our build system picks up NodeJS bindings when we need browser bindings.
|
||||
this._reshowTimer = setTimeout(this.onNagTimerFired, (this.nagAgainAt - Date.now()) + 100) as any as number;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -43,7 +43,7 @@ import SdkConfig from "./SdkConfig";
|
|||
import { ensureDMExists } from "./createRoom";
|
||||
import { ViewUserPayload } from "./dispatcher/payloads/ViewUserPayload";
|
||||
import { Action } from "./dispatcher/actions";
|
||||
import { EffectiveMembership, getEffectiveMembership } from "./utils/membership";
|
||||
import { EffectiveMembership, getEffectiveMembership, leaveRoomBehaviour } from "./utils/membership";
|
||||
import SettingsStore from "./settings/SettingsStore";
|
||||
|
||||
// XXX: workaround for https://github.com/microsoft/TypeScript/issues/31816
|
||||
|
@ -602,11 +602,7 @@ export const Commands = [
|
|||
}
|
||||
|
||||
if (!targetRoomId) targetRoomId = roomId;
|
||||
return success(
|
||||
cli.leaveRoomChain(targetRoomId).then(function() {
|
||||
dis.dispatch({action: 'view_next_room'});
|
||||
}),
|
||||
);
|
||||
return success(leaveRoomBehaviour(targetRoomId));
|
||||
},
|
||||
category: CommandCategories.actions,
|
||||
}),
|
||||
|
@ -734,7 +730,7 @@ export const Commands = [
|
|||
const cli = MatrixClientPeg.get();
|
||||
const room = cli.getRoom(roomId);
|
||||
if (!room) return reject(_t("Command failed"));
|
||||
const member = room.getMember(args);
|
||||
const member = room.getMember(userId);
|
||||
if (!member || getEffectiveMembership(member.membership) === EffectiveMembership.Leave) {
|
||||
return reject(_t("Could not find user in room"));
|
||||
}
|
||||
|
|
|
@ -1003,9 +1003,10 @@ export default createReactClass({
|
|||
this.state.inviterProfile.avatarUrl, 36, 36,
|
||||
) : null;
|
||||
|
||||
let inviterName = group.inviter.userId;
|
||||
const inviter = group.inviter || {};
|
||||
let inviterName = inviter.userId;
|
||||
if (this.state.inviterProfile) {
|
||||
inviterName = this.state.inviterProfile.displayName || group.inviter.userId;
|
||||
inviterName = this.state.inviterProfile.displayName || inviter.userId;
|
||||
}
|
||||
return <div className="mx_GroupView_membershipSection mx_GroupView_membershipSection_invited">
|
||||
<div className="mx_GroupView_membershipSubSection">
|
||||
|
@ -1016,7 +1017,7 @@ export default createReactClass({
|
|||
height={36}
|
||||
/>
|
||||
{ _t("%(inviter)s has invited you to join this community", {
|
||||
inviter: inviterName,
|
||||
inviter: inviterName || _t("Someone"),
|
||||
}) }
|
||||
</div>
|
||||
<div className="mx_GroupView_membership_buttonContainer">
|
||||
|
|
|
@ -37,6 +37,7 @@ import IndicatorScrollbar from "../structures/IndicatorScrollbar";
|
|||
import AccessibleTooltipButton from "../views/elements/AccessibleTooltipButton";
|
||||
import { OwnProfileStore } from "../../stores/OwnProfileStore";
|
||||
import { MatrixClientPeg } from "../../MatrixClientPeg";
|
||||
import RoomListNumResults from "../views/rooms/RoomListNumResults";
|
||||
|
||||
interface IProps {
|
||||
isMinimized: boolean;
|
||||
|
@ -377,7 +378,7 @@ export default class LeftPanel extends React.Component<IProps, IState> {
|
|||
const tagPanel = !this.state.showTagPanel ? null : (
|
||||
<div className="mx_LeftPanel_tagPanelContainer">
|
||||
<TagPanel/>
|
||||
{SettingsStore.isFeatureEnabled("feature_custom_tags") ? <CustomRoomTagPanel /> : null}
|
||||
{SettingsStore.getValue("feature_custom_tags") ? <CustomRoomTagPanel /> : null}
|
||||
</div>
|
||||
);
|
||||
|
||||
|
@ -409,6 +410,7 @@ export default class LeftPanel extends React.Component<IProps, IState> {
|
|||
{this.renderHeader()}
|
||||
{this.renderSearchExplore()}
|
||||
{this.renderBreadcrumbs()}
|
||||
<RoomListNumResults />
|
||||
<div className="mx_LeftPanel_roomListWrapper">
|
||||
<div
|
||||
className={roomListClasses}
|
||||
|
|
|
@ -76,6 +76,7 @@ import { OpenToTabPayload } from "../../dispatcher/payloads/OpenToTabPayload";
|
|||
import ErrorDialog from "../views/dialogs/ErrorDialog";
|
||||
import { RoomNotificationStateStore } from "../../stores/notifications/RoomNotificationStateStore";
|
||||
import { SettingLevel } from "../../settings/SettingLevel";
|
||||
import { leaveRoomBehaviour } from "../../utils/membership";
|
||||
|
||||
/** constants for MatrixChat.state.view */
|
||||
export enum Views {
|
||||
|
@ -1082,50 +1083,13 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
|||
button: _t("Leave"),
|
||||
onFinished: (shouldLeave) => {
|
||||
if (shouldLeave) {
|
||||
const d = MatrixClientPeg.get().leaveRoomChain(roomId);
|
||||
const d = leaveRoomBehaviour(roomId);
|
||||
|
||||
// FIXME: controller shouldn't be loading a view :(
|
||||
const Loader = sdk.getComponent("elements.Spinner");
|
||||
const modal = Modal.createDialog(Loader, null, 'mx_Dialog_spinner');
|
||||
|
||||
d.then((errors) => {
|
||||
modal.close();
|
||||
|
||||
for (const leftRoomId of Object.keys(errors)) {
|
||||
const err = errors[leftRoomId];
|
||||
if (!err) continue;
|
||||
|
||||
console.error("Failed to leave room " + leftRoomId + " " + err);
|
||||
let title = _t("Failed to leave room");
|
||||
let message = _t("Server may be unavailable, overloaded, or you hit a bug.");
|
||||
if (err.errcode === 'M_CANNOT_LEAVE_SERVER_NOTICE_ROOM') {
|
||||
title = _t("Can't leave Server Notices room");
|
||||
message = _t(
|
||||
"This room is used for important messages from the Homeserver, " +
|
||||
"so you cannot leave it.",
|
||||
);
|
||||
} else if (err && err.message) {
|
||||
message = err.message;
|
||||
}
|
||||
Modal.createTrackedDialog('Failed to leave room', '', ErrorDialog, {
|
||||
title: title,
|
||||
description: message,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.state.currentRoomId === roomId) {
|
||||
dis.dispatch({action: 'view_next_room'});
|
||||
}
|
||||
}, (err) => {
|
||||
// This should only happen if something went seriously wrong with leaving the chain.
|
||||
modal.close();
|
||||
console.error("Failed to leave room " + roomId + " " + err);
|
||||
Modal.createTrackedDialog('Failed to leave room', '', ErrorDialog, {
|
||||
title: _t("Failed to leave room"),
|
||||
description: _t("Unknown error"),
|
||||
});
|
||||
});
|
||||
d.finally(() => modal.close());
|
||||
}
|
||||
},
|
||||
});
|
||||
|
|
|
@ -126,7 +126,8 @@ export default class RoomSearch extends React.PureComponent<IProps, IState> {
|
|||
public render(): React.ReactNode {
|
||||
const classes = classNames({
|
||||
'mx_RoomSearch': true,
|
||||
'mx_RoomSearch_expanded': this.state.query || this.state.focused,
|
||||
'mx_RoomSearch_hasQuery': this.state.query,
|
||||
'mx_RoomSearch_focused': this.state.focused,
|
||||
'mx_RoomSearch_minimized': this.props.isMinimized,
|
||||
});
|
||||
|
||||
|
|
|
@ -1933,6 +1933,7 @@ export default createReactClass({
|
|||
disabled={this.props.disabled}
|
||||
showApps={this.state.showApps}
|
||||
e2eStatus={this.state.e2eStatus}
|
||||
resizeNotifier={this.props.resizeNotifier}
|
||||
permalinkCreator={this._getPermalinkCreatorForRoom(this.state.room)}
|
||||
/>;
|
||||
}
|
||||
|
|
|
@ -14,15 +14,22 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import React from "react";
|
||||
import classNames from "classnames";
|
||||
import { Room } from "matrix-js-sdk/src/models/room";
|
||||
import { User } from "matrix-js-sdk/src/models/user";
|
||||
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||
|
||||
import { TagID } from '../../../stores/room-list/models';
|
||||
import RoomAvatar from "./RoomAvatar";
|
||||
import RoomTileIcon from "../rooms/RoomTileIcon";
|
||||
import NotificationBadge from '../rooms/NotificationBadge';
|
||||
import { RoomNotificationStateStore } from "../../../stores/notifications/RoomNotificationStateStore";
|
||||
import { NotificationState } from "../../../stores/notifications/NotificationState";
|
||||
import {isPresenceEnabled} from "../../../utils/presence";
|
||||
import {MatrixClientPeg} from "../../../MatrixClientPeg";
|
||||
import {_t} from "../../../languageHandler";
|
||||
import TextWithTooltip from "../elements/TextWithTooltip";
|
||||
import DMRoomMap from "../../../utils/DMRoomMap";
|
||||
|
||||
interface IProps {
|
||||
room: Room;
|
||||
|
@ -36,18 +43,134 @@ interface IProps {
|
|||
|
||||
interface IState {
|
||||
notificationState?: NotificationState;
|
||||
icon: Icon;
|
||||
}
|
||||
|
||||
enum Icon {
|
||||
// Note: the names here are used in CSS class names
|
||||
None = "NONE", // ... except this one
|
||||
Globe = "GLOBE",
|
||||
PresenceOnline = "ONLINE",
|
||||
PresenceAway = "AWAY",
|
||||
PresenceOffline = "OFFLINE",
|
||||
}
|
||||
|
||||
function tooltipText(variant: Icon) {
|
||||
switch (variant) {
|
||||
case Icon.Globe:
|
||||
return _t("This room is public");
|
||||
case Icon.PresenceOnline:
|
||||
return _t("Online");
|
||||
case Icon.PresenceAway:
|
||||
return _t("Away");
|
||||
case Icon.PresenceOffline:
|
||||
return _t("Offline");
|
||||
}
|
||||
}
|
||||
|
||||
export default class DecoratedRoomAvatar extends React.PureComponent<IProps, IState> {
|
||||
private _dmUser: User;
|
||||
private isUnmounted = false;
|
||||
private isWatchingTimeline = false;
|
||||
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
notificationState: RoomNotificationStateStore.instance.getRoomState(this.props.room),
|
||||
icon: this.calculateIcon(),
|
||||
};
|
||||
}
|
||||
|
||||
public componentWillUnmount() {
|
||||
this.isUnmounted = true;
|
||||
if (this.isWatchingTimeline) this.props.room.off('Room.timeline', this.onRoomTimeline);
|
||||
this.dmUser = null; // clear listeners, if any
|
||||
}
|
||||
|
||||
private get isPublicRoom(): boolean {
|
||||
const joinRules = this.props.room.currentState.getStateEvents("m.room.join_rules", "");
|
||||
const joinRule = joinRules && joinRules.getContent().join_rule;
|
||||
return joinRule === 'public';
|
||||
}
|
||||
|
||||
private get dmUser(): User {
|
||||
return this._dmUser;
|
||||
}
|
||||
|
||||
private set dmUser(val: User) {
|
||||
const oldUser = this._dmUser;
|
||||
this._dmUser = val;
|
||||
if (oldUser && oldUser !== this._dmUser) {
|
||||
oldUser.off('User.currentlyActive', this.onPresenceUpdate);
|
||||
oldUser.off('User.presence', this.onPresenceUpdate);
|
||||
}
|
||||
if (this._dmUser && oldUser !== this._dmUser) {
|
||||
this._dmUser.on('User.currentlyActive', this.onPresenceUpdate);
|
||||
this._dmUser.on('User.presence', this.onPresenceUpdate);
|
||||
}
|
||||
}
|
||||
|
||||
private onRoomTimeline = (ev: MatrixEvent, room: Room) => {
|
||||
if (this.isUnmounted) return;
|
||||
|
||||
// apparently these can happen?
|
||||
if (!room) return;
|
||||
if (this.props.room.roomId !== room.roomId) return;
|
||||
|
||||
if (ev.getType() === 'm.room.join_rules' || ev.getType() === 'm.room.member') {
|
||||
this.setState({icon: this.calculateIcon()});
|
||||
}
|
||||
};
|
||||
|
||||
private onPresenceUpdate = () => {
|
||||
if (this.isUnmounted) return;
|
||||
|
||||
let newIcon = this.getPresenceIcon();
|
||||
if (newIcon !== this.state.icon) this.setState({icon: newIcon});
|
||||
};
|
||||
|
||||
private getPresenceIcon(): Icon {
|
||||
if (!this.dmUser) return Icon.None;
|
||||
|
||||
let icon = Icon.None;
|
||||
|
||||
const isOnline = this.dmUser.currentlyActive || this.dmUser.presence === 'online';
|
||||
if (isOnline) {
|
||||
icon = Icon.PresenceOnline;
|
||||
} else if (this.dmUser.presence === 'offline') {
|
||||
icon = Icon.PresenceOffline;
|
||||
} else if (this.dmUser.presence === 'unavailable') {
|
||||
icon = Icon.PresenceAway;
|
||||
}
|
||||
|
||||
return icon;
|
||||
}
|
||||
|
||||
private calculateIcon(): Icon {
|
||||
let icon = Icon.None;
|
||||
|
||||
// We look at the DMRoomMap and not the tag here so that we don't exclude DMs in Favourites
|
||||
const otherUserId = DMRoomMap.shared().getUserIdForRoomId(this.props.room.roomId);
|
||||
if (otherUserId && this.props.room.getJoinedMemberCount() === 2) {
|
||||
// Track presence, if available
|
||||
if (isPresenceEnabled()) {
|
||||
if (otherUserId) {
|
||||
this.dmUser = MatrixClientPeg.get().getUser(otherUserId);
|
||||
icon = this.getPresenceIcon();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Track publicity
|
||||
icon = this.isPublicRoom ? Icon.Globe : Icon.None;
|
||||
if (!this.isWatchingTimeline) {
|
||||
this.props.room.on('Room.timeline', this.onRoomTimeline);
|
||||
this.isWatchingTimeline = true;
|
||||
}
|
||||
}
|
||||
return icon;
|
||||
}
|
||||
|
||||
public render(): React.ReactNode {
|
||||
let badge: React.ReactNode;
|
||||
if (this.props.displayBadge) {
|
||||
|
@ -58,7 +181,19 @@ export default class DecoratedRoomAvatar extends React.PureComponent<IProps, ISt
|
|||
/>;
|
||||
}
|
||||
|
||||
return <div className="mx_DecoratedRoomAvatar">
|
||||
let icon;
|
||||
if (this.state.icon !== Icon.None) {
|
||||
icon = <TextWithTooltip
|
||||
tooltip={tooltipText(this.state.icon)}
|
||||
class={`mx_DecoratedRoomAvatar_icon mx_DecoratedRoomAvatar_icon_${this.state.icon.toLowerCase()}`}
|
||||
/>;
|
||||
}
|
||||
|
||||
const classes = classNames("mx_DecoratedRoomAvatar", {
|
||||
mx_DecoratedRoomAvatar_cutout: icon,
|
||||
});
|
||||
|
||||
return <div className={classes}>
|
||||
<RoomAvatar
|
||||
room={this.props.room}
|
||||
width={this.props.avatarSize}
|
||||
|
@ -66,7 +201,7 @@ export default class DecoratedRoomAvatar extends React.PureComponent<IProps, ISt
|
|||
oobData={this.props.oobData}
|
||||
viewAvatarOnClick={this.props.viewAvatarOnClick}
|
||||
/>
|
||||
<RoomTileIcon room={this.props.room} />
|
||||
{icon}
|
||||
{badge}
|
||||
</div>;
|
||||
}
|
||||
|
|
|
@ -53,7 +53,7 @@ export default class MemberStatusMessageAvatar extends React.Component {
|
|||
if (this.props.member.userId !== MatrixClientPeg.get().getUserId()) {
|
||||
throw new Error("Cannot use MemberStatusMessageAvatar on anyone but the logged in user");
|
||||
}
|
||||
if (!SettingsStore.isFeatureEnabled("feature_custom_status")) {
|
||||
if (!SettingsStore.getValue("feature_custom_status")) {
|
||||
return;
|
||||
}
|
||||
const { user } = this.props.member;
|
||||
|
@ -105,7 +105,7 @@ export default class MemberStatusMessageAvatar extends React.Component {
|
|||
resizeMethod={this.props.resizeMethod}
|
||||
/>;
|
||||
|
||||
if (!SettingsStore.isFeatureEnabled("feature_custom_status")) {
|
||||
if (!SettingsStore.getValue("feature_custom_status")) {
|
||||
return avatar;
|
||||
}
|
||||
|
||||
|
|
|
@ -114,9 +114,12 @@ export default class RoomAvatar extends React.Component<IProps, IState> {
|
|||
}
|
||||
|
||||
private onRoomAvatarClick = () => {
|
||||
const avatarUrl = this.props.room.getAvatarUrl(
|
||||
MatrixClientPeg.get().getHomeserverUrl(),
|
||||
null, null, null, false);
|
||||
const avatarUrl = Avatar.avatarUrlForRoom(
|
||||
this.props.room,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
);
|
||||
const params = {
|
||||
src: avatarUrl,
|
||||
name: this.props.room.name,
|
||||
|
|
|
@ -81,7 +81,7 @@ export default createReactClass({
|
|||
let canPin = room.currentState.mayClientSendStateEvent('m.room.pinned_events', cli);
|
||||
|
||||
// HACK: Intentionally say we can't pin if the user doesn't want to use the functionality
|
||||
if (!SettingsStore.isFeatureEnabled("feature_pinning")) canPin = false;
|
||||
if (!SettingsStore.getValue("feature_pinning")) canPin = false;
|
||||
|
||||
this.setState({canRedact, canPin});
|
||||
},
|
||||
|
|
|
@ -1,404 +0,0 @@
|
|||
/*
|
||||
Copyright 2015, 2016 OpenMarket Ltd
|
||||
Copyright 2017 Vector Creations Ltd
|
||||
Copyright 2019 Michael Telatynski <7t3chguy@gmail.com>
|
||||
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 classNames from 'classnames';
|
||||
import * as sdk from '../../../index';
|
||||
import { _t, _td } from '../../../languageHandler';
|
||||
import {MatrixClientPeg} from '../../../MatrixClientPeg';
|
||||
import dis from '../../../dispatcher/dispatcher';
|
||||
import DMRoomMap from '../../../utils/DMRoomMap';
|
||||
import * as Rooms from '../../../Rooms';
|
||||
import * as RoomNotifs from '../../../RoomNotifs';
|
||||
import Modal from '../../../Modal';
|
||||
import RoomListActions from '../../../actions/RoomListActions';
|
||||
import RoomViewStore from '../../../stores/RoomViewStore';
|
||||
import {sleep} from "../../../utils/promise";
|
||||
import {MenuItem, MenuItemCheckbox, MenuItemRadio} from "../../structures/ContextMenu";
|
||||
|
||||
const RoomTagOption = ({active, onClick, src, srcSet, label}) => {
|
||||
const classes = classNames('mx_RoomTileContextMenu_tag_field', {
|
||||
'mx_RoomTileContextMenu_tag_fieldSet': active,
|
||||
'mx_RoomTileContextMenu_tag_fieldDisabled': false,
|
||||
});
|
||||
|
||||
return (
|
||||
<MenuItemCheckbox className={classes} onClick={onClick} active={active} label={label}>
|
||||
<img className="mx_RoomTileContextMenu_tag_icon" src={src} width="15" height="15" alt="" />
|
||||
<img className="mx_RoomTileContextMenu_tag_icon_set" src={srcSet} width="15" height="15" alt="" />
|
||||
{ label }
|
||||
</MenuItemCheckbox>
|
||||
);
|
||||
};
|
||||
|
||||
const NotifOption = ({active, onClick, src, label}) => {
|
||||
const classes = classNames('mx_RoomTileContextMenu_notif_field', {
|
||||
'mx_RoomTileContextMenu_notif_fieldSet': active,
|
||||
});
|
||||
|
||||
return (
|
||||
<MenuItemRadio className={classes} onClick={onClick} active={active} label={label}>
|
||||
<img className="mx_RoomTileContextMenu_notif_activeIcon" src={require("../../../../res/img/notif-active.svg")} width="12" height="12" alt="" />
|
||||
<img className="mx_RoomTileContextMenu_notif_icon mx_filterFlipColor" src={src} width="16" height="12" alt="" />
|
||||
{ label }
|
||||
</MenuItemRadio>
|
||||
);
|
||||
};
|
||||
|
||||
export default createReactClass({
|
||||
displayName: 'RoomTileContextMenu',
|
||||
|
||||
propTypes: {
|
||||
room: PropTypes.object.isRequired,
|
||||
/* callback called when the menu is dismissed */
|
||||
onFinished: PropTypes.func,
|
||||
},
|
||||
|
||||
getInitialState() {
|
||||
const dmRoomMap = new DMRoomMap(MatrixClientPeg.get());
|
||||
return {
|
||||
roomNotifState: RoomNotifs.getRoomNotifsState(this.props.room.roomId),
|
||||
isFavourite: this.props.room.tags.hasOwnProperty("m.favourite"),
|
||||
isLowPriority: this.props.room.tags.hasOwnProperty("m.lowpriority"),
|
||||
isDirectMessage: Boolean(dmRoomMap.getUserIdForRoomId(this.props.room.roomId)),
|
||||
};
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
this._unmounted = false;
|
||||
},
|
||||
|
||||
componentWillUnmount: function() {
|
||||
this._unmounted = true;
|
||||
},
|
||||
|
||||
_toggleTag: function(tagNameOn, tagNameOff) {
|
||||
if (!MatrixClientPeg.get().isGuest()) {
|
||||
sleep(500).then(() => {
|
||||
dis.dispatch(RoomListActions.tagRoom(
|
||||
MatrixClientPeg.get(),
|
||||
this.props.room,
|
||||
tagNameOff, tagNameOn,
|
||||
undefined, 0,
|
||||
), true);
|
||||
|
||||
this.props.onFinished();
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
_onClickFavourite: function() {
|
||||
// Tag room as 'Favourite'
|
||||
if (!this.state.isFavourite && this.state.isLowPriority) {
|
||||
this.setState({
|
||||
isFavourite: true,
|
||||
isLowPriority: false,
|
||||
});
|
||||
this._toggleTag("m.favourite", "m.lowpriority");
|
||||
} else if (this.state.isFavourite) {
|
||||
this.setState({isFavourite: false});
|
||||
this._toggleTag(null, "m.favourite");
|
||||
} else if (!this.state.isFavourite) {
|
||||
this.setState({isFavourite: true});
|
||||
this._toggleTag("m.favourite");
|
||||
}
|
||||
},
|
||||
|
||||
_onClickLowPriority: function() {
|
||||
// Tag room as 'Low Priority'
|
||||
if (!this.state.isLowPriority && this.state.isFavourite) {
|
||||
this.setState({
|
||||
isFavourite: false,
|
||||
isLowPriority: true,
|
||||
});
|
||||
this._toggleTag("m.lowpriority", "m.favourite");
|
||||
} else if (this.state.isLowPriority) {
|
||||
this.setState({isLowPriority: false});
|
||||
this._toggleTag(null, "m.lowpriority");
|
||||
} else if (!this.state.isLowPriority) {
|
||||
this.setState({isLowPriority: true});
|
||||
this._toggleTag("m.lowpriority");
|
||||
}
|
||||
},
|
||||
|
||||
_onClickDM: function() {
|
||||
if (MatrixClientPeg.get().isGuest()) return;
|
||||
|
||||
const newIsDirectMessage = !this.state.isDirectMessage;
|
||||
this.setState({
|
||||
isDirectMessage: newIsDirectMessage,
|
||||
});
|
||||
|
||||
Rooms.guessAndSetDMRoom(
|
||||
this.props.room, newIsDirectMessage,
|
||||
).then(sleep(500)).finally(() => {
|
||||
// Close the context menu
|
||||
if (this.props.onFinished) {
|
||||
this.props.onFinished();
|
||||
}
|
||||
}, (err) => {
|
||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
Modal.createTrackedDialog('Failed to set Direct Message status of room', '', ErrorDialog, {
|
||||
title: _t('Failed to set Direct Message status of room'),
|
||||
description: ((err && err.message) ? err.message : _t('Operation failed')),
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
_onClickLeave: function() {
|
||||
// Leave room
|
||||
dis.dispatch({
|
||||
action: 'leave_room',
|
||||
room_id: this.props.room.roomId,
|
||||
});
|
||||
|
||||
// Close the context menu
|
||||
if (this.props.onFinished) {
|
||||
this.props.onFinished();
|
||||
}
|
||||
},
|
||||
|
||||
_onClickReject: function() {
|
||||
dis.dispatch({
|
||||
action: 'reject_invite',
|
||||
room_id: this.props.room.roomId,
|
||||
});
|
||||
|
||||
// Close the context menu
|
||||
if (this.props.onFinished) {
|
||||
this.props.onFinished();
|
||||
}
|
||||
},
|
||||
|
||||
_onClickForget: function() {
|
||||
// FIXME: duplicated with RoomSettings (and dead code in RoomView)
|
||||
MatrixClientPeg.get().forget(this.props.room.roomId).then(() => {
|
||||
// Switch to another room view if we're currently viewing the
|
||||
// historical room
|
||||
if (RoomViewStore.getRoomId() === this.props.room.roomId) {
|
||||
dis.dispatch({ action: 'view_next_room' });
|
||||
}
|
||||
}, function(err) {
|
||||
const errCode = err.errcode || _td("unknown error code");
|
||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
Modal.createTrackedDialog('Failed to forget room', '', ErrorDialog, {
|
||||
title: _t('Failed to forget room %(errCode)s', {errCode: errCode}),
|
||||
description: ((err && err.message) ? err.message : _t('Operation failed')),
|
||||
});
|
||||
});
|
||||
|
||||
// Close the context menu
|
||||
if (this.props.onFinished) {
|
||||
this.props.onFinished();
|
||||
}
|
||||
},
|
||||
|
||||
_saveNotifState: function(newState) {
|
||||
if (MatrixClientPeg.get().isGuest()) return;
|
||||
|
||||
const oldState = this.state.roomNotifState;
|
||||
const roomId = this.props.room.roomId;
|
||||
|
||||
this.setState({
|
||||
roomNotifState: newState,
|
||||
});
|
||||
RoomNotifs.setRoomNotifsState(roomId, newState).then(() => {
|
||||
// delay slightly so that the user can see their state change
|
||||
// before closing the menu
|
||||
return sleep(500).then(() => {
|
||||
if (this._unmounted) return;
|
||||
// Close the context menu
|
||||
if (this.props.onFinished) {
|
||||
this.props.onFinished();
|
||||
}
|
||||
});
|
||||
}, (error) => {
|
||||
// TODO: some form of error notification to the user
|
||||
// to inform them that their state change failed.
|
||||
// For now we at least set the state back
|
||||
if (this._unmounted) return;
|
||||
this.setState({
|
||||
roomNotifState: oldState,
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
_onClickAlertMe: function() {
|
||||
this._saveNotifState(RoomNotifs.ALL_MESSAGES_LOUD);
|
||||
},
|
||||
|
||||
_onClickAllNotifs: function() {
|
||||
this._saveNotifState(RoomNotifs.ALL_MESSAGES);
|
||||
},
|
||||
|
||||
_onClickMentions: function() {
|
||||
this._saveNotifState(RoomNotifs.MENTIONS_ONLY);
|
||||
},
|
||||
|
||||
_onClickMute: function() {
|
||||
this._saveNotifState(RoomNotifs.MUTE);
|
||||
},
|
||||
|
||||
_renderNotifMenu: function() {
|
||||
return (
|
||||
<div className="mx_RoomTileContextMenu" role="group" aria-label={_t("Notification settings")}>
|
||||
<div className="mx_RoomTileContextMenu_notif_picker" role="presentation">
|
||||
<img src={require("../../../../res/img/notif-slider.svg")} width="20" height="107" alt="" />
|
||||
</div>
|
||||
|
||||
<NotifOption
|
||||
active={this.state.roomNotifState === RoomNotifs.ALL_MESSAGES_LOUD}
|
||||
label={_t('All messages (noisy)')}
|
||||
onClick={this._onClickAlertMe}
|
||||
src={require("../../../../res/img/icon-context-mute-off-copy.svg")}
|
||||
/>
|
||||
<NotifOption
|
||||
active={this.state.roomNotifState === RoomNotifs.ALL_MESSAGES}
|
||||
label={_t('All messages')}
|
||||
onClick={this._onClickAllNotifs}
|
||||
src={require("../../../../res/img/icon-context-mute-off.svg")}
|
||||
/>
|
||||
<NotifOption
|
||||
active={this.state.roomNotifState === RoomNotifs.MENTIONS_ONLY}
|
||||
label={_t('Mentions only')}
|
||||
onClick={this._onClickMentions}
|
||||
src={require("../../../../res/img/icon-context-mute-mentions.svg")}
|
||||
/>
|
||||
<NotifOption
|
||||
active={this.state.roomNotifState === RoomNotifs.MUTE}
|
||||
label={_t('Mute')}
|
||||
onClick={this._onClickMute}
|
||||
src={require("../../../../res/img/icon-context-mute.svg")}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
||||
_onClickSettings: function() {
|
||||
dis.dispatch({
|
||||
action: 'open_room_settings',
|
||||
room_id: this.props.room.roomId,
|
||||
});
|
||||
if (this.props.onFinished) {
|
||||
this.props.onFinished();
|
||||
}
|
||||
},
|
||||
|
||||
_renderSettingsMenu: function() {
|
||||
return (
|
||||
<div>
|
||||
<MenuItem className="mx_RoomTileContextMenu_tag_field" onClick={this._onClickSettings}>
|
||||
<img className="mx_RoomTileContextMenu_tag_icon" src={require("../../../../res/img/feather-customised/settings.svg")} width="15" height="15" alt="" />
|
||||
{ _t('Settings') }
|
||||
</MenuItem>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
||||
_renderLeaveMenu: function(membership) {
|
||||
if (!membership) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let leaveClickHandler = null;
|
||||
let leaveText = null;
|
||||
|
||||
switch (membership) {
|
||||
case "join":
|
||||
leaveClickHandler = this._onClickLeave;
|
||||
leaveText = _t('Leave');
|
||||
break;
|
||||
case "leave":
|
||||
case "ban":
|
||||
leaveClickHandler = this._onClickForget;
|
||||
leaveText = _t('Forget');
|
||||
break;
|
||||
case "invite":
|
||||
leaveClickHandler = this._onClickReject;
|
||||
leaveText = _t('Reject');
|
||||
break;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<MenuItem className="mx_RoomTileContextMenu_leave" onClick={leaveClickHandler}>
|
||||
<img className="mx_RoomTileContextMenu_tag_icon" src={require("../../../../res/img/icon_context_delete.svg")} width="15" height="15" alt="" />
|
||||
{ leaveText }
|
||||
</MenuItem>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
||||
_renderRoomTagMenu: function() {
|
||||
return (
|
||||
<div>
|
||||
<RoomTagOption
|
||||
active={this.state.isFavourite}
|
||||
label={_t('Favourite')}
|
||||
onClick={this._onClickFavourite}
|
||||
src={require("../../../../res/img/icon_context_fave.svg")}
|
||||
srcSet={require("../../../../res/img/icon_context_fave_on.svg")}
|
||||
/>
|
||||
<RoomTagOption
|
||||
active={this.state.isLowPriority}
|
||||
label={_t('Low Priority')}
|
||||
onClick={this._onClickLowPriority}
|
||||
src={require("../../../../res/img/icon_context_low.svg")}
|
||||
srcSet={require("../../../../res/img/icon_context_low_on.svg")}
|
||||
/>
|
||||
<RoomTagOption
|
||||
active={this.state.isDirectMessage}
|
||||
label={_t('Direct Chat')}
|
||||
onClick={this._onClickDM}
|
||||
src={require("../../../../res/img/icon_context_person.svg")}
|
||||
srcSet={require("../../../../res/img/icon_context_person_on.svg")}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
||||
render: function() {
|
||||
const myMembership = this.props.room.getMyMembership();
|
||||
|
||||
switch (myMembership) {
|
||||
case 'join':
|
||||
return <div>
|
||||
{ this._renderNotifMenu() }
|
||||
<hr className="mx_RoomTileContextMenu_separator" role="separator" />
|
||||
{ this._renderLeaveMenu(myMembership) }
|
||||
<hr className="mx_RoomTileContextMenu_separator" role="separator" />
|
||||
{ this._renderRoomTagMenu() }
|
||||
<hr className="mx_RoomTileContextMenu_separator" role="separator" />
|
||||
{ this._renderSettingsMenu() }
|
||||
</div>;
|
||||
case 'invite':
|
||||
return <div>
|
||||
{ this._renderLeaveMenu(myMembership) }
|
||||
</div>;
|
||||
default:
|
||||
return <div>
|
||||
{ this._renderLeaveMenu(myMembership) }
|
||||
<hr className="mx_RoomTileContextMenu_separator" role="separator" />
|
||||
{ this._renderSettingsMenu() }
|
||||
</div>;
|
||||
}
|
||||
},
|
||||
});
|
|
@ -1,155 +0,0 @@
|
|||
/*
|
||||
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 from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import dis from '../../../dispatcher/dispatcher';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import LogoutDialog from "../dialogs/LogoutDialog";
|
||||
import Modal from "../../../Modal";
|
||||
import SdkConfig from '../../../SdkConfig';
|
||||
import { getHostingLink } from '../../../utils/HostingLink';
|
||||
import {MatrixClientPeg} from '../../../MatrixClientPeg';
|
||||
import {MenuItem} from "../../structures/ContextMenu";
|
||||
import * as sdk from "../../../index";
|
||||
import {getHomePageUrl} from "../../../utils/pages";
|
||||
import {Action} from "../../../dispatcher/actions";
|
||||
|
||||
export default class TopLeftMenu extends React.Component {
|
||||
static propTypes = {
|
||||
displayName: PropTypes.string.isRequired,
|
||||
userId: PropTypes.string.isRequired,
|
||||
onFinished: PropTypes.func,
|
||||
|
||||
// Optional function to collect a reference to the container
|
||||
// of this component directly.
|
||||
containerRef: PropTypes.func,
|
||||
};
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.viewHomePage = this.viewHomePage.bind(this);
|
||||
this.openSettings = this.openSettings.bind(this);
|
||||
this.signIn = this.signIn.bind(this);
|
||||
this.signOut = this.signOut.bind(this);
|
||||
}
|
||||
|
||||
hasHomePage() {
|
||||
return !!getHomePageUrl(SdkConfig.get());
|
||||
}
|
||||
|
||||
render() {
|
||||
const isGuest = MatrixClientPeg.get().isGuest();
|
||||
|
||||
const hostingSignupLink = getHostingLink('user-context-menu');
|
||||
let hostingSignup = null;
|
||||
if (hostingSignupLink) {
|
||||
hostingSignup = <div className="mx_TopLeftMenu_upgradeLink">
|
||||
{_t(
|
||||
"<a>Upgrade</a> to your own domain", {},
|
||||
{
|
||||
a: sub =>
|
||||
<a href={hostingSignupLink} target="_blank" rel="noreferrer noopener" tabIndex={-1}>{sub}</a>,
|
||||
},
|
||||
)}
|
||||
<a href={hostingSignupLink} target="_blank" rel="noreferrer noopener" role="presentation" aria-hidden={true} tabIndex={-1}>
|
||||
<img src={require("../../../../res/img/external-link.svg")} width="11" height="10" alt='' />
|
||||
</a>
|
||||
</div>;
|
||||
}
|
||||
|
||||
let homePageItem = null;
|
||||
if (this.hasHomePage()) {
|
||||
homePageItem = (
|
||||
<MenuItem className="mx_TopLeftMenu_icon_home" onClick={this.viewHomePage}>
|
||||
{_t("Home")}
|
||||
</MenuItem>
|
||||
);
|
||||
}
|
||||
|
||||
let signInOutItem;
|
||||
if (isGuest) {
|
||||
signInOutItem = (
|
||||
<MenuItem className="mx_TopLeftMenu_icon_signin" onClick={this.signIn}>
|
||||
{_t("Sign in")}
|
||||
</MenuItem>
|
||||
);
|
||||
} else {
|
||||
signInOutItem = (
|
||||
<MenuItem className="mx_TopLeftMenu_icon_signout" onClick={this.signOut}>
|
||||
{_t("Sign out")}
|
||||
</MenuItem>
|
||||
);
|
||||
}
|
||||
|
||||
const helpItem = (
|
||||
<MenuItem className="mx_TopLeftMenu_icon_help" onClick={this.openHelp}>
|
||||
{_t("Help")}
|
||||
</MenuItem>
|
||||
);
|
||||
|
||||
const settingsItem = (
|
||||
<MenuItem className="mx_TopLeftMenu_icon_settings" onClick={this.openSettings}>
|
||||
{_t("Settings")}
|
||||
</MenuItem>
|
||||
);
|
||||
|
||||
return <div className="mx_TopLeftMenu" ref={this.props.containerRef} role="menu">
|
||||
<div className="mx_TopLeftMenu_section_noIcon" aria-readonly={true} tabIndex={-1}>
|
||||
<div>{this.props.displayName}</div>
|
||||
<div className="mx_TopLeftMenu_greyedText" aria-hidden={true}>{this.props.userId}</div>
|
||||
{hostingSignup}
|
||||
</div>
|
||||
<ul className="mx_TopLeftMenu_section_withIcon" role="none">
|
||||
{homePageItem}
|
||||
{settingsItem}
|
||||
{helpItem}
|
||||
{signInOutItem}
|
||||
</ul>
|
||||
</div>;
|
||||
}
|
||||
|
||||
openHelp = () => {
|
||||
this.closeMenu();
|
||||
const RedesignFeedbackDialog = sdk.getComponent("views.dialogs.RedesignFeedbackDialog");
|
||||
Modal.createTrackedDialog('Report bugs & give feedback', '', RedesignFeedbackDialog);
|
||||
};
|
||||
|
||||
viewHomePage() {
|
||||
dis.dispatch({action: 'view_home_page'});
|
||||
this.closeMenu();
|
||||
}
|
||||
|
||||
openSettings() {
|
||||
dis.fire(Action.ViewUserSettings);
|
||||
this.closeMenu();
|
||||
}
|
||||
|
||||
signIn() {
|
||||
dis.dispatch({action: 'start_login'});
|
||||
this.closeMenu();
|
||||
}
|
||||
|
||||
signOut() {
|
||||
Modal.createTrackedDialog('Logout E2E Export', '', LogoutDialog);
|
||||
this.closeMenu();
|
||||
}
|
||||
|
||||
closeMenu() {
|
||||
if (this.props.onFinished) this.props.onFinished();
|
||||
}
|
||||
}
|
|
@ -23,7 +23,8 @@ import * as sdk from '../../../index';
|
|||
import SdkConfig from '../../../SdkConfig';
|
||||
import Modal from '../../../Modal';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import sendBugReport from '../../../rageshake/submit-rageshake';
|
||||
import sendBugReport, {downloadBugReport} from '../../../rageshake/submit-rageshake';
|
||||
import AccessibleButton from "../elements/AccessibleButton";
|
||||
|
||||
export default class BugReportDialog extends React.Component {
|
||||
constructor(props) {
|
||||
|
@ -35,6 +36,8 @@ export default class BugReportDialog extends React.Component {
|
|||
issueUrl: "",
|
||||
text: "",
|
||||
progress: null,
|
||||
downloadBusy: false,
|
||||
downloadProgress: null,
|
||||
};
|
||||
this._unmounted = false;
|
||||
this._onSubmit = this._onSubmit.bind(this);
|
||||
|
@ -43,6 +46,7 @@ export default class BugReportDialog extends React.Component {
|
|||
this._onIssueUrlChange = this._onIssueUrlChange.bind(this);
|
||||
this._onSendLogsChange = this._onSendLogsChange.bind(this);
|
||||
this._sendProgressCallback = this._sendProgressCallback.bind(this);
|
||||
this._downloadProgressCallback = this._downloadProgressCallback.bind(this);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
|
@ -95,6 +99,31 @@ export default class BugReportDialog extends React.Component {
|
|||
});
|
||||
}
|
||||
|
||||
_onDownload = async (ev) => {
|
||||
this.setState({ downloadBusy: true });
|
||||
this._downloadProgressCallback(_t("Preparing to download logs"));
|
||||
|
||||
try {
|
||||
await downloadBugReport({
|
||||
sendLogs: true,
|
||||
progressCallback: this._downloadProgressCallback,
|
||||
label: this.props.label,
|
||||
});
|
||||
|
||||
this.setState({
|
||||
downloadBusy: false,
|
||||
downloadProgress: null,
|
||||
});
|
||||
} catch (err) {
|
||||
if (!this._unmounted) {
|
||||
this.setState({
|
||||
downloadBusy: false,
|
||||
downloadProgress: _t("Failed to send logs: ") + `${err.message}`,
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
_onTextChange(ev) {
|
||||
this.setState({ text: ev.target.value });
|
||||
}
|
||||
|
@ -114,6 +143,13 @@ export default class BugReportDialog extends React.Component {
|
|||
this.setState({progress: progress});
|
||||
}
|
||||
|
||||
_downloadProgressCallback(downloadProgress) {
|
||||
if (this._unmounted) {
|
||||
return;
|
||||
}
|
||||
this.setState({ downloadProgress });
|
||||
}
|
||||
|
||||
render() {
|
||||
const Loader = sdk.getComponent("elements.Spinner");
|
||||
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
||||
|
@ -173,6 +209,14 @@ export default class BugReportDialog extends React.Component {
|
|||
},
|
||||
) }
|
||||
</b></p>
|
||||
|
||||
<div className="mx_BugReportDialog_download">
|
||||
<AccessibleButton onClick={this._onDownload} kind="link" disabled={this.state.downloadBusy}>
|
||||
{ _t("Download logs") }
|
||||
</AccessibleButton>
|
||||
{this.state.downloadProgress && <span>{this.state.downloadProgress} ...</span>}
|
||||
</div>
|
||||
|
||||
<Field
|
||||
type="text"
|
||||
className="mx_BugReportDialog_field_input"
|
||||
|
|
|
@ -1,116 +0,0 @@
|
|||
/*
|
||||
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import * as PropTypes from 'prop-types';
|
||||
import BaseDialog from './BaseDialog';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import DialogButtons from '../elements/DialogButtons';
|
||||
|
||||
export enum RebrandDialogKind {
|
||||
NAG,
|
||||
ONE_TIME,
|
||||
}
|
||||
|
||||
interface IProps {
|
||||
onFinished: (bool) => void;
|
||||
kind: RebrandDialogKind;
|
||||
targetUrl?: string;
|
||||
}
|
||||
|
||||
export default class RebrandDialog extends React.PureComponent<IProps> {
|
||||
private onDoneClick = () => {
|
||||
this.props.onFinished(true);
|
||||
};
|
||||
|
||||
private onGoToElementClick = () => {
|
||||
this.props.onFinished(true);
|
||||
};
|
||||
|
||||
private onRemindMeLaterClick = () => {
|
||||
this.props.onFinished(false);
|
||||
};
|
||||
|
||||
private getPrettyTargetUrl() {
|
||||
const u = new URL(this.props.targetUrl);
|
||||
let ret = u.host;
|
||||
if (u.pathname !== '/') ret += u.pathname;
|
||||
return ret;
|
||||
}
|
||||
|
||||
getBodyText() {
|
||||
if (this.props.kind === RebrandDialogKind.NAG) {
|
||||
return _t(
|
||||
"Use your account to sign in to the latest version of the app at <a />", {},
|
||||
{
|
||||
a: sub => <a href={this.props.targetUrl} rel="noopener noreferrer" target="_blank">{this.getPrettyTargetUrl()}</a>,
|
||||
},
|
||||
);
|
||||
} else {
|
||||
return _t(
|
||||
"You’re already signed in and good to go here, but you can also grab the latest " +
|
||||
"versions of the app on all platforms at <a>element.io/get-started</a>.", {},
|
||||
{
|
||||
a: sub => <a href="https://element.io/get-started" rel="noopener noreferrer" target="_blank">{sub}</a>,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
getDialogButtons() {
|
||||
if (this.props.kind === RebrandDialogKind.NAG) {
|
||||
return <DialogButtons primaryButton={_t("Go to Element")}
|
||||
primaryButtonClass='primary'
|
||||
onPrimaryButtonClick={this.onGoToElementClick}
|
||||
hasCancel={true}
|
||||
cancelButton={"Remind me later"}
|
||||
onCancel={this.onRemindMeLaterClick}
|
||||
focus={true}
|
||||
/>;
|
||||
} else {
|
||||
return <DialogButtons primaryButton={_t("Done")}
|
||||
primaryButtonClass='primary'
|
||||
hasCancel={false}
|
||||
onPrimaryButtonClick={this.onDoneClick}
|
||||
focus={true}
|
||||
/>;
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return <BaseDialog title={_t("We’re excited to announce Riot is now Element!")}
|
||||
className='mx_RebrandDialog'
|
||||
contentId='mx_Dialog_content'
|
||||
onFinished={this.props.onFinished}
|
||||
hasCancel={false}
|
||||
>
|
||||
<div className="mx_RebrandDialog_body">{this.getBodyText()}</div>
|
||||
<div className="mx_RebrandDialog_logoContainer">
|
||||
<img className="mx_RebrandDialog_logo" src={require("../../../../res/img/riot-logo.svg")} alt="Riot Logo" />
|
||||
<span className="mx_RebrandDialog_chevron" />
|
||||
<img className="mx_RebrandDialog_logo" src={require("../../../../res/img/element-logo.svg")} alt="Element Logo" />
|
||||
</div>
|
||||
<div>
|
||||
{_t(
|
||||
"Learn more at <a>element.io/previously-riot</a>", {}, {
|
||||
a: sub => <a href="https://element.io/previously-riot" rel="noopener noreferrer" target="_blank">{sub}</a>,
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
{this.getDialogButtons()}
|
||||
</BaseDialog>;
|
||||
}
|
||||
}
|
|
@ -87,7 +87,7 @@ export default class RoomSettingsDialog extends React.Component {
|
|||
<NotificationSettingsTab roomId={this.props.roomId} />,
|
||||
));
|
||||
|
||||
if (SettingsStore.isFeatureEnabled("feature_bridge_state")) {
|
||||
if (SettingsStore.getValue("feature_bridge_state")) {
|
||||
tabs.push(new Tab(
|
||||
ROOM_BRIDGES_TAB,
|
||||
_td("Bridges"),
|
||||
|
|
|
@ -30,6 +30,7 @@ import * as ContextMenu from "../../structures/ContextMenu";
|
|||
import {toRightOf} from "../../structures/ContextMenu";
|
||||
import {copyPlaintext, selectText} from "../../../utils/strings";
|
||||
import StyledCheckbox from '../elements/StyledCheckbox';
|
||||
import AccessibleTooltipButton from '../elements/AccessibleTooltipButton';
|
||||
|
||||
const socials = [
|
||||
{
|
||||
|
@ -210,10 +211,11 @@ export default class ShareDialog extends React.PureComponent<IProps, IState> {
|
|||
>
|
||||
{ matrixToUrl }
|
||||
</a>
|
||||
<a href={matrixToUrl} className="mx_ShareDialog_matrixto_copy" onClick={this.onCopyClick}>
|
||||
{ _t('COPY') }
|
||||
<div> </div>
|
||||
</a>
|
||||
<AccessibleTooltipButton
|
||||
title={_t("Copy")}
|
||||
onClick={this.onCopyClick}
|
||||
className="mx_ShareDialog_matrixto_copy"
|
||||
/>
|
||||
</div>
|
||||
{ checkbox }
|
||||
<hr />
|
||||
|
|
|
@ -54,7 +54,7 @@ export default class UserSettingsDialog extends React.Component {
|
|||
super();
|
||||
|
||||
this.state = {
|
||||
mjolnirEnabled: SettingsStore.isFeatureEnabled("feature_mjolnir"),
|
||||
mjolnirEnabled: SettingsStore.getValue("feature_mjolnir"),
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -116,7 +116,7 @@ export default class UserSettingsDialog extends React.Component {
|
|||
"mx_UserSettingsDialog_securityIcon",
|
||||
<SecurityUserSettingsTab closeSettingsFn={this.props.onFinished} />,
|
||||
));
|
||||
if (SdkConfig.get()['showLabsSettings'] || SettingsStore.getLabsFeatures().length > 0) {
|
||||
if (SdkConfig.get()['showLabsSettings']) {
|
||||
tabs.push(new Tab(
|
||||
USER_LABS_TAB,
|
||||
_td("Labs"),
|
||||
|
|
|
@ -25,6 +25,7 @@ interface ITooltipProps extends React.ComponentProps<typeof AccessibleButton> {
|
|||
title: string;
|
||||
tooltip?: React.ReactNode;
|
||||
tooltipClassName?: string;
|
||||
forceHide?: boolean;
|
||||
}
|
||||
|
||||
interface IState {
|
||||
|
@ -39,7 +40,16 @@ export default class AccessibleTooltipButton extends React.PureComponent<IToolti
|
|||
};
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps: Readonly<ITooltipProps>) {
|
||||
if (!prevProps.forceHide && this.props.forceHide && this.state.hover) {
|
||||
this.setState({
|
||||
hover: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onMouseOver = () => {
|
||||
if (this.props.forceHide) return;
|
||||
this.setState({
|
||||
hover: true,
|
||||
});
|
||||
|
|
|
@ -311,16 +311,16 @@ export default class AppTile extends React.Component {
|
|||
this.props.onEditClick();
|
||||
} else {
|
||||
// TODO: Open the right manager for the widget
|
||||
if (SettingsStore.isFeatureEnabled("feature_many_integration_managers")) {
|
||||
if (SettingsStore.getValue("feature_many_integration_managers")) {
|
||||
IntegrationManagers.sharedInstance().openAll(
|
||||
this.props.room,
|
||||
'type_' + this.props.type,
|
||||
'type_' + this.props.app.type,
|
||||
this.props.app.id,
|
||||
);
|
||||
} else {
|
||||
IntegrationManagers.sharedInstance().getPrimaryManager().open(
|
||||
this.props.room,
|
||||
'type_' + this.props.type,
|
||||
'type_' + this.props.app.type,
|
||||
this.props.app.id,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -28,7 +28,7 @@ export default createReactClass({
|
|||
const imgClass = this.props.imgClassName || "";
|
||||
|
||||
let imageSource;
|
||||
if (SettingsStore.isFeatureEnabled('feature_new_spinner')) {
|
||||
if (SettingsStore.getValue('feature_new_spinner')) {
|
||||
imageSource = require("../../../../res/img/spinner.svg");
|
||||
} else {
|
||||
imageSource = require("../../../../res/img/spinner.gif");
|
||||
|
|
|
@ -34,7 +34,7 @@ export default class ManageIntegsButton extends React.Component {
|
|||
if (!managers.hasManager()) {
|
||||
managers.openNoManagerDialog();
|
||||
} else {
|
||||
if (SettingsStore.isFeatureEnabled("feature_many_integration_managers")) {
|
||||
if (SettingsStore.getValue("feature_many_integration_managers")) {
|
||||
managers.openAll(this.props.room);
|
||||
} else {
|
||||
managers.getPrimaryManager().open(this.props.room);
|
||||
|
|
|
@ -22,7 +22,7 @@ import SettingsStore from "../../../settings/SettingsStore";
|
|||
|
||||
const Spinner = ({w = 32, h = 32, imgClassName, message}) => {
|
||||
let imageSource;
|
||||
if (SettingsStore.isFeatureEnabled('feature_new_spinner')) {
|
||||
if (SettingsStore.getValue('feature_new_spinner')) {
|
||||
imageSource = require("../../../../res/img/spinner.svg");
|
||||
} else {
|
||||
imageSource = require("../../../../res/img/spinner.gif");
|
||||
|
|
|
@ -25,6 +25,7 @@ interface IDefinition<T extends string> {
|
|||
disabled?: boolean;
|
||||
label: React.ReactChild;
|
||||
description?: React.ReactChild;
|
||||
checked?: boolean; // If provided it will override the value comparison done in the group
|
||||
}
|
||||
|
||||
interface IProps<T extends string> {
|
||||
|
@ -33,7 +34,7 @@ interface IProps<T extends string> {
|
|||
definitions: IDefinition<T>[];
|
||||
value?: T; // if not provided no options will be selected
|
||||
outlined?: boolean;
|
||||
onChange(newValue: T);
|
||||
onChange(newValue: T): void;
|
||||
}
|
||||
|
||||
function StyledRadioGroup<T extends string>({name, definitions, value, className, outlined, onChange}: IProps<T>) {
|
||||
|
@ -46,7 +47,7 @@ function StyledRadioGroup<T extends string>({name, definitions, value, className
|
|||
<StyledRadioButton
|
||||
className={classNames(className, d.className)}
|
||||
onChange={_onChange}
|
||||
checked={d.value === value}
|
||||
checked={d.checked !== undefined ? d.checked : d.value === value}
|
||||
name={name}
|
||||
value={d.value}
|
||||
disabled={d.disabled}
|
||||
|
|
|
@ -18,6 +18,7 @@ limitations under the License.
|
|||
|
||||
import React, {useEffect} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { EventStatus } from 'matrix-js-sdk';
|
||||
|
||||
import { _t } from '../../../languageHandler';
|
||||
import * as sdk from '../../../index';
|
||||
|
@ -114,13 +115,19 @@ export default class MessageActionBar extends React.PureComponent {
|
|||
static contextType = RoomContext;
|
||||
|
||||
componentDidMount() {
|
||||
this.props.mxEvent.on("Event.decrypted", this.onDecrypted);
|
||||
if (this.props.mxEvent.status && this.props.mxEvent.status !== EventStatus.SENT) {
|
||||
this.props.mxEvent.on("Event.status", this.onSent);
|
||||
}
|
||||
if (this.props.mxEvent.isBeingDecrypted()) {
|
||||
this.props.mxEvent.once("Event.decrypted", this.onDecrypted);
|
||||
}
|
||||
this.props.mxEvent.on("Event.beforeRedaction", this.onBeforeRedaction);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.props.mxEvent.removeListener("Event.decrypted", this.onDecrypted);
|
||||
this.props.mxEvent.removeListener("Event.beforeRedaction", this.onBeforeRedaction);
|
||||
this.props.mxEvent.off("Event.status", this.onSent);
|
||||
this.props.mxEvent.off("Event.decrypted", this.onDecrypted);
|
||||
this.props.mxEvent.off("Event.beforeRedaction", this.onBeforeRedaction);
|
||||
}
|
||||
|
||||
onDecrypted = () => {
|
||||
|
@ -134,6 +141,11 @@ export default class MessageActionBar extends React.PureComponent {
|
|||
this.forceUpdate();
|
||||
};
|
||||
|
||||
onSent = () => {
|
||||
// When an event is sent and echoed the possible actions change.
|
||||
this.forceUpdate();
|
||||
};
|
||||
|
||||
onFocusChange = (focused) => {
|
||||
if (!this.props.onFocusChange) {
|
||||
return;
|
||||
|
|
|
@ -95,7 +95,7 @@ export default createReactClass({
|
|||
}
|
||||
}
|
||||
|
||||
if (SettingsStore.isFeatureEnabled("feature_mjolnir")) {
|
||||
if (SettingsStore.getValue("feature_mjolnir")) {
|
||||
const key = `mx_mjolnir_render_${this.props.mxEvent.getRoomId()}__${this.props.mxEvent.getId()}`;
|
||||
const allowRender = localStorage.getItem(key) === "true";
|
||||
|
||||
|
|
|
@ -172,6 +172,8 @@ export default createReactClass({
|
|||
const hidden = global.localStorage.getItem("hide_preview_" + this.props.mxEvent.getId());
|
||||
this.setState({ widgetHidden: hidden });
|
||||
}
|
||||
} else if (this.state.links.length) {
|
||||
this.setState({ links: [] });
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -39,6 +39,8 @@ interface IProps {
|
|||
title: string;
|
||||
}
|
||||
|
||||
// TODO: replace this, the composer buttons and the right panel buttons with a unified
|
||||
// representation
|
||||
export default class HeaderButton extends React.Component<IProps> {
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
|
|
@ -1428,7 +1428,7 @@ const UserInfoHeader = ({onClose, member, e2eStatus}) => {
|
|||
presenceLastActiveAgo = member.user.lastActiveAgo;
|
||||
presenceCurrentlyActive = member.user.currentlyActive;
|
||||
|
||||
if (SettingsStore.isFeatureEnabled("feature_custom_status")) {
|
||||
if (SettingsStore.getValue("feature_custom_status")) {
|
||||
statusMessage = member.user._unstable_statusMessage;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,154 +0,0 @@
|
|||
/*
|
||||
Copyright 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.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import createReactClass from 'create-react-class';
|
||||
|
||||
import Tinter from '../../../Tinter';
|
||||
import dis from '../../../dispatcher/dispatcher';
|
||||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
import {SettingLevel} from "../../../settings/SettingLevel";
|
||||
|
||||
const ROOM_COLORS = [
|
||||
// magic room default values courtesy of Ribot
|
||||
[Tinter.getKeyRgb()[0], Tinter.getKeyRgb()[1]],
|
||||
["#81bddb", "#eaf1f4"],
|
||||
["#bd79cb", "#f3eaf5"],
|
||||
["#c65d94", "#f5eaef"],
|
||||
["#e55e5e", "#f5eaea"],
|
||||
["#eca46f", "#f5eeea"],
|
||||
["#dad658", "#f5f4ea"],
|
||||
["#80c553", "#eef5ea"],
|
||||
["#bb814e", "#eee8e3"],
|
||||
//["#595959", "#ececec"], // Grey makes everything appear disabled, so remove it for now
|
||||
];
|
||||
|
||||
// Dev note: this component is not attached anywhere, but is left here as it
|
||||
// has a high possibility of being used in the nearish future.
|
||||
// Ref: https://github.com/vector-im/element-web/issues/8421
|
||||
|
||||
export default createReactClass({
|
||||
displayName: 'ColorSettings',
|
||||
|
||||
propTypes: {
|
||||
room: PropTypes.object.isRequired,
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
const data = {
|
||||
index: 0,
|
||||
primary_color: ROOM_COLORS[0][0],
|
||||
secondary_color: ROOM_COLORS[0][1],
|
||||
hasChanged: false,
|
||||
};
|
||||
const scheme = SettingsStore.getValueAt(SettingLevel.ROOM_ACCOUNT, "roomColor", this.props.room.roomId);
|
||||
|
||||
if (scheme.primary_color && scheme.secondary_color) {
|
||||
// We only use the user's scheme if the scheme is valid.
|
||||
data.primary_color = scheme.primary_color;
|
||||
data.secondary_color = scheme.secondary_color;
|
||||
}
|
||||
data.index = this._getColorIndex(data);
|
||||
|
||||
if (data.index === -1) {
|
||||
// append the unrecognised colours to our palette
|
||||
data.index = ROOM_COLORS.length;
|
||||
ROOM_COLORS.push([
|
||||
scheme.primary_color, scheme.secondary_color,
|
||||
]);
|
||||
}
|
||||
return data;
|
||||
},
|
||||
|
||||
saveSettings: function() { // : Promise
|
||||
if (!this.state.hasChanged) {
|
||||
return Promise.resolve(); // They didn't explicitly give a color to save.
|
||||
}
|
||||
const originalState = this.getInitialState();
|
||||
if (originalState.primary_color !== this.state.primary_color ||
|
||||
originalState.secondary_color !== this.state.secondary_color) {
|
||||
console.log("ColorSettings: Saving new color");
|
||||
// We would like guests to be able to set room colour but currently
|
||||
// they can't, so we still send the request but display a sensible
|
||||
// error if it fails.
|
||||
// TODO: Support guests for room color. Technically this is possible via granular settings
|
||||
// Granular settings would mean the guest is forced to use the DEVICE level though.
|
||||
SettingsStore.setValue("roomColor", this.props.room.roomId, SettingLevel.ROOM_ACCOUNT, {
|
||||
primary_color: this.state.primary_color,
|
||||
secondary_color: this.state.secondary_color,
|
||||
}).catch(function(err) {
|
||||
if (err.errcode === 'M_GUEST_ACCESS_FORBIDDEN') {
|
||||
dis.dispatch({action: 'require_registration'});
|
||||
}
|
||||
});
|
||||
}
|
||||
return Promise.resolve(); // no color diff
|
||||
},
|
||||
|
||||
_getColorIndex: function(scheme) {
|
||||
if (!scheme || !scheme.primary_color || !scheme.secondary_color) {
|
||||
return -1;
|
||||
}
|
||||
// XXX: we should validate these values
|
||||
for (let i = 0; i < ROOM_COLORS.length; i++) {
|
||||
const room_color = ROOM_COLORS[i];
|
||||
if (room_color[0] === String(scheme.primary_color).toLowerCase() &&
|
||||
room_color[1] === String(scheme.secondary_color).toLowerCase()) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
},
|
||||
|
||||
_onColorSchemeChanged: function(index) {
|
||||
// preview what the user just changed the scheme to.
|
||||
Tinter.tint(ROOM_COLORS[index][0], ROOM_COLORS[index][1]);
|
||||
this.setState({
|
||||
index: index,
|
||||
primary_color: ROOM_COLORS[index][0],
|
||||
secondary_color: ROOM_COLORS[index][1],
|
||||
hasChanged: true,
|
||||
});
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
<div className="mx_ColorSettings_roomColors">
|
||||
{ ROOM_COLORS.map((room_color, i) => {
|
||||
let selected;
|
||||
if (i === this.state.index) {
|
||||
selected = (
|
||||
<div className="mx_ColorSettings_roomColor_selected">
|
||||
<img src={require("../../../../res/img/tick.svg")} width="17" height="14" alt="./" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
const boundClick = this._onColorSchemeChanged.bind(this, i);
|
||||
return (
|
||||
<div className="mx_ColorSettings_roomColor"
|
||||
key={"room_color_" + i}
|
||||
style={{ backgroundColor: room_color[1] }}
|
||||
onClick={boundClick}>
|
||||
{ selected }
|
||||
<div className="mx_ColorSettings_roomColorPrimary" style={{ backgroundColor: room_color[0] }}></div>
|
||||
</div>
|
||||
);
|
||||
}) }
|
||||
</div>
|
||||
);
|
||||
},
|
||||
});
|
|
@ -130,7 +130,7 @@ export default createReactClass({
|
|||
},
|
||||
|
||||
_launchManageIntegrations: function() {
|
||||
if (SettingsStore.isFeatureEnabled("feature_many_integration_managers")) {
|
||||
if (SettingsStore.getValue("feature_many_integration_managers")) {
|
||||
IntegrationManagers.sharedInstance().openAll();
|
||||
} else {
|
||||
IntegrationManagers.sharedInstance().getPrimaryManager().open(this.props.room, 'add_integ');
|
||||
|
|
|
@ -104,7 +104,7 @@ export default createReactClass({
|
|||
},
|
||||
|
||||
_rateLimitedUpdate: new RateLimitedFunc(function() {
|
||||
if (SettingsStore.isFeatureEnabled("feature_state_counters")) {
|
||||
if (SettingsStore.getValue("feature_state_counters")) {
|
||||
this.setState({counters: this._computeCounters()});
|
||||
}
|
||||
}, 500),
|
||||
|
@ -112,7 +112,7 @@ export default createReactClass({
|
|||
_computeCounters: function() {
|
||||
let counters = [];
|
||||
|
||||
if (this.props.room && SettingsStore.isFeatureEnabled("feature_state_counters")) {
|
||||
if (this.props.room && SettingsStore.getValue("feature_state_counters")) {
|
||||
const stateEvs = this.props.room.currentState.getStateEvents('re.jki.counter');
|
||||
stateEvs.sort((a, b) => {
|
||||
return a.getStateKey() < b.getStateKey();
|
||||
|
@ -206,7 +206,7 @@ export default createReactClass({
|
|||
/>;
|
||||
|
||||
let stateViews = null;
|
||||
if (this.state.counters && SettingsStore.isFeatureEnabled("feature_state_counters")) {
|
||||
if (this.state.counters && SettingsStore.getValue("feature_state_counters")) {
|
||||
let counters = [];
|
||||
|
||||
this.state.counters.forEach((counter, idx) => {
|
||||
|
|
|
@ -93,7 +93,7 @@ interface IProps {
|
|||
initialCaret?: DocumentOffset;
|
||||
|
||||
onChange();
|
||||
onPaste(event: ClipboardEvent<HTMLDivElement>, model: EditorModel): boolean;
|
||||
onPaste?(event: ClipboardEvent<HTMLDivElement>, model: EditorModel): boolean;
|
||||
}
|
||||
|
||||
interface IState {
|
||||
|
@ -554,10 +554,12 @@ export default class BasicMessageEditor extends React.Component<IProps, IState>
|
|||
}
|
||||
|
||||
private onAutoCompleteConfirm = (completion: ICompletion) => {
|
||||
this.modifiedFlag = true;
|
||||
this.props.model.autoComplete.onComponentConfirm(completion);
|
||||
};
|
||||
|
||||
private onAutoCompleteSelectionChange = (completion: ICompletion, completionIndex: number) => {
|
||||
this.modifiedFlag = true;
|
||||
this.props.model.autoComplete.onComponentSelectionChange(completion);
|
||||
this.setState({completionIndex});
|
||||
};
|
||||
|
|
|
@ -50,7 +50,7 @@ export default createReactClass({
|
|||
componentDidMount() {
|
||||
const cli = MatrixClientPeg.get();
|
||||
|
||||
if (SettingsStore.isFeatureEnabled("feature_custom_status")) {
|
||||
if (SettingsStore.getValue("feature_custom_status")) {
|
||||
const { user } = this.props.member;
|
||||
if (user) {
|
||||
user.on("User._unstable_statusMessage", this._onStatusMessageCommitted);
|
||||
|
@ -209,7 +209,7 @@ export default createReactClass({
|
|||
const presenceState = member.user ? member.user.presence : null;
|
||||
|
||||
let statusMessage = null;
|
||||
if (member.user && SettingsStore.isFeatureEnabled("feature_custom_status")) {
|
||||
if (member.user && SettingsStore.getValue("feature_custom_status")) {
|
||||
statusMessage = this.state.statusMessage;
|
||||
}
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
import React, {createRef} from 'react';
|
||||
import classNames from 'classnames';
|
||||
import PropTypes from 'prop-types';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import CallHandler from '../../../CallHandler';
|
||||
|
@ -29,6 +30,7 @@ import E2EIcon from './E2EIcon';
|
|||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
import {aboveLeftOf, ContextMenu, ContextMenuTooltipButton, useContextMenu} from "../../structures/ContextMenu";
|
||||
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
|
||||
import ReplyPreview from "./ReplyPreview";
|
||||
|
||||
function ComposerAvatar(props) {
|
||||
const MemberStatusMessageAvatar = sdk.getComponent('avatars.MemberStatusMessageAvatar');
|
||||
|
@ -117,9 +119,19 @@ const EmojiButton = ({addEmoji}) => {
|
|||
</ContextMenu>;
|
||||
}
|
||||
|
||||
const className = classNames(
|
||||
"mx_MessageComposer_button",
|
||||
"mx_MessageComposer_emoji",
|
||||
{
|
||||
"mx_MessageComposer_button_highlight": menuDisplayed,
|
||||
},
|
||||
);
|
||||
|
||||
// TODO: replace ContextMenuTooltipButton with a unified representation of
|
||||
// the header buttons and the right panel buttons
|
||||
return <React.Fragment>
|
||||
<ContextMenuTooltipButton
|
||||
className="mx_MessageComposer_button mx_MessageComposer_emoji"
|
||||
className={className}
|
||||
onClick={openMenu}
|
||||
isExpanded={menuDisplayed}
|
||||
title={_t('Emoji picker')}
|
||||
|
@ -213,7 +225,7 @@ export default class MessageComposer extends React.Component {
|
|||
this._onRoomViewStoreUpdate = this._onRoomViewStoreUpdate.bind(this);
|
||||
this._onTombstoneClick = this._onTombstoneClick.bind(this);
|
||||
this.renderPlaceholderText = this.renderPlaceholderText.bind(this);
|
||||
|
||||
this._dispatcherRef = null;
|
||||
this.state = {
|
||||
isQuoting: Boolean(RoomViewStore.getQuotingEvent()),
|
||||
tombstone: this._getRoomTombstone(),
|
||||
|
@ -222,7 +234,20 @@ export default class MessageComposer extends React.Component {
|
|||
};
|
||||
}
|
||||
|
||||
onAction = (payload) => {
|
||||
if (payload.action === 'reply_to_event') {
|
||||
// add a timeout for the reply preview to be rendered, so
|
||||
// that the ScrollPanel listening to the resizeNotifier can
|
||||
// correctly measure it's new height and scroll down to keep
|
||||
// at the bottom if it already is
|
||||
setTimeout(() => {
|
||||
this.props.resizeNotifier.notifyTimelineHeightChanged();
|
||||
}, 100);
|
||||
}
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this.dispatcherRef = dis.register(this.onAction);
|
||||
MatrixClientPeg.get().on("RoomState.events", this._onRoomStateEvents);
|
||||
this._roomStoreToken = RoomViewStore.addListener(this._onRoomViewStoreUpdate);
|
||||
this._waitForOwnMember();
|
||||
|
@ -251,6 +276,7 @@ export default class MessageComposer extends React.Component {
|
|||
if (this._roomStoreToken) {
|
||||
this._roomStoreToken.remove();
|
||||
}
|
||||
dis.unregister(this.dispatcherRef);
|
||||
}
|
||||
|
||||
_onRoomStateEvents(ev, state) {
|
||||
|
@ -354,6 +380,7 @@ export default class MessageComposer extends React.Component {
|
|||
key="controls_input"
|
||||
room={this.props.room}
|
||||
placeholder={this.renderPlaceholderText()}
|
||||
resizeNotifier={this.props.resizeNotifier}
|
||||
permalinkCreator={this.props.permalinkCreator} />,
|
||||
<UploadButton key="controls_upload" roomId={this.props.room.roomId} />,
|
||||
<EmojiButton key="emoji_button" addEmoji={this.addEmoji} />,
|
||||
|
@ -404,6 +431,7 @@ export default class MessageComposer extends React.Component {
|
|||
return (
|
||||
<div className="mx_MessageComposer mx_GroupLayout">
|
||||
<div className="mx_MessageComposer_wrapper">
|
||||
<ReplyPreview permalinkCreator={this.props.permalinkCreator} />
|
||||
<div className="mx_MessageComposer_row">
|
||||
{ controls }
|
||||
</div>
|
||||
|
|
|
@ -226,7 +226,7 @@ export default createReactClass({
|
|||
title={_t("Settings")} />;
|
||||
}
|
||||
|
||||
if (this.props.onPinnedClick && SettingsStore.isFeatureEnabled('feature_pinning')) {
|
||||
if (this.props.onPinnedClick && SettingsStore.getValue('feature_pinning')) {
|
||||
let pinsIndicator = null;
|
||||
if (this._hasUnreadPins()) {
|
||||
pinsIndicator = (<div className="mx_RoomHeader_pinsIndicator mx_RoomHeader_pinsIndicatorUnread" />);
|
||||
|
|
|
@ -43,6 +43,8 @@ import SettingsStore from "../../../settings/SettingsStore";
|
|||
import CustomRoomTagStore from "../../../stores/CustomRoomTagStore";
|
||||
import { arrayFastClone, arrayHasDiff } from "../../../utils/arrays";
|
||||
import { objectShallowClone, objectWithOnly } from "../../../utils/objects";
|
||||
import { IconizedContextMenuOption, IconizedContextMenuOptionList } from "../context_menus/IconizedContextMenu";
|
||||
import AccessibleButton from "../elements/AccessibleButton";
|
||||
|
||||
interface IProps {
|
||||
onKeyDown: (ev: React.KeyboardEvent) => void;
|
||||
|
@ -81,6 +83,7 @@ interface ITagAesthetics {
|
|||
sectionLabelRaw?: string;
|
||||
addRoomLabel?: string;
|
||||
onAddRoom?: (dispatcher?: Dispatcher<ActionPayload>) => void;
|
||||
addRoomContextMenu?: (onFinished: () => void) => React.ReactNode;
|
||||
isInvite: boolean;
|
||||
defaultHidden: boolean;
|
||||
}
|
||||
|
@ -112,9 +115,30 @@ const TAG_AESTHETICS: {
|
|||
sectionLabel: _td("Rooms"),
|
||||
isInvite: false,
|
||||
defaultHidden: false,
|
||||
addRoomLabel: _td("Create room"),
|
||||
onAddRoom: (dispatcher?: Dispatcher<ActionPayload>) => {
|
||||
(dispatcher || defaultDispatcher).dispatch({action: 'view_create_room'})
|
||||
addRoomLabel: _td("Add room"),
|
||||
addRoomContextMenu: (onFinished: () => void) => {
|
||||
return <IconizedContextMenuOptionList first>
|
||||
<IconizedContextMenuOption
|
||||
label={_t("Create new room")}
|
||||
iconClassName="mx_RoomList_iconPlus"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
onFinished();
|
||||
defaultDispatcher.dispatch({action: "view_create_room"});
|
||||
}}
|
||||
/>
|
||||
<IconizedContextMenuOption
|
||||
label={_t("Explore public rooms")}
|
||||
iconClassName="mx_RoomList_iconExplore"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
onFinished();
|
||||
defaultDispatcher.fire(Action.ViewRoomDirectory);
|
||||
}}
|
||||
/>
|
||||
</IconizedContextMenuOptionList>;
|
||||
},
|
||||
},
|
||||
[DefaultTagID.LowPriority]: {
|
||||
|
@ -255,6 +279,10 @@ export default class RoomList extends React.PureComponent<IProps, IState> {
|
|||
}
|
||||
};
|
||||
|
||||
private onExplore = () => {
|
||||
dis.fire(Action.ViewRoomDirectory);
|
||||
};
|
||||
|
||||
private renderCommunityInvites(): TemporaryTile[] {
|
||||
// TODO: Put community invites in a more sensible place (not in the room list)
|
||||
// See https://github.com/vector-im/element-web/issues/14456
|
||||
|
@ -324,6 +352,7 @@ export default class RoomList extends React.PureComponent<IProps, IState> {
|
|||
label={aesthetics.sectionLabelRaw ? aesthetics.sectionLabelRaw : _t(aesthetics.sectionLabel)}
|
||||
onAddRoom={aesthetics.onAddRoom}
|
||||
addRoomLabel={aesthetics.addRoomLabel ? _t(aesthetics.addRoomLabel) : aesthetics.addRoomLabel}
|
||||
addRoomContextMenu={aesthetics.addRoomContextMenu}
|
||||
isMinimized={this.props.isMinimized}
|
||||
onResize={this.props.onResize}
|
||||
extraBadTilesThatShouldntExist={extraTiles}
|
||||
|
@ -335,6 +364,16 @@ export default class RoomList extends React.PureComponent<IProps, IState> {
|
|||
}
|
||||
|
||||
public render() {
|
||||
let explorePrompt: JSX.Element;
|
||||
if (RoomListStore.instance.getFirstNameFilterCondition()) {
|
||||
explorePrompt = <div className="mx_RoomList_explorePrompt">
|
||||
<div>{_t("Can't see what you’re looking for?")}</div>
|
||||
<AccessibleButton kind="link" onClick={this.onExplore}>
|
||||
{_t("Explore all public rooms")}
|
||||
</AccessibleButton>
|
||||
</div>;
|
||||
}
|
||||
|
||||
const sublists = this.renderSublists();
|
||||
return (
|
||||
<RovingTabIndexProvider handleHomeEnd={true} onKeyDown={this.props.onKeyDown}>
|
||||
|
@ -346,7 +385,10 @@ export default class RoomList extends React.PureComponent<IProps, IState> {
|
|||
className="mx_RoomList"
|
||||
role="tree"
|
||||
aria-label={_t("Rooms")}
|
||||
>{sublists}</div>
|
||||
>
|
||||
{sublists}
|
||||
{explorePrompt}
|
||||
</div>
|
||||
)}
|
||||
</RovingTabIndexProvider>
|
||||
);
|
||||
|
|
41
src/components/views/rooms/RoomListNumResults.tsx
Normal file
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React, {useState} from "react";
|
||||
|
||||
import { _t } from "../../../languageHandler";
|
||||
import RoomListStore, { LISTS_UPDATE_EVENT } from "../../../stores/room-list/RoomListStore";
|
||||
import {useEventEmitter} from "../../../hooks/useEventEmitter";
|
||||
|
||||
const RoomListNumResults: React.FC = () => {
|
||||
const [count, setCount] = useState<number>(null);
|
||||
useEventEmitter(RoomListStore.instance, LISTS_UPDATE_EVENT, () => {
|
||||
if (RoomListStore.instance.getFirstNameFilterCondition()) {
|
||||
const numRooms = Object.values(RoomListStore.instance.orderedLists).flat(1).length;
|
||||
setCount(numRooms);
|
||||
} else {
|
||||
setCount(null);
|
||||
}
|
||||
});
|
||||
|
||||
if (typeof count !== "number") return null;
|
||||
|
||||
return <div className="mx_LeftPanel_roomListFilterCount">
|
||||
{_t("%(count)s results", { count })}
|
||||
</div>;
|
||||
};
|
||||
|
||||
export default RoomListNumResults;
|
|
@ -50,6 +50,7 @@ import { arrayFastClone, arrayHasOrderChange } from "../../../utils/arrays";
|
|||
import { objectExcluding, objectHasDiff } from "../../../utils/objects";
|
||||
import TemporaryTile from "./TemporaryTile";
|
||||
import { ListNotificationState } from "../../../stores/notifications/ListNotificationState";
|
||||
import IconizedContextMenu from "../context_menus/IconizedContextMenu";
|
||||
|
||||
const SHOW_N_BUTTON_HEIGHT = 28; // As defined by CSS
|
||||
const RESIZE_HANDLE_HEIGHT = 4; // As defined by CSS
|
||||
|
@ -65,6 +66,7 @@ interface IProps {
|
|||
startAsHidden: boolean;
|
||||
label: string;
|
||||
onAddRoom?: () => void;
|
||||
addRoomContextMenu?: (onFinished: () => void) => React.ReactNode;
|
||||
addRoomLabel: string;
|
||||
isMinimized: boolean;
|
||||
tagId: TagID;
|
||||
|
@ -87,6 +89,7 @@ type PartialDOMRect = Pick<DOMRect, "left" | "top" | "height">;
|
|||
|
||||
interface IState {
|
||||
contextMenuPosition: PartialDOMRect;
|
||||
addRoomContextMenuPosition: PartialDOMRect;
|
||||
isResizing: boolean;
|
||||
isExpanded: boolean; // used for the for expand of the sublist when the room list is being filtered
|
||||
height: number;
|
||||
|
@ -112,6 +115,7 @@ export default class RoomSublist extends React.Component<IProps, IState> {
|
|||
this.notificationState = RoomNotificationStateStore.instance.getListState(this.props.tagId);
|
||||
this.state = {
|
||||
contextMenuPosition: null,
|
||||
addRoomContextMenuPosition: null,
|
||||
isResizing: false,
|
||||
isExpanded: this.isBeingFiltered ? this.isBeingFiltered : !this.layout.isCollapsed,
|
||||
height: 0, // to be fixed in a moment, we need `rooms` to calculate this.
|
||||
|
@ -376,10 +380,21 @@ export default class RoomSublist extends React.Component<IProps, IState> {
|
|||
});
|
||||
};
|
||||
|
||||
private onAddRoomContextMenu = (ev: React.MouseEvent) => {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
const target = ev.target as HTMLButtonElement;
|
||||
this.setState({addRoomContextMenuPosition: target.getBoundingClientRect()});
|
||||
};
|
||||
|
||||
private onCloseMenu = () => {
|
||||
this.setState({contextMenuPosition: null});
|
||||
};
|
||||
|
||||
private onCloseAddRoomMenu = () => {
|
||||
this.setState({addRoomContextMenuPosition: null});
|
||||
};
|
||||
|
||||
private onUnreadFirstChanged = async () => {
|
||||
const isUnreadFirst = RoomListStore.instance.getListOrder(this.props.tagId) === ListAlgorithm.Importance;
|
||||
const newAlgorithm = isUnreadFirst ? ListAlgorithm.Natural : ListAlgorithm.Importance;
|
||||
|
@ -594,6 +609,18 @@ export default class RoomSublist extends React.Component<IProps, IState> {
|
|||
</div>
|
||||
</ContextMenu>
|
||||
);
|
||||
} else if (this.state.addRoomContextMenuPosition) {
|
||||
contextMenu = (
|
||||
<IconizedContextMenu
|
||||
chevronFace={ChevronFace.None}
|
||||
left={this.state.addRoomContextMenuPosition.left - 7} // center align with the handle
|
||||
top={this.state.addRoomContextMenuPosition.top + this.state.addRoomContextMenuPosition.height}
|
||||
onFinished={this.onCloseAddRoomMenu}
|
||||
compact
|
||||
>
|
||||
{this.props.addRoomContextMenu(this.onCloseAddRoomMenu)}
|
||||
</IconizedContextMenu>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -637,9 +664,21 @@ export default class RoomSublist extends React.Component<IProps, IState> {
|
|||
tabIndex={tabIndex}
|
||||
onClick={this.onAddRoom}
|
||||
className="mx_RoomSublist_auxButton"
|
||||
tooltipClassName="mx_RoomSublist_addRoomTooltip"
|
||||
aria-label={this.props.addRoomLabel || _t("Add room")}
|
||||
title={this.props.addRoomLabel}
|
||||
tooltipClassName={"mx_RoomSublist_addRoomTooltip"}
|
||||
/>
|
||||
);
|
||||
} else if (this.props.addRoomContextMenu) {
|
||||
addRoomButton = (
|
||||
<ContextMenuTooltipButton
|
||||
tabIndex={tabIndex}
|
||||
onClick={this.onAddRoomContextMenu}
|
||||
className="mx_RoomSublist_auxButton"
|
||||
tooltipClassName="mx_RoomSublist_addRoomTooltip"
|
||||
aria-label={this.props.addRoomLabel || _t("Add room")}
|
||||
title={this.props.addRoomLabel}
|
||||
isExpanded={!!this.state.addRoomContextMenuPosition}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -113,13 +113,19 @@ export default class RoomTile extends React.PureComponent<IProps, IState> {
|
|||
};
|
||||
|
||||
private get showContextMenu(): boolean {
|
||||
return !this.props.isMinimized && this.props.tag !== DefaultTagID.Invite;
|
||||
return this.props.tag !== DefaultTagID.Invite;
|
||||
}
|
||||
|
||||
private get showMessagePreview(): boolean {
|
||||
return !this.props.isMinimized && this.props.showMessagePreview;
|
||||
}
|
||||
|
||||
public componentDidUpdate(prevProps: Readonly<IProps>, prevState: Readonly<IState>) {
|
||||
if (prevProps.showMessagePreview !== this.props.showMessagePreview && this.showMessagePreview) {
|
||||
this.setState({messagePreview: this.generatePreview()});
|
||||
}
|
||||
}
|
||||
|
||||
public componentDidMount() {
|
||||
// when we're first rendered (or our sublist is expanded) make sure we are visible if we're active
|
||||
if (this.state.selected) {
|
||||
|
@ -298,7 +304,9 @@ export default class RoomTile extends React.PureComponent<IProps, IState> {
|
|||
private onClickMute = ev => this.saveNotifState(ev, MUTE);
|
||||
|
||||
private renderNotificationsMenu(isActive: boolean): React.ReactElement {
|
||||
if (MatrixClientPeg.get().isGuest() || this.props.tag === DefaultTagID.Archived || !this.showContextMenu) {
|
||||
if (MatrixClientPeg.get().isGuest() || this.props.tag === DefaultTagID.Archived ||
|
||||
!this.showContextMenu || this.props.isMinimized
|
||||
) {
|
||||
// the menu makes no sense in these cases so do not show one
|
||||
return null;
|
||||
}
|
||||
|
@ -524,9 +532,13 @@ export default class RoomTile extends React.PureComponent<IProps, IState> {
|
|||
ariaDescribedBy = messagePreviewId(this.props.room.roomId);
|
||||
}
|
||||
|
||||
const props: Partial<React.ComponentProps<typeof AccessibleTooltipButton>> = {};
|
||||
let Button: React.ComponentType<React.ComponentProps<typeof AccessibleButton>> = AccessibleButton;
|
||||
if (this.props.isMinimized) {
|
||||
Button = AccessibleTooltipButton;
|
||||
props.title = name;
|
||||
// force the tooltip to hide whilst we are showing the context menu
|
||||
props.forceHide = !!this.state.generalMenuPosition;
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -534,6 +546,7 @@ export default class RoomTile extends React.PureComponent<IProps, IState> {
|
|||
<RovingTabIndexWrapper inputRef={this.roomTileRef}>
|
||||
{({onFocus, isActive, ref}) =>
|
||||
<Button
|
||||
{...props}
|
||||
onFocus={onFocus}
|
||||
tabIndex={isActive ? 0 : -1}
|
||||
inputRef={ref}
|
||||
|
@ -544,7 +557,6 @@ export default class RoomTile extends React.PureComponent<IProps, IState> {
|
|||
aria-label={ariaLabel}
|
||||
aria-selected={this.state.selected}
|
||||
aria-describedby={ariaDescribedBy}
|
||||
title={this.props.isMinimized ? name : undefined}
|
||||
>
|
||||
{roomAvatar}
|
||||
{nameContainer}
|
||||
|
|
|
@ -1,168 +0,0 @@
|
|||
/*
|
||||
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { Room } from "matrix-js-sdk/src/models/room";
|
||||
import { DefaultTagID, TagID } from "../../../stores/room-list/models";
|
||||
import { User } from "matrix-js-sdk/src/models/user";
|
||||
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||
import DMRoomMap from "../../../utils/DMRoomMap";
|
||||
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
||||
import { isPresenceEnabled } from "../../../utils/presence";
|
||||
import { _t } from "../../../languageHandler";
|
||||
import TextWithTooltip from "../elements/TextWithTooltip";
|
||||
|
||||
enum Icon {
|
||||
// Note: the names here are used in CSS class names
|
||||
None = "NONE", // ... except this one
|
||||
Globe = "GLOBE",
|
||||
PresenceOnline = "ONLINE",
|
||||
PresenceAway = "AWAY",
|
||||
PresenceOffline = "OFFLINE",
|
||||
}
|
||||
|
||||
function tooltipText(variant: Icon) {
|
||||
switch (variant) {
|
||||
case Icon.Globe:
|
||||
return _t("This room is public");
|
||||
case Icon.PresenceOnline:
|
||||
return _t("Online");
|
||||
case Icon.PresenceAway:
|
||||
return _t("Away");
|
||||
case Icon.PresenceOffline:
|
||||
return _t("Offline");
|
||||
}
|
||||
}
|
||||
|
||||
interface IProps {
|
||||
room: Room;
|
||||
}
|
||||
|
||||
interface IState {
|
||||
icon: Icon;
|
||||
}
|
||||
|
||||
export default class RoomTileIcon extends React.Component<IProps, IState> {
|
||||
private _dmUser: User;
|
||||
private isUnmounted = false;
|
||||
private isWatchingTimeline = false;
|
||||
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
icon: this.calculateIcon(),
|
||||
};
|
||||
}
|
||||
|
||||
private get isPublicRoom(): boolean {
|
||||
const joinRules = this.props.room.currentState.getStateEvents("m.room.join_rules", "");
|
||||
const joinRule = joinRules && joinRules.getContent().join_rule;
|
||||
return joinRule === 'public';
|
||||
}
|
||||
|
||||
private get dmUser(): User {
|
||||
return this._dmUser;
|
||||
}
|
||||
|
||||
private set dmUser(val: User) {
|
||||
const oldUser = this._dmUser;
|
||||
this._dmUser = val;
|
||||
if (oldUser && oldUser !== this._dmUser) {
|
||||
oldUser.off('User.currentlyActive', this.onPresenceUpdate);
|
||||
oldUser.off('User.presence', this.onPresenceUpdate);
|
||||
}
|
||||
if (this._dmUser && oldUser !== this._dmUser) {
|
||||
this._dmUser.on('User.currentlyActive', this.onPresenceUpdate);
|
||||
this._dmUser.on('User.presence', this.onPresenceUpdate);
|
||||
}
|
||||
}
|
||||
|
||||
public componentWillUnmount() {
|
||||
this.isUnmounted = true;
|
||||
if (this.isWatchingTimeline) this.props.room.off('Room.timeline', this.onRoomTimeline);
|
||||
this.dmUser = null; // clear listeners, if any
|
||||
}
|
||||
|
||||
private onRoomTimeline = (ev: MatrixEvent, room: Room) => {
|
||||
if (this.isUnmounted) return;
|
||||
|
||||
// apparently these can happen?
|
||||
if (!room) return;
|
||||
if (this.props.room.roomId !== room.roomId) return;
|
||||
|
||||
if (ev.getType() === 'm.room.join_rules' || ev.getType() === 'm.room.member') {
|
||||
this.setState({icon: this.calculateIcon()});
|
||||
}
|
||||
};
|
||||
|
||||
private onPresenceUpdate = () => {
|
||||
if (this.isUnmounted) return;
|
||||
|
||||
let newIcon = this.getPresenceIcon();
|
||||
if (newIcon !== this.state.icon) this.setState({icon: newIcon});
|
||||
};
|
||||
|
||||
private getPresenceIcon(): Icon {
|
||||
if (!this.dmUser) return Icon.None;
|
||||
|
||||
let icon = Icon.None;
|
||||
|
||||
const isOnline = this.dmUser.currentlyActive || this.dmUser.presence === 'online';
|
||||
if (isOnline) {
|
||||
icon = Icon.PresenceOnline;
|
||||
} else if (this.dmUser.presence === 'offline') {
|
||||
icon = Icon.PresenceOffline;
|
||||
} else if (this.dmUser.presence === 'unavailable') {
|
||||
icon = Icon.PresenceAway;
|
||||
}
|
||||
|
||||
return icon;
|
||||
}
|
||||
|
||||
private calculateIcon(): Icon {
|
||||
let icon = Icon.None;
|
||||
|
||||
// We look at the DMRoomMap and not the tag here so that we don't exclude DMs in Favourites
|
||||
const otherUserId = DMRoomMap.shared().getUserIdForRoomId(this.props.room.roomId);
|
||||
if (otherUserId && this.props.room.getJoinedMemberCount() === 2) {
|
||||
// Track presence, if available
|
||||
if (isPresenceEnabled()) {
|
||||
if (otherUserId) {
|
||||
this.dmUser = MatrixClientPeg.get().getUser(otherUserId);
|
||||
icon = this.getPresenceIcon();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Track publicity
|
||||
icon = this.isPublicRoom ? Icon.Globe : Icon.None;
|
||||
if (!this.isWatchingTimeline) {
|
||||
this.props.room.on('Room.timeline', this.onRoomTimeline);
|
||||
this.isWatchingTimeline = true;
|
||||
}
|
||||
}
|
||||
return icon;
|
||||
}
|
||||
|
||||
public render(): React.ReactElement {
|
||||
if (this.state.icon === Icon.None) return null;
|
||||
|
||||
return <TextWithTooltip
|
||||
tooltip={tooltipText(this.state.icon)}
|
||||
class={`mx_RoomTileIcon mx_RoomTileIcon_${this.state.icon.toLowerCase()}`}
|
||||
/>;
|
||||
}
|
||||
}
|
|
@ -29,7 +29,6 @@ import {
|
|||
} from '../../../editor/serialize';
|
||||
import {CommandPartCreator} from '../../../editor/parts';
|
||||
import BasicMessageComposer from "./BasicMessageComposer";
|
||||
import ReplyPreview from "./ReplyPreview";
|
||||
import RoomViewStore from '../../../stores/RoomViewStore';
|
||||
import ReplyThread from "../elements/ReplyThread";
|
||||
import {parseEvent} from '../../../editor/deserialize';
|
||||
|
@ -444,9 +443,6 @@ export default class SendMessageComposer extends React.Component {
|
|||
render() {
|
||||
return (
|
||||
<div className="mx_SendMessageComposer" onClick={this.focusComposer} onKeyDown={this._onKeyDown}>
|
||||
<div className="mx_SendMessageComposer_overlayWrapper">
|
||||
<ReplyPreview permalinkCreator={this.props.permalinkCreator} />
|
||||
</div>
|
||||
<BasicMessageComposer
|
||||
ref={this._setEditorRef}
|
||||
model={this.model}
|
||||
|
|
|
@ -14,6 +14,7 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import {_t, _td} from '../../../languageHandler';
|
||||
import AppTile from '../elements/AppTile';
|
||||
import {MatrixClientPeg} from '../../../MatrixClientPeg';
|
||||
|
@ -362,7 +363,7 @@ export default class Stickerpicker extends React.Component {
|
|||
*/
|
||||
_launchManageIntegrations() {
|
||||
// TODO: Open the right integration manager for the widget
|
||||
if (SettingsStore.isFeatureEnabled("feature_many_integration_managers")) {
|
||||
if (SettingsStore.getValue("feature_many_integration_managers")) {
|
||||
IntegrationManagers.sharedInstance().openAll(
|
||||
this.props.room,
|
||||
`type_${WidgetType.STICKERPICKER.preferred}`,
|
||||
|
@ -380,14 +381,21 @@ export default class Stickerpicker extends React.Component {
|
|||
render() {
|
||||
let stickerPicker;
|
||||
let stickersButton;
|
||||
const className = classNames(
|
||||
"mx_MessageComposer_button",
|
||||
"mx_MessageComposer_stickers",
|
||||
"mx_Stickers_hideStickers",
|
||||
"mx_MessageComposer_button_highlight",
|
||||
);
|
||||
if (this.state.showStickers) {
|
||||
// Show hide-stickers button
|
||||
stickersButton =
|
||||
<AccessibleButton
|
||||
id='stickersButton'
|
||||
key="controls_hide_stickers"
|
||||
className="mx_MessageComposer_button mx_MessageComposer_stickers mx_Stickers_hideStickers"
|
||||
className={className}
|
||||
onClick={this._onHideStickersClick}
|
||||
active={this.state.showStickers}
|
||||
title={_t("Hide Stickers")}
|
||||
>
|
||||
</AccessibleButton>;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2019 The Matrix.org Foundation C.I.C.
|
||||
Copyright 2019, 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.
|
||||
|
@ -14,8 +14,10 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from "react";
|
||||
import {Room} from "matrix-js-sdk/src/models/room";
|
||||
import {MatrixEvent} from "matrix-js-sdk/src/models/event";
|
||||
|
||||
import {_t} from "../../../../../languageHandler";
|
||||
import {MatrixClientPeg} from "../../../../../MatrixClientPeg";
|
||||
import BridgeTile from "../../BridgeTile";
|
||||
|
@ -27,28 +29,26 @@ const BRIDGE_EVENT_TYPES = [
|
|||
|
||||
const BRIDGES_LINK = "https://matrix.org/bridges/";
|
||||
|
||||
export default class BridgeSettingsTab extends React.Component {
|
||||
static propTypes = {
|
||||
roomId: PropTypes.string.isRequired,
|
||||
};
|
||||
interface IProps {
|
||||
roomId: string;
|
||||
}
|
||||
|
||||
_renderBridgeCard(event, room) {
|
||||
export default class BridgeSettingsTab extends React.Component<IProps> {
|
||||
private renderBridgeCard(event: MatrixEvent, room: Room) {
|
||||
const content = event.getContent();
|
||||
if (!content || !content.channel || !content.protocol) {
|
||||
return null;
|
||||
}
|
||||
return <BridgeTile room={room} ev={event}></BridgeTile>;
|
||||
return <BridgeTile key={event.getId()} room={room} ev={event} />;
|
||||
}
|
||||
|
||||
static getBridgeStateEvents(roomId) {
|
||||
static getBridgeStateEvents(roomId: string) {
|
||||
const client = MatrixClientPeg.get();
|
||||
const roomState = (client.getRoom(roomId)).currentState;
|
||||
const roomState = client.getRoom(roomId).currentState;
|
||||
|
||||
const bridgeEvents = [].concat(...BRIDGE_EVENT_TYPES.map((typeName) =>
|
||||
Object.values(roomState.events[typeName] || {}),
|
||||
return [].concat(...BRIDGE_EVENT_TYPES.map((typeName) =>
|
||||
Array.from(roomState.events.get(typeName).values()),
|
||||
));
|
||||
|
||||
return bridgeEvents;
|
||||
}
|
||||
|
||||
render() {
|
||||
|
@ -58,8 +58,7 @@ export default class BridgeSettingsTab extends React.Component {
|
|||
const client = MatrixClientPeg.get();
|
||||
const room = client.getRoom(this.props.roomId);
|
||||
|
||||
let content = null;
|
||||
|
||||
let content: JSX.Element;
|
||||
if (bridgeEvents.length > 0) {
|
||||
content = <div>
|
||||
<p>{_t(
|
||||
|
@ -72,7 +71,7 @@ export default class BridgeSettingsTab extends React.Component {
|
|||
},
|
||||
)}</p>
|
||||
<ul className="mx_RoomSettingsDialog_BridgeList">
|
||||
{ bridgeEvents.map((event) => this._renderBridgeCard(event, room)) }
|
||||
{ bridgeEvents.map((event) => this.renderBridgeCard(event, room)) }
|
||||
</ul>
|
||||
</div>;
|
||||
} else {
|
|
@ -22,6 +22,7 @@ import * as sdk from "../../../../..";
|
|||
import LabelledToggleSwitch from "../../../elements/LabelledToggleSwitch";
|
||||
import Modal from "../../../../../Modal";
|
||||
import QuestionDialog from "../../../dialogs/QuestionDialog";
|
||||
import StyledRadioGroup from '../../../elements/StyledRadioGroup';
|
||||
import {SettingLevel} from "../../../../../settings/SettingLevel";
|
||||
|
||||
export default class SecurityRoomSettingsTab extends React.Component {
|
||||
|
@ -144,7 +145,7 @@ export default class SecurityRoomSettingsTab extends React.Component {
|
|||
});
|
||||
};
|
||||
|
||||
_onRoomAccessRadioToggle = (ev) => {
|
||||
_onRoomAccessRadioToggle = (roomAccess) => {
|
||||
// join_rule
|
||||
// INVITE | PUBLIC
|
||||
// ----------------------+----------------
|
||||
|
@ -161,7 +162,7 @@ export default class SecurityRoomSettingsTab extends React.Component {
|
|||
let joinRule = "invite";
|
||||
let guestAccess = "can_join";
|
||||
|
||||
switch (ev.target.value) {
|
||||
switch (roomAccess) {
|
||||
case "invite_only":
|
||||
// no change - use defaults above
|
||||
break;
|
||||
|
@ -190,11 +191,11 @@ export default class SecurityRoomSettingsTab extends React.Component {
|
|||
});
|
||||
};
|
||||
|
||||
_onHistoryRadioToggle = (ev) => {
|
||||
_onHistoryRadioToggle = (history) => {
|
||||
const beforeHistory = this.state.history;
|
||||
this.setState({history: ev.target.value});
|
||||
this.setState({history: history});
|
||||
MatrixClientPeg.get().sendStateEvent(this.props.roomId, "m.room.history_visibility", {
|
||||
history_visibility: ev.target.value,
|
||||
history_visibility: history,
|
||||
}, "").catch((e) => {
|
||||
console.error(e);
|
||||
this.setState({history: beforeHistory});
|
||||
|
@ -257,27 +258,31 @@ export default class SecurityRoomSettingsTab extends React.Component {
|
|||
<div>
|
||||
{guestWarning}
|
||||
{aliasWarning}
|
||||
<label>
|
||||
<input type="radio" name="roomVis" value="invite_only"
|
||||
disabled={!canChangeAccess}
|
||||
<StyledRadioGroup
|
||||
name="roomVis"
|
||||
value={joinRule}
|
||||
onChange={this._onRoomAccessRadioToggle}
|
||||
checked={joinRule !== "public"} />
|
||||
{_t('Only people who have been invited')}
|
||||
</label>
|
||||
<label>
|
||||
<input type="radio" name="roomVis" value="public_no_guests"
|
||||
disabled={!canChangeAccess}
|
||||
onChange={this._onRoomAccessRadioToggle}
|
||||
checked={joinRule === "public" && guestAccess !== "can_join"} />
|
||||
{_t('Anyone who knows the room\'s link, apart from guests')}
|
||||
</label>
|
||||
<label>
|
||||
<input type="radio" name="roomVis" value="public_with_guests"
|
||||
disabled={!canChangeAccess}
|
||||
onChange={this._onRoomAccessRadioToggle}
|
||||
checked={joinRule === "public" && guestAccess === "can_join"} />
|
||||
{_t("Anyone who knows the room's link, including guests")}
|
||||
</label>
|
||||
definitions={[
|
||||
{
|
||||
value: "invite_only",
|
||||
disabled: !canChangeAccess,
|
||||
label: _t('Only people who have been invited'),
|
||||
checked: joinRule !== "public",
|
||||
},
|
||||
{
|
||||
value: "public_no_guests",
|
||||
disabled: !canChangeAccess,
|
||||
label: _t('Anyone who knows the room\'s link, apart from guests'),
|
||||
checked: joinRule === "public" && guestAccess !== "can_join",
|
||||
},
|
||||
{
|
||||
value: "public_with_guests",
|
||||
disabled: !canChangeAccess,
|
||||
label: _t("Anyone who knows the room's link, including guests"),
|
||||
checked: joinRule === "public" && guestAccess === "can_join",
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -294,34 +299,33 @@ export default class SecurityRoomSettingsTab extends React.Component {
|
|||
{_t('Changes to who can read history will only apply to future messages in this room. ' +
|
||||
'The visibility of existing history will be unchanged.')}
|
||||
</div>
|
||||
<label>
|
||||
<input type="radio" name="historyVis" value="world_readable"
|
||||
disabled={!canChangeHistory}
|
||||
checked={history === "world_readable"}
|
||||
onChange={this._onHistoryRadioToggle} />
|
||||
{_t("Anyone")}
|
||||
</label>
|
||||
<label>
|
||||
<input type="radio" name="historyVis" value="shared"
|
||||
disabled={!canChangeHistory}
|
||||
checked={history === "shared"}
|
||||
onChange={this._onHistoryRadioToggle} />
|
||||
{_t('Members only (since the point in time of selecting this option)')}
|
||||
</label>
|
||||
<label>
|
||||
<input type="radio" name="historyVis" value="invited"
|
||||
disabled={!canChangeHistory}
|
||||
checked={history === "invited"}
|
||||
onChange={this._onHistoryRadioToggle} />
|
||||
{_t('Members only (since they were invited)')}
|
||||
</label>
|
||||
<label >
|
||||
<input type="radio" name="historyVis" value="joined"
|
||||
disabled={!canChangeHistory}
|
||||
checked={history === "joined"}
|
||||
onChange={this._onHistoryRadioToggle} />
|
||||
{_t('Members only (since they joined)')}
|
||||
</label>
|
||||
<StyledRadioGroup
|
||||
name="historyVis"
|
||||
value={history}
|
||||
onChange={this._onHistoryRadioToggle}
|
||||
definitions={[
|
||||
{
|
||||
value: "world_readable",
|
||||
disabled: !canChangeHistory,
|
||||
label: _t("Anyone"),
|
||||
},
|
||||
{
|
||||
value: "shared",
|
||||
disabled: !canChangeHistory,
|
||||
label: _t('Members only (since the point in time of selecting this option)'),
|
||||
},
|
||||
{
|
||||
value: "invited",
|
||||
disabled: !canChangeHistory,
|
||||
label: _t('Members only (since they were invited)'),
|
||||
},
|
||||
{
|
||||
value: "joined",
|
||||
disabled: !canChangeHistory,
|
||||
label: _t('Members only (since they joined)'),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -237,7 +237,7 @@ export default class AppearanceUserSettingsTab extends React.Component<IProps, I
|
|||
}
|
||||
|
||||
let customThemeForm: JSX.Element;
|
||||
if (SettingsStore.isFeatureEnabled("feature_custom_themes")) {
|
||||
if (SettingsStore.getValue("feature_custom_themes")) {
|
||||
let messageElement = null;
|
||||
if (this.state.customThemeMessage.text) {
|
||||
if (this.state.customThemeMessage.isError) {
|
||||
|
|
|
@ -28,14 +28,15 @@ export class LabsSettingToggle extends React.Component {
|
|||
};
|
||||
|
||||
_onChange = async (checked) => {
|
||||
await SettingsStore.setFeatureEnabled(this.props.featureId, checked);
|
||||
await SettingsStore.setValue(this.props.featureId, null, SettingLevel.DEVICE, checked);
|
||||
this.forceUpdate();
|
||||
};
|
||||
|
||||
render() {
|
||||
const label = SettingsStore.getDisplayName(this.props.featureId);
|
||||
const value = SettingsStore.isFeatureEnabled(this.props.featureId);
|
||||
return <LabelledToggleSwitch value={value} label={label} onChange={this._onChange} />;
|
||||
const value = SettingsStore.getValue(this.props.featureId);
|
||||
const canChange = SettingsStore.canSetValue(this.props.featureId, null, SettingLevel.DEVICE);
|
||||
return <LabelledToggleSwitch value={value} label={label} onChange={this._onChange} disabled={!canChange} />;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -46,7 +47,7 @@ export default class LabsUserSettingsTab extends React.Component {
|
|||
|
||||
render() {
|
||||
const SettingsFlag = sdk.getComponent("views.elements.SettingsFlag");
|
||||
const flags = SettingsStore.getLabsFeatures().map(f => <LabsSettingToggle featureId={f} key={f} />);
|
||||
const flags = SettingsStore.getFeatureSettingNames().map(f => <LabsSettingToggle featureId={f} key={f} />);
|
||||
return (
|
||||
<div className="mx_SettingsTab">
|
||||
<div className="mx_SettingsTab_heading">{_t("Labs")}</div>
|
||||
|
|