Merge remote-tracking branch 'origin/develop' into dbkr/udd_no_auto_show

This commit is contained in:
David Baker 2017-11-15 12:11:33 +00:00
commit f8fc6dc83e
69 changed files with 3101 additions and 1068 deletions

View file

@ -0,0 +1,157 @@
/*
Copyright 2017 Travis Ralston
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.
*/
'use strict';
import React from "react";
import * as sdk from "../../../index";
import MatrixClientPeg from "../../../MatrixClientPeg";
import AccessibleButton from '../elements/AccessibleButton';
import Presence from "../../../Presence";
import dispatcher from "../../../dispatcher";
import * as ContextualMenu from "../../structures/ContextualMenu";
import SettingsStore from "../../../settings/SettingsStore";
module.exports = React.createClass({
displayName: 'MemberPresenceAvatar',
propTypes: {
member: React.PropTypes.object.isRequired,
width: React.PropTypes.number,
height: React.PropTypes.number,
resizeMethod: React.PropTypes.string,
},
getDefaultProps: function() {
return {
width: 40,
height: 40,
resizeMethod: 'crop',
};
},
getInitialState: function() {
const presenceState = this.props.member.user.presence;
const presenceMessage = this.props.member.user.presenceStatusMsg;
return {
status: presenceState,
message: presenceMessage,
};
},
componentWillMount: function() {
MatrixClientPeg.get().on("User.presence", this.onUserPresence);
this.dispatcherRef = dispatcher.register(this.onAction);
},
componentWillUnmount: function() {
if (MatrixClientPeg.get()) {
MatrixClientPeg.get().removeListener("User.presence", this.onUserPresence);
}
dispatcher.unregister(this.dispatcherRef);
},
onAction: function(payload) {
if (payload.action !== "self_presence_updated") return;
if (this.props.member.userId !== MatrixClientPeg.get().getUserId()) return;
this.setState({
status: payload.statusInfo.presence,
message: payload.statusInfo.status_msg,
});
},
onUserPresence: function(event, user) {
if (user.userId !== MatrixClientPeg.get().getUserId()) return;
this.setState({
status: user.presence,
message: user.presenceStatusMsg,
});
},
onStatusChange: function(newStatus) {
Presence.stopMaintainingStatus();
if (newStatus === "online") {
Presence.setState(newStatus);
} else Presence.setState(newStatus, null, true);
},
onClick: function(e) {
const PresenceContextMenu = sdk.getComponent('context_menus.PresenceContextMenu');
const elementRect = e.target.getBoundingClientRect();
// The window X and Y offsets are to adjust position when zoomed in to page
const x = (elementRect.left + window.pageXOffset) - (elementRect.width / 2) + 3;
const chevronOffset = 12;
let y = elementRect.top + (elementRect.height / 2) + window.pageYOffset;
y = y - (chevronOffset + 4); // where 4 is 1/4 the height of the chevron
ContextualMenu.createMenu(PresenceContextMenu, {
chevronOffset: chevronOffset,
chevronFace: 'bottom',
left: x,
top: y,
menuWidth: 125,
currentStatus: this.state.status,
onChange: this.onStatusChange,
});
e.stopPropagation();
// const presenceState = this.props.member.user.presence;
// const presenceLastActiveAgo = this.props.member.user.lastActiveAgo;
// const presenceLastTs = this.props.member.user.lastPresenceTs;
// const presenceCurrentlyActive = this.props.member.user.currentlyActive;
// const presenceMessage = this.props.member.user.presenceStatusMsg;
},
render: function() {
const MemberAvatar = sdk.getComponent("avatars.MemberAvatar");
let onClickFn = null;
if (this.props.member.userId === MatrixClientPeg.get().getUserId()) {
onClickFn = this.onClick;
}
const avatarNode = (
<MemberAvatar member={this.props.member} width={this.props.width} height={this.props.height}
resizeMethod={this.props.resizeMethod} />
);
let statusNode = (
<span className={"mx_MemberPresenceAvatar_status mx_MemberPresenceAvatar_status_" + this.state.status} />
);
// LABS: Disable presence management functions for now
if (!SettingsStore.isFeatureEnabled("feature_presence_management")) {
statusNode = null;
onClickFn = null;
}
let avatar = (
<div className="mx_MemberPresenceAvatar">
{ avatarNode }
{ statusNode }
</div>
);
if (onClickFn) {
avatar = (
<AccessibleButton onClick={onClickFn} className="mx_MemberPresenceAvatar" element="div">
{ avatarNode }
{ statusNode }
</AccessibleButton>
);
}
return avatar;
},
});

View file

@ -21,6 +21,7 @@ import MatrixClientPeg from '../../../MatrixClientPeg';
import GeminiScrollbar from 'react-gemini-scrollbar';
import Resend from '../../../Resend';
import { _t } from '../../../languageHandler';
import SettingsStore from "../../../settings/SettingsStore";
function DeviceListEntry(props) {
const {userId, device} = props;
@ -114,12 +115,13 @@ export default React.createClass({
},
render: function() {
const client = MatrixClientPeg.get();
const blacklistUnverified = client.getGlobalBlacklistUnverifiedDevices() ||
this.props.room.getBlacklistUnverifiedDevices();
if (this.state.devices === null) {
const Spinner = sdk.getComponent("elements.Spinner");
return <Spinner />;
}
let warning;
if (blacklistUnverified) {
if (SettingsStore.getValue("blacklistUnverifiedDevices", this.props.room.roomId)) {
warning = (
<h4>
{ _t("You are currently blacklisting unverified devices; to send " +

View file

@ -381,12 +381,12 @@ export default React.createClass({
return (
<div className={this.props.fullWidth ? "mx_AppTileFullWidth" : "mx_AppTile"} id={this.props.id}>
<div ref="menu_bar" className="mx_AppTileMenuBar" onClick={this.onClickMenuBar}>
{ this.formatAppTileName() }
<b>{ this.formatAppTileName() }</b>
<span className="mx_AppTileMenuBarWidgets">
{ /* Edit widget */ }
{ showEditButton && <img
src="img/edit.svg"
className="mx_filterFlipColor mx_AppTileMenuBarWidget mx_AppTileMenuBarWidgetPadding"
src="img/edit_green.svg"
className="mx_AppTileMenuBarWidget mx_AppTileMenuBarWidgetPadding"
width="8" height="8"
alt={_t('Edit')}
title={_t('Edit')}

View file

@ -18,8 +18,8 @@ limitations under the License.
import React from 'react';
import sdk from '../../../index';
import UserSettingsStore from '../../../UserSettingsStore';
import * as languageHandler from '../../../languageHandler';
import SettingsStore from "../../../settings/SettingsStore";
function languageMatchesSearchQuery(query, language) {
if (language.label.toUpperCase().indexOf(query.toUpperCase()) == 0) return true;
@ -54,9 +54,9 @@ export default class LanguageDropdown extends React.Component {
// If no value is given, we start with the first
// country selected, but our parent component
// doesn't know this, therefore we do this.
const _localSettings = UserSettingsStore.getLocalSettings();
if (_localSettings.hasOwnProperty('language')) {
this.props.onOptionChange(_localSettings.language);
const language = SettingsStore.getValue("language", null, /*excludeDefault:*/true);
if (language) {
this.props.onOptionChange(language);
}else {
const language = languageHandler.normalizeLanguageKey(languageHandler.getLanguageFromBrowser());
this.props.onOptionChange(language);
@ -95,12 +95,12 @@ export default class LanguageDropdown extends React.Component {
// default value here too, otherwise we need to handle null / undefined
// values between mounting and the initial value propgating
let language = SettingsStore.getValue("language", null, /*excludeDefault:*/true);
let value = null;
const _localSettings = UserSettingsStore.getLocalSettings();
if (_localSettings.hasOwnProperty('language')) {
value = this.props.value || _localSettings.language;
if (language) {
value = this.props.value || language;
} else {
const language = navigator.language || navigator.userLanguage;
language = navigator.language || navigator.userLanguage;
value = this.props.value || language;
}

View file

@ -20,14 +20,16 @@ import React from 'react';
import * as Roles from '../../../Roles';
import { _t } from '../../../languageHandler';
let LEVEL_ROLE_MAP = {};
const reverseRoles = {};
module.exports = React.createClass({
displayName: 'PowerSelector',
propTypes: {
value: React.PropTypes.number.isRequired,
// The maximum value that can be set with the power selector
maxValue: React.PropTypes.number.isRequired,
// Default user power level for the room
usersDefault: React.PropTypes.number.isRequired,
// if true, the <select/> should be a 'controlled' form element and updated by React
// to reflect the current value, rather than left freeform.
@ -43,78 +45,98 @@ module.exports = React.createClass({
getInitialState: function() {
return {
custom: (LEVEL_ROLE_MAP[this.props.value] === undefined),
levelRoleMap: {},
// List of power levels to show in the drop-down
options: [],
};
},
getDefaultProps: function() {
return {
maxValue: Infinity,
usersDefault: 0,
};
},
componentWillMount: function() {
LEVEL_ROLE_MAP = Roles.levelRoleMap();
Object.keys(LEVEL_ROLE_MAP).forEach(function(key) {
reverseRoles[LEVEL_ROLE_MAP[key]] = key;
});
this._initStateFromProps(this.props);
},
componentWillReceiveProps: function(newProps) {
this._initStateFromProps(newProps);
},
_initStateFromProps: function(newProps) {
// This needs to be done now because levelRoleMap has translated strings
const levelRoleMap = Roles.levelRoleMap(newProps.usersDefault);
const options = Object.keys(levelRoleMap).filter((l) => {
return l === undefined || l <= newProps.maxValue;
});
this.setState({
levelRoleMap,
options,
custom: levelRoleMap[newProps.value] === undefined,
});
},
onSelectChange: function(event) {
this.setState({ custom: event.target.value === "Custom" });
if (event.target.value !== "Custom") {
this.props.onChange(this.getValue());
this.setState({ custom: event.target.value === "SELECT_VALUE_CUSTOM" });
if (event.target.value !== "SELECT_VALUE_CUSTOM") {
this.props.onChange(event.target.value);
}
},
onCustomBlur: function(event) {
this.props.onChange(this.getValue());
this.props.onChange(parseInt(this.refs.custom.value));
},
onCustomKeyDown: function(event) {
if (event.key == "Enter") {
this.props.onChange(this.getValue());
this.props.onChange(parseInt(this.refs.custom.value));
}
},
getValue: function() {
let value;
if (this.refs.select) {
value = reverseRoles[this.refs.select.value];
if (this.refs.custom) {
if (value === undefined) value = parseInt( this.refs.custom.value );
}
}
return value;
},
render: function() {
let customPicker;
if (this.state.custom) {
let input;
if (this.props.disabled) {
input = <span>{ this.props.value }</span>;
customPicker = <span>{ _t(
"Custom of %(powerLevel)s",
{ powerLevel: this.props.value },
) }</span>;
} else {
input = <input ref="custom" type="text" size="3" defaultValue={this.props.value} onBlur={this.onCustomBlur} onKeyDown={this.onCustomKeyDown} />;
customPicker = <span> = <input
ref="custom"
type="text"
size="3"
defaultValue={this.props.value}
onBlur={this.onCustomBlur}
onKeyDown={this.onCustomKeyDown}
/>
</span>;
}
customPicker = <span> of { input }</span>;
}
let selectValue;
if (this.state.custom) {
selectValue = "Custom";
selectValue = "SELECT_VALUE_CUSTOM";
} else {
selectValue = LEVEL_ROLE_MAP[this.props.value] || "Custom";
selectValue = this.state.levelRoleMap[this.props.value] ?
this.props.value : "SELECT_VALUE_CUSTOM";
}
let select;
if (this.props.disabled) {
select = <span>{ selectValue }</span>;
select = <span>{ this.state.levelRoleMap[selectValue] }</span>;
} else {
// Each level must have a definition in LEVEL_ROLE_MAP
const levels = [0, 50, 100];
let options = levels.map((level) => {
// Each level must have a definition in this.state.levelRoleMap
let options = this.state.options.map((level) => {
return {
value: LEVEL_ROLE_MAP[level],
// Give a userDefault (users_default in the power event) of 0 but
// because level !== undefined, this should never be used.
text: Roles.textualPowerLevel(level, 0),
value: level,
text: Roles.textualPowerLevel(level, this.props.usersDefault),
};
});
options.push({ value: "Custom", text: _t("Custom level") });
options.push({ value: "SELECT_VALUE_CUSTOM", text: _t("Custom level") });
options = options.map((op) => {
return <option value={op.value} key={op.value}>{ op.text }</option>;
});

View file

@ -0,0 +1,110 @@
/*
Copyright 2017 Travis Ralston
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 SettingsStore from "../../../settings/SettingsStore";
import { _t } from '../../../languageHandler';
module.exports = React.createClass({
displayName: 'SettingsFlag',
propTypes: {
name: React.PropTypes.string.isRequired,
level: React.PropTypes.string.isRequired,
roomId: React.PropTypes.string, // for per-room settings
label: React.PropTypes.string, // untranslated
onChange: React.PropTypes.func,
isExplicit: React.PropTypes.bool,
manualSave: React.PropTypes.bool,
// If group is supplied, then this will create a radio button instead.
group: React.PropTypes.string,
value: React.PropTypes.any, // the value for the radio button
},
getInitialState: function() {
return {
value: SettingsStore.getValueAt(
this.props.level,
this.props.name,
this.props.roomId,
this.props.isExplicit,
),
};
},
onChange: function(e) {
if (this.props.group && !e.target.checked) return;
const newState = this.props.group ? this.props.value : e.target.checked;
if (!this.props.manualSave) this.save(newState);
else this.setState({ value: newState });
if (this.props.onChange) this.props.onChange(newState);
},
save: function(val = undefined) {
return SettingsStore.setValue(
this.props.name,
this.props.roomId,
this.props.level,
val !== undefined ? val : this.state.value,
);
},
render: function() {
const value = this.props.manualSave ? this.state.value : SettingsStore.getValueAt(
this.props.level,
this.props.name,
this.props.roomId,
this.props.isExplicit,
);
const canChange = SettingsStore.canSetValue(this.props.name, this.props.roomId, this.props.level);
let label = this.props.label;
if (!label) label = SettingsStore.getDisplayName(this.props.name, this.props.level);
else label = _t(label);
// We generate a relatively complex ID to avoid conflicts
const id = this.props.name + "_" + this.props.group + "_" + this.props.value + "_" + this.props.level;
let checkbox = (
<input id={id}
type="checkbox"
defaultChecked={value}
onChange={this.onChange}
disabled={!canChange}
/>
);
if (this.props.group) {
checkbox = (
<input id={id}
type="radio"
name={this.props.group}
value={this.props.value}
checked={value === this.props.value}
onChange={this.onChange}
disabled={!canChange}
/>
);
}
return (
<label>
{ checkbox }
{ label }
</label>
);
},
});

View file

@ -0,0 +1,59 @@
/*
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.
*/
'use strict';
import SettingsStore from "../../../settings/SettingsStore";
const React = require('react');
module.exports = React.createClass({
displayName: 'LoginPage',
render: function() {
// FIXME: this should be turned into a proper skin with a StatusLoginPage component
if (SettingsStore.getValue("theme") === 'status') {
return (
<div className="mx_StatusLogin">
<div className="mx_StatusLogin_brand">
<img src="themes/status/img/logo.svg" alt="Status" width="221" height="53" />
</div>
<div className="mx_StatusLogin_content">
<div className="mx_StatusLogin_header">
<h1>Status Community Chat</h1>
<div className="mx_StatusLogin_subtitle">
A safer, decentralised communication
platform <a href="https://riot.im">powered by Riot</a>
</div>
</div>
{ this.props.children }
<div className="mx_StatusLogin_footer">
<p>This channel is for our development community.</p>
<p>Interested in SNT and discussions on the cryptocurrency market?</p>
<p><a href="https://t.me/StatusNetworkChat" target="_blank" className="mx_StatusLogin_footer_cta">Join Telegram Chat</a></p>
</div>
</div>
</div>
);
} else {
return (
<div className="mx_Login">
{ this.props.children }
</div>
);
}
},
});

View file

@ -20,7 +20,7 @@ import classNames from 'classnames';
import sdk from '../../../index';
import { _t } from '../../../languageHandler';
import {field_input_incorrect} from '../../../UiEffects';
import SdkConfig from '../../../SdkConfig';
/**
* A pure UI component which displays a username/password form.
@ -144,7 +144,10 @@ class PasswordLogin extends React.Component {
type="text"
name="username" // make it a little easier for browser's remember-password
onChange={this.onUsernameChanged}
placeholder={_t('User name')}
placeholder={ SdkConfig.get().disable_custom_urls ?
_t("Username on %(hs)s", {
hs: this.props.hsUrl.replace(/^https?:\/\//, '')
}) : _t("User name")}
value={this.state.username}
autoFocus
disabled={disabled}
@ -210,9 +213,9 @@ class PasswordLogin extends React.Component {
const loginField = this.renderLoginField(this.state.loginType, matrixIdText === '');
return (
<div>
<form onSubmit={this.onSubmitForm}>
let loginType;
if (!SdkConfig.get().disable_3pid_login) {
loginType = (
<div className="mx_Login_type_container">
<label className="mx_Login_type_label">{ _t('Sign in with') }</label>
<Dropdown
@ -225,6 +228,13 @@ class PasswordLogin extends React.Component {
<span key={PasswordLogin.LOGIN_FIELD_PHONE}>{ _t('Phone') }</span>
</Dropdown>
</div>
);
}
return (
<div>
<form onSubmit={this.onSubmitForm}>
{ loginType }
{ loginField }
<input className={pwFieldClass} ref={(e) => {this._passwordField = e;}} type="password"
name="password"

View file

@ -22,6 +22,8 @@ import Email from '../../../email';
import { looksValid as phoneNumberLooksValid } from '../../../phonenumber';
import Modal from '../../../Modal';
import { _t } from '../../../languageHandler';
import SdkConfig from '../../../SdkConfig';
import SettingsStore from "../../../settings/SettingsStore";
const FIELD_EMAIL = 'field_email';
const FIELD_PHONE_COUNTRY = 'field_phone_country';
@ -122,7 +124,7 @@ module.exports = React.createClass({
password: this.refs.password.value.trim(),
email: email,
phoneCountry: this.state.phoneCountry,
phoneNumber: this.refs.phoneNumber.value.trim(),
phoneNumber: this.refs.phoneNumber ? this.refs.phoneNumber.value.trim() : '',
});
if (promise) {
@ -180,7 +182,7 @@ module.exports = React.createClass({
this.markFieldValid(field_id, emailValid, "RegistrationForm.ERR_EMAIL_INVALID");
break;
case FIELD_PHONE_NUMBER:
const phoneNumber = this.refs.phoneNumber.value;
const phoneNumber = this.refs.phoneNumber ? this.refs.phoneNumber.value : '';
const phoneNumberValid = phoneNumber === '' || phoneNumberLooksValid(phoneNumber);
this.markFieldValid(field_id, phoneNumberValid, "RegistrationForm.ERR_PHONE_NUMBER_INVALID");
break;
@ -273,10 +275,14 @@ module.exports = React.createClass({
render: function() {
const self = this;
const theme = SettingsStore.getValue("theme");
// FIXME: remove hardcoded Status team tweaks at some point
const emailPlaceholder = theme === 'status' ? _t("Email address") : _t("Email address (optional)");
const emailSection = (
<div>
<input type="text" ref="email"
autoFocus={true} placeholder={_t("Email address (optional)")}
autoFocus={true} placeholder={ emailPlaceholder }
defaultValue={this.props.defaultEmail}
className={this._classForField(FIELD_EMAIL, 'mx_Login_field')}
onBlur={function() {self.validateField(FIELD_EMAIL);}}
@ -306,28 +312,31 @@ module.exports = React.createClass({
}
const CountryDropdown = sdk.getComponent('views.login.CountryDropdown');
const phoneSection = (
<div className="mx_Login_phoneSection">
<CountryDropdown ref="phone_country" onOptionChange={this._onPhoneCountryChange}
className="mx_Login_phoneCountry mx_Login_field_prefix"
value={this.state.phoneCountry}
isSmall={true}
showPrefix={true}
/>
<input type="text" ref="phoneNumber"
placeholder={_t("Mobile phone number (optional)")}
defaultValue={this.props.defaultPhoneNumber}
className={this._classForField(
FIELD_PHONE_NUMBER,
'mx_Login_phoneNumberField',
'mx_Login_field',
'mx_Login_field_has_prefix',
)}
onBlur={function() {self.validateField(FIELD_PHONE_NUMBER);}}
value={self.state.phoneNumber}
/>
</div>
);
let phoneSection;
if (!SdkConfig.get().disable_3pid_login) {
phoneSection = (
<div className="mx_Login_phoneSection">
<CountryDropdown ref="phone_country" onOptionChange={this._onPhoneCountryChange}
className="mx_Login_phoneCountry mx_Login_field_prefix"
value={this.state.phoneCountry}
isSmall={true}
showPrefix={true}
/>
<input type="text" ref="phoneNumber"
placeholder={_t("Mobile phone number (optional)")}
defaultValue={this.props.defaultPhoneNumber}
className={this._classForField(
FIELD_PHONE_NUMBER,
'mx_Login_phoneNumberField',
'mx_Login_field',
'mx_Login_field_has_prefix',
)}
onBlur={function() {self.validateField(FIELD_PHONE_NUMBER);}}
value={self.state.phoneNumber}
/>
</div>
);
}
const registerButton = (
<input className="mx_Login_submit" type="submit" value={_t("Register")} />

View file

@ -25,8 +25,8 @@ import sdk from '../../../index';
import dis from '../../../dispatcher';
import { decryptFile, readBlobAsDataUri } from '../../../utils/DecryptFile';
import Promise from 'bluebird';
import UserSettingsStore from '../../../UserSettingsStore';
import { _t } from '../../../languageHandler';
import SettingsStore from "../../../settings/SettingsStore";
module.exports = React.createClass({
displayName: 'MImageBody',
@ -81,7 +81,7 @@ module.exports = React.createClass({
},
onImageEnter: function(e) {
if (!this._isGif() || UserSettingsStore.getSyncedSetting("autoplayGifsAndVideos", false)) {
if (!this._isGif() || SettingsStore.getValue("autoplayGifsAndVideos")) {
return;
}
const imgElement = e.target;
@ -89,7 +89,7 @@ module.exports = React.createClass({
},
onImageLeave: function(e) {
if (!this._isGif() || UserSettingsStore.getSyncedSetting("autoplayGifsAndVideos", false)) {
if (!this._isGif() || SettingsStore.getValue("autoplayGifsAndVideos")) {
return;
}
const imgElement = e.target;
@ -218,7 +218,7 @@ module.exports = React.createClass({
const contentUrl = this._getContentUrl();
let thumbUrl;
if (this._isGif() && UserSettingsStore.getSyncedSetting("autoplayGifsAndVideos", false)) {
if (this._isGif() && SettingsStore.getValue("autoplayGifsAndVideos")) {
thumbUrl = contentUrl;
} else {
thumbUrl = this._getThumbUrl();

View file

@ -21,8 +21,8 @@ import MFileBody from './MFileBody';
import MatrixClientPeg from '../../../MatrixClientPeg';
import { decryptFile, readBlobAsDataUri } from '../../../utils/DecryptFile';
import Promise from 'bluebird';
import UserSettingsStore from '../../../UserSettingsStore';
import { _t } from '../../../languageHandler';
import SettingsStore from "../../../settings/SettingsStore";
module.exports = React.createClass({
displayName: 'MVideoBody',
@ -151,7 +151,7 @@ module.exports = React.createClass({
const contentUrl = this._getContentUrl();
const thumbUrl = this._getThumbUrl();
const autoplay = UserSettingsStore.getSyncedSetting("autoplayGifsAndVideos", false);
const autoplay = SettingsStore.getValue("autoplayGifsAndVideos");
let height = null;
let width = null;
let poster = null;

View file

@ -29,11 +29,11 @@ import Modal from '../../../Modal';
import SdkConfig from '../../../SdkConfig';
import dis from '../../../dispatcher';
import { _t } from '../../../languageHandler';
import UserSettingsStore from "../../../UserSettingsStore";
import MatrixClientPeg from '../../../MatrixClientPeg';
import ContextualMenu from '../../structures/ContextualMenu';
import {RoomMember} from 'matrix-js-sdk';
import classNames from 'classnames';
import SettingsStore from "../../../settings/SettingsStore";
import PushProcessor from 'matrix-js-sdk/lib/pushprocessor';
linkifyMatrix(linkify);
@ -104,7 +104,7 @@ module.exports = React.createClass({
setTimeout(() => {
if (this._unmounted) return;
for (let i = 0; i < blocks.length; i++) {
if (UserSettingsStore.getSyncedSetting("enableSyntaxHighlightLanguageDetection", false)) {
if (SettingsStore.getValue("enableSyntaxHighlightLanguageDetection")) {
highlight.highlightBlock(blocks[i]);
} else {
// Only syntax highlight if there's a class starting with language-
@ -169,7 +169,7 @@ module.exports = React.createClass({
},
pillifyLinks: function(nodes) {
const shouldShowPillAvatar = !UserSettingsStore.getSyncedSetting("Pill.shouldHidePillAvatar", false);
const shouldShowPillAvatar = !SettingsStore.getValue("Pill.shouldHidePillAvatar");
let node = nodes[0];
while (node) {
let pillified = false;
@ -419,7 +419,7 @@ module.exports = React.createClass({
const content = mxEvent.getContent();
let body = HtmlUtils.bodyToHtml(content, this.props.highlights, {
disableBigEmoji: UserSettingsStore.getSyncedSetting('TextualBody.disableBigEmoji', false),
disableBigEmoji: SettingsStore.getValue('TextualBody.disableBigEmoji'),
});
if (this.props.highlightLink) {

View file

@ -22,10 +22,11 @@ const MatrixClientPeg = require("../../../MatrixClientPeg");
const Modal = require("../../../Modal");
import dis from '../../../dispatcher';
import SettingsStore, {SettingLevel} from "../../../settings/SettingsStore";
const ROOM_COLORS = [
// magic room default values courtesy of Ribot
["#76cfa6", "#eaf5f0"],
[Tinter.getKeyRgb()[0], Tinter.getKeyRgb()[1]],
["#81bddb", "#eaf1f4"],
["#bd79cb", "#f3eaf5"],
["#c65d94", "#f5eaef"],
@ -47,17 +48,17 @@ module.exports = React.createClass({
getInitialState: function() {
const data = {
index: 0,
primary_color: ROOM_COLORS[0].primary_color,
secondary_color: ROOM_COLORS[0].secondary_color,
primary_color: ROOM_COLORS[0][0],
secondary_color: ROOM_COLORS[0][1],
hasChanged: false,
};
const event = this.props.room.getAccountData("org.matrix.room.color_scheme");
if (!event) {
return data;
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;
}
const scheme = event.getContent();
data.primary_color = scheme.primary_color;
data.secondary_color = scheme.secondary_color;
data.index = this._getColorIndex(data);
if (data.index === -1) {
@ -81,13 +82,13 @@ module.exports = React.createClass({
// 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.
return MatrixClientPeg.get().setRoomAccountData(
this.props.room.roomId, "org.matrix.room.color_scheme", {
primary_color: this.state.primary_color,
secondary_color: this.state.secondary_color,
},
).catch(function(err) {
if (err.errcode == 'M_GUEST_ACCESS_FORBIDDEN') {
// 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: 'view_set_mxid'});
}
});

View file

@ -1,5 +1,6 @@
/*
Copyright 2016 OpenMarket Ltd
Copyright 2017 Travis Ralston
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -14,13 +15,10 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import Promise from 'bluebird';
const React = require('react');
const MatrixClientPeg = require('../../../MatrixClientPeg');
const sdk = require("../../../index");
const Modal = require("../../../Modal");
const UserSettingsStore = require('../../../UserSettingsStore');
import { _t, _tJsx } from '../../../languageHandler';
import SettingsStore, {SettingLevel} from "../../../settings/SettingsStore";
module.exports = React.createClass({
@ -30,137 +28,64 @@ module.exports = React.createClass({
room: React.PropTypes.object,
},
getInitialState: function() {
const cli = MatrixClientPeg.get();
const roomState = this.props.room.currentState;
const roomPreviewUrls = this.props.room.currentState.getStateEvents('org.matrix.room.preview_urls', '');
const userPreviewUrls = this.props.room.getAccountData("org.matrix.room.preview_urls");
return {
globalDisableUrlPreview: (roomPreviewUrls && roomPreviewUrls.getContent().disable) || false,
userDisableUrlPreview: (userPreviewUrls && (userPreviewUrls.getContent().disable === true)) || false,
userEnableUrlPreview: (userPreviewUrls && (userPreviewUrls.getContent().disable === false)) || false,
};
},
componentDidMount: function() {
this.originalState = Object.assign({}, this.state);
},
saveSettings: function() {
const promises = [];
if (this.state.globalDisableUrlPreview !== this.originalState.globalDisableUrlPreview) {
console.log("UrlPreviewSettings: Updating room's preview_urls state event");
promises.push(
MatrixClientPeg.get().sendStateEvent(
this.props.room.roomId, "org.matrix.room.preview_urls", {
disable: this.state.globalDisableUrlPreview,
}, "",
),
);
}
let content = undefined;
if (this.state.userDisableUrlPreview !== this.originalState.userDisableUrlPreview) {
console.log("UrlPreviewSettings: Disabling user's per-room preview_urls");
content = this.state.userDisableUrlPreview ? { disable: true } : {};
}
if (this.state.userEnableUrlPreview !== this.originalState.userEnableUrlPreview) {
console.log("UrlPreviewSettings: Enabling user's per-room preview_urls");
if (!content || content.disable === undefined) {
content = this.state.userEnableUrlPreview ? { disable: false } : {};
}
}
if (content) {
promises.push(
MatrixClientPeg.get().setRoomAccountData(
this.props.room.roomId, "org.matrix.room.preview_urls", content,
),
);
}
console.log("UrlPreviewSettings: saveSettings: " + JSON.stringify(promises));
if (this.refs.urlPreviewsRoom) promises.push(this.refs.urlPreviewsRoom.save());
if (this.refs.urlPrviewsSelf) promises.push(this.refs.urlPreviewsSelf.save());
return promises;
},
onGlobalDisableUrlPreviewChange: function() {
this.setState({
globalDisableUrlPreview: this.refs.globalDisableUrlPreview.checked ? true : false,
});
},
onUserEnableUrlPreviewChange: function() {
this.setState({
userDisableUrlPreview: false,
userEnableUrlPreview: this.refs.userEnableUrlPreview.checked ? true : false,
});
},
onUserDisableUrlPreviewChange: function() {
this.setState({
userDisableUrlPreview: this.refs.userDisableUrlPreview.checked ? true : false,
userEnableUrlPreview: false,
});
},
render: function() {
const self = this;
const roomState = this.props.room.currentState;
const cli = MatrixClientPeg.get();
const SettingsFlag = sdk.getComponent("elements.SettingsFlag");
const roomId = this.props.room.roomId;
const maySetRoomPreviewUrls = roomState.mayClientSendStateEvent('org.matrix.room.preview_urls', cli);
let disableRoomPreviewUrls;
if (maySetRoomPreviewUrls) {
disableRoomPreviewUrls =
<label>
<input type="checkbox" ref="globalDisableUrlPreview"
onChange={this.onGlobalDisableUrlPreviewChange}
checked={this.state.globalDisableUrlPreview} />
{ _t("Disable URL previews by default for participants in this room") }
</label>;
} else {
disableRoomPreviewUrls =
<label>
{ _t("URL previews are %(globalDisableUrlPreview)s by default for participants in this room.", {globalDisableUrlPreview: this.state.globalDisableUrlPreview ? _t("disabled") : _t("enabled")}) }
</label>;
}
let urlPreviewText = null;
if (UserSettingsStore.getUrlPreviewsDisabled()) {
urlPreviewText = (
_tJsx("You have <a>disabled</a> URL previews by default.", /<a>(.*?)<\/a>/, (sub)=><a href="#/settings">{ sub }</a>)
);
} else {
urlPreviewText = (
let previewsForAccount = null;
if (SettingsStore.getValueAt(SettingLevel.ACCOUNT, "urlPreviewsEnabled")) {
previewsForAccount = (
_tJsx("You have <a>enabled</a> URL previews by default.", /<a>(.*?)<\/a>/, (sub)=><a href="#/settings">{ sub }</a>)
);
} else {
previewsForAccount = (
_tJsx("You have <a>disabled</a> URL previews by default.", /<a>(.*?)<\/a>/, (sub)=><a href="#/settings">{ sub }</a>)
);
}
let previewsForRoom = null;
if (SettingsStore.canSetValue("urlPreviewsEnabled", roomId, "room")) {
previewsForRoom = (
<label>
<SettingsFlag name="urlPreviewsEnabled"
level={SettingLevel.ROOM}
roomId={this.props.room.roomId}
isExplicit={true}
manualSave={true}
ref="urlPreviewsRoom" />
</label>
);
} else {
let str = "URL previews are enabled by default for participants in this room.";
if (!SettingsStore.getValueAt(SettingLevel.ROOM, "urlPreviewsEnabled")) {
str = "URL previews are disabled by default for participants in this room.";
}
previewsForRoom = (<label>{ _t(str) }</label>);
}
let previewsForRoomAccount = (
<SettingsFlag name="urlPreviewsEnabled"
level={SettingLevel.ROOM_ACCOUNT}
roomId={this.props.room.roomId}
manualSave={true}
ref="urlPreviewsSelf"
/>
);
return (
<div className="mx_RoomSettings_toggles">
<h3>{ _t("URL Previews") }</h3>
<label>
{ urlPreviewText }
</label>
{ disableRoomPreviewUrls }
<label>
<input type="checkbox" ref="userEnableUrlPreview"
onChange={this.onUserEnableUrlPreviewChange}
checked={this.state.userEnableUrlPreview} />
{ _t("Enable URL previews for this room (affects only you)") }
</label>
<label>
<input type="checkbox" ref="userDisableUrlPreview"
onChange={this.onUserDisableUrlPreviewChange}
checked={this.state.userDisableUrlPreview} />
{ _t("Disable URL previews for this room (affects only you)") }
</label>
<label>{ previewsForAccount }</label>
{ previewsForRoom }
<label>{ previewsForRoomAccount }</label>
</div>
);
},

View file

@ -24,9 +24,10 @@ import isEqual from 'lodash/isEqual';
import sdk from '../../../index';
import type {Completion} from '../../../autocomplete/Autocompleter';
import Promise from 'bluebird';
import UserSettingsStore from '../../../UserSettingsStore';
import { Room } from 'matrix-js-sdk';
import {getCompletions} from '../../../autocomplete/Autocompleter';
import SettingsStore from "../../../settings/SettingsStore";
import Autocompleter from '../../../autocomplete/Autocompleter';
const COMPOSER_SELECTED = 0;
@ -95,7 +96,7 @@ export default class Autocomplete extends React.Component {
});
return Promise.resolve(null);
}
let autocompleteDelay = UserSettingsStore.getLocalSetting('autocompleteDelay', 200);
let autocompleteDelay = SettingsStore.getValue("autocompleteDelay");
// Don't debounce if we are already showing completions
if (this.state.completions.length > 0 || this.state.forceComplete) {

View file

@ -22,7 +22,6 @@ import dis from "../../../dispatcher";
import ObjectUtils from '../../../ObjectUtils';
import AppsDrawer from './AppsDrawer';
import { _t, _tJsx} from '../../../languageHandler';
import UserSettingsStore from '../../../UserSettingsStore';
module.exports = React.createClass({

View file

@ -494,7 +494,6 @@ module.exports = withMatrixClient(React.createClass({
const defaultPerms = {
can: {},
muted: false,
modifyLevel: false,
};
const room = this.props.matrixClient.getRoom(member.roomId);
if (!room) return defaultPerms;
@ -516,13 +515,15 @@ module.exports = withMatrixClient(React.createClass({
},
_calculateCanPermissions: function(me, them, powerLevels) {
const isMe = me.userId === them.userId;
const can = {
kick: false,
ban: false,
mute: false,
modifyLevel: false,
modifyLevelMax: 0,
};
const canAffectUser = them.powerLevel < me.powerLevel;
const canAffectUser = them.powerLevel < me.powerLevel || isMe;
if (!canAffectUser) {
//console.log("Cannot affect user: %s >= %s", them.powerLevel, me.powerLevel);
return can;
@ -531,16 +532,13 @@ module.exports = withMatrixClient(React.createClass({
(powerLevels.events ? powerLevels.events["m.room.power_levels"] : null) ||
powerLevels.state_default
);
const levelToSend = (
(powerLevels.events ? powerLevels.events["m.room.message"] : null) ||
powerLevels.events_default
);
can.kick = me.powerLevel >= powerLevels.kick;
can.ban = me.powerLevel >= powerLevels.ban;
can.mute = me.powerLevel >= editPowerLevel;
can.toggleMod = me.powerLevel > them.powerLevel && them.powerLevel >= levelToSend;
can.modifyLevel = me.powerLevel > them.powerLevel && me.powerLevel >= editPowerLevel;
can.modifyLevel = me.powerLevel >= editPowerLevel && (isMe || me.powerLevel > them.powerLevel);
can.modifyLevelMax = me.powerLevel;
return can;
},
@ -832,8 +830,11 @@ module.exports = withMatrixClient(React.createClass({
presenceCurrentlyActive = this.props.member.user.currentlyActive;
}
let roomMemberDetails = null;
const room = this.props.matrixClient.getRoom(this.props.member.roomId);
const poweLevelEvent = room ? room.currentState.getStateEvents("m.room.power_levels", "") : null;
const powerLevelUsersDefault = poweLevelEvent.getContent().users_default;
let roomMemberDetails = null;
if (this.props.member.roomId) { // is in room
const PowerSelector = sdk.getComponent('elements.PowerSelector');
const PresenceLabel = sdk.getComponent('rooms.PresenceLabel');
@ -842,7 +843,9 @@ module.exports = withMatrixClient(React.createClass({
{ _t("Level:") } <b>
<PowerSelector controlled={true}
value={parseInt(this.props.member.powerLevel)}
maxValue={this.state.can.modifyLevelMax}
disabled={!this.state.can.modifyLevel}
usersDefault={powerLevelUsersDefault}
onChange={this.onPowerChange} />
</b>
</div>

View file

@ -22,7 +22,7 @@ import Modal from '../../../Modal';
import sdk from '../../../index';
import dis from '../../../dispatcher';
import Autocomplete from './Autocomplete';
import UserSettingsStore from '../../../UserSettingsStore';
import SettingsStore, {SettingLevel} from "../../../settings/SettingsStore";
export default class MessageComposer extends React.Component {
@ -49,10 +49,10 @@ export default class MessageComposer extends React.Component {
inputState: {
style: [],
blockType: null,
isRichtextEnabled: UserSettingsStore.getSyncedSetting('MessageComposerInput.isRichTextEnabled', false),
isRichtextEnabled: SettingsStore.getValue('MessageComposerInput.isRichTextEnabled'),
wordCount: 0,
},
showFormatting: UserSettingsStore.getSyncedSetting('MessageComposer.showFormatting', false),
showFormatting: SettingsStore.getValue('MessageComposer.showFormatting'),
};
}
@ -226,7 +226,7 @@ export default class MessageComposer extends React.Component {
}
onToggleFormattingClicked() {
UserSettingsStore.setSyncedSetting('MessageComposer.showFormatting', !this.state.showFormatting);
SettingsStore.setValue("MessageComposer.showFormatting", null, SettingLevel.DEVICE, !this.state.showFormatting);
this.setState({showFormatting: !this.state.showFormatting});
}
@ -238,7 +238,7 @@ export default class MessageComposer extends React.Component {
render() {
const me = this.props.room.getMember(MatrixClientPeg.get().credentials.userId);
const uploadInputStyle = {display: 'none'};
const MemberAvatar = sdk.getComponent('avatars.MemberAvatar');
const MemberPresenceAvatar = sdk.getComponent('avatars.MemberPresenceAvatar');
const TintableSvg = sdk.getComponent("elements.TintableSvg");
const MessageComposerInput = sdk.getComponent("rooms.MessageComposerInput");
@ -246,7 +246,7 @@ export default class MessageComposer extends React.Component {
controls.push(
<div key="controls_avatar" className="mx_MessageComposer_avatar">
<MemberAvatar member={me} width={24} height={24} />
<MemberPresenceAvatar member={me} width={24} height={24} />
</div>,
);

View file

@ -35,7 +35,6 @@ import { _t, _td } from '../../../languageHandler';
import Analytics from '../../../Analytics';
import dis from '../../../dispatcher';
import UserSettingsStore from '../../../UserSettingsStore';
import * as RichText from '../../../RichText';
import * as HtmlUtils from '../../../HtmlUtils';
@ -50,6 +49,7 @@ const REGEX_MATRIXTO = new RegExp(MATRIXTO_URL_PATTERN);
const REGEX_MATRIXTO_MARKDOWN_GLOBAL = new RegExp(MATRIXTO_MD_LINK_PATTERN, 'g');
import {asciiRegexp, shortnameToUnicode, emojioneList, asciiList, mapUnicodeToShort} from 'emojione';
import SettingsStore, {SettingLevel} from "../../../settings/SettingsStore";
const EMOJI_SHORTNAMES = Object.keys(emojioneList);
const EMOJI_UNICODE_TO_SHORTNAME = mapUnicodeToShort();
const REGEX_EMOJI_WHITESPACE = new RegExp('(?:^|\\s)(' + asciiRegexp + ')\\s$');
@ -158,7 +158,7 @@ export default class MessageComposerInput extends React.Component {
this.onMarkdownToggleClicked = this.onMarkdownToggleClicked.bind(this);
this.onTextPasted = this.onTextPasted.bind(this);
const isRichtextEnabled = UserSettingsStore.getSyncedSetting('MessageComposerInput.isRichTextEnabled', false);
const isRichtextEnabled = SettingsStore.getValue('MessageComposerInput.isRichTextEnabled');
Analytics.setRichtextMode(isRichtextEnabled);
@ -209,7 +209,7 @@ export default class MessageComposerInput extends React.Component {
createEditorState(richText: boolean, contentState: ?ContentState): EditorState {
const decorators = richText ? RichText.getScopedRTDecorators(this.props) :
RichText.getScopedMDDecorators(this.props);
const shouldShowPillAvatar = !UserSettingsStore.getSyncedSetting("Pill.shouldHidePillAvatar", false);
const shouldShowPillAvatar = !SettingsStore.getValue("Pill.shouldHidePillAvatar");
decorators.push({
strategy: this.findPillEntities.bind(this),
component: (entityProps) => {
@ -377,7 +377,7 @@ export default class MessageComposerInput extends React.Component {
}
sendTyping(isTyping) {
if (UserSettingsStore.getSyncedSetting('dontSendTypingNotifications', false)) return;
if (SettingsStore.getValue('dontSendTypingNotifications')) return;
MatrixClientPeg.get().sendTyping(
this.props.room.roomId,
this.isTyping, TYPING_SERVER_TIMEOUT,
@ -424,7 +424,7 @@ export default class MessageComposerInput extends React.Component {
}
// Automatic replacement of plaintext emoji to Unicode emoji
if (UserSettingsStore.getSyncedSetting('MessageComposerInput.autoReplaceEmoji', false)) {
if (SettingsStore.getValue('MessageComposerInput.autoReplaceEmoji')) {
// The first matched group includes just the matched plaintext emoji
const emojiMatch = REGEX_EMOJI_WHITESPACE.exec(text.slice(0, currentStartOffset));
if(emojiMatch) {
@ -544,7 +544,7 @@ export default class MessageComposerInput extends React.Component {
editorState: this.createEditorState(enabled, contentState),
isRichtextEnabled: enabled,
});
UserSettingsStore.setSyncedSetting('MessageComposerInput.isRichTextEnabled', enabled);
SettingsStore.setValue("MessageComposerInput.isRichTextEnabled", null, SettingLevel.ACCOUNT, enabled);
}
handleKeyCommand = (command: string): boolean => {

View file

@ -31,7 +31,7 @@ import linkifyMatrix from '../../../linkify-matrix';
import AccessibleButton from '../elements/AccessibleButton';
import ManageIntegsButton from '../elements/ManageIntegsButton';
import {CancelButton} from './SimpleRoomHeader';
import UserSettingsStore from "../../../UserSettingsStore";
import SettingsStore from "../../../settings/SettingsStore";
linkifyMatrix(linkify);
@ -339,7 +339,7 @@ module.exports = React.createClass({
</AccessibleButton>;
}
if (this.props.onPinnedClick && UserSettingsStore.isFeatureEnabled('feature_pinning')) {
if (this.props.onPinnedClick && SettingsStore.isFeatureEnabled('feature_pinning')) {
let pinsIndicator = null;
if (this._hasUnreadPins()) {
pinsIndicator = (<div className="mx_RoomHeader_pinsIndicator mx_RoomHeader_pinsIndicatorUnread" />);

View file

@ -23,8 +23,8 @@ import sdk from '../../../index';
import Modal from '../../../Modal';
import ObjectUtils from '../../../ObjectUtils';
import dis from '../../../dispatcher';
import UserSettingsStore from '../../../UserSettingsStore';
import AccessibleButton from '../elements/AccessibleButton';
import SettingsStore, {SettingLevel} from "../../../settings/SettingsStore";
// parse a string as an integer; if the input is undefined, or cannot be parsed
@ -309,9 +309,9 @@ module.exports = React.createClass({
}
// url preview settings
const ps = this.saveUrlPreviewSettings();
let ps = this.saveUrlPreviewSettings();
if (ps.length > 0) {
promises.push(ps);
ps.map(p => promises.push(p));
}
// related groups
@ -363,26 +363,16 @@ module.exports = React.createClass({
},
saveBlacklistUnverifiedDevicesPerRoom: function() {
if (!this.refs.blacklistUnverified) return;
if (this._isRoomBlacklistUnverified() !== this.refs.blacklistUnverified.checked) {
this._setRoomBlacklistUnverified(this.refs.blacklistUnverified.checked);
}
},
_isRoomBlacklistUnverified: function() {
const blacklistUnverifiedDevicesPerRoom = UserSettingsStore.getLocalSettings().blacklistUnverifiedDevicesPerRoom;
if (blacklistUnverifiedDevicesPerRoom) {
return blacklistUnverifiedDevicesPerRoom[this.props.room.roomId];
}
return false;
},
_setRoomBlacklistUnverified: function(value) {
const blacklistUnverifiedDevicesPerRoom = UserSettingsStore.getLocalSettings().blacklistUnverifiedDevicesPerRoom || {};
blacklistUnverifiedDevicesPerRoom[this.props.room.roomId] = value;
UserSettingsStore.setLocalSetting('blacklistUnverifiedDevicesPerRoom', blacklistUnverifiedDevicesPerRoom);
this.props.room.setBlacklistUnverifiedDevices(value);
if (!this.refs.blacklistUnverifiedDevices) return;
this.refs.blacklistUnverifiedDevices.save().then(() => {
const value = SettingsStore.getValueAt(
SettingLevel.ROOM_DEVICE,
"blacklistUnverifiedDevices",
this.props.room.roomId,
/*explicit=*/true,
);
this.props.room.setBlacklistUnverifiedDevices(value);
});
},
_hasDiff: function(strA, strB) {
@ -588,19 +578,20 @@ module.exports = React.createClass({
},
_renderEncryptionSection: function() {
const SettingsFlag = sdk.getComponent("elements.SettingsFlag");
const cli = MatrixClientPeg.get();
const roomState = this.props.room.currentState;
const isEncrypted = cli.isRoomEncrypted(this.props.room.roomId);
const isGlobalBlacklistUnverified = UserSettingsStore.getLocalSettings().blacklistUnverifiedDevices;
const isRoomBlacklistUnverified = this._isRoomBlacklistUnverified();
const settings =
<label>
<input type="checkbox" ref="blacklistUnverified"
defaultChecked={isGlobalBlacklistUnverified || isRoomBlacklistUnverified}
disabled={isGlobalBlacklistUnverified || (this.refs.encrypt && !this.refs.encrypt.checked)} />
{ _t('Never send encrypted messages to unverified devices in this room from this device') }.
</label>;
let settings = (
<SettingsFlag name="blacklistUnverifiedDevices"
level={SettingLevel.ROOM_DEVICE}
roomId={this.props.room.roomId}
manualSave={true}
ref="blacklistUnverifiedDevices"
/>
);
if (!isEncrypted && roomState.mayClientSendStateEvent("m.room.encryption", cli)) {
return (
@ -910,31 +901,31 @@ module.exports = React.createClass({
<div className="mx_RoomSettings_powerLevels mx_RoomSettings_settings">
<div className="mx_RoomSettings_powerLevel">
<span className="mx_RoomSettings_powerLevelKey">{ _t('The default role for new room members is') } </span>
<PowerSelector ref="users_default" value={default_user_level} controlled={false} disabled={!can_change_levels || current_user_level < default_user_level} onChange={this.onPowerLevelsChanged} />
<PowerSelector ref="users_default" value={default_user_level} usersDefault={default_user_level} controlled={false} disabled={!can_change_levels || current_user_level < default_user_level} onChange={this.onPowerLevelsChanged} />
</div>
<div className="mx_RoomSettings_powerLevel">
<span className="mx_RoomSettings_powerLevelKey">{ _t('To send messages, you must be a') } </span>
<PowerSelector ref="events_default" value={send_level} controlled={false} disabled={!can_change_levels || current_user_level < send_level} onChange={this.onPowerLevelsChanged} />
<PowerSelector ref="events_default" value={send_level} usersDefault={default_user_level} controlled={false} disabled={!can_change_levels || current_user_level < send_level} onChange={this.onPowerLevelsChanged} />
</div>
<div className="mx_RoomSettings_powerLevel">
<span className="mx_RoomSettings_powerLevelKey">{ _t('To invite users into the room, you must be a') } </span>
<PowerSelector ref="invite" value={invite_level} controlled={false} disabled={!can_change_levels || current_user_level < invite_level} onChange={this.onPowerLevelsChanged} />
<PowerSelector ref="invite" value={invite_level} usersDefault={default_user_level} controlled={false} disabled={!can_change_levels || current_user_level < invite_level} onChange={this.onPowerLevelsChanged} />
</div>
<div className="mx_RoomSettings_powerLevel">
<span className="mx_RoomSettings_powerLevelKey">{ _t('To configure the room, you must be a') } </span>
<PowerSelector ref="state_default" value={state_level} controlled={false} disabled={!can_change_levels || current_user_level < state_level} onChange={this.onPowerLevelsChanged} />
<PowerSelector ref="state_default" value={state_level} usersDefault={default_user_level} controlled={false} disabled={!can_change_levels || current_user_level < state_level} onChange={this.onPowerLevelsChanged} />
</div>
<div className="mx_RoomSettings_powerLevel">
<span className="mx_RoomSettings_powerLevelKey">{ _t('To kick users, you must be a') } </span>
<PowerSelector ref="kick" value={kick_level} controlled={false} disabled={!can_change_levels || current_user_level < kick_level} onChange={this.onPowerLevelsChanged} />
<PowerSelector ref="kick" value={kick_level} usersDefault={default_user_level} controlled={false} disabled={!can_change_levels || current_user_level < kick_level} onChange={this.onPowerLevelsChanged} />
</div>
<div className="mx_RoomSettings_powerLevel">
<span className="mx_RoomSettings_powerLevelKey">{ _t('To ban users, you must be a') } </span>
<PowerSelector ref="ban" value={ban_level} controlled={false} disabled={!can_change_levels || current_user_level < ban_level} onChange={this.onPowerLevelsChanged} />
<PowerSelector ref="ban" value={ban_level} usersDefault={default_user_level} controlled={false} disabled={!can_change_levels || current_user_level < ban_level} onChange={this.onPowerLevelsChanged} />
</div>
<div className="mx_RoomSettings_powerLevel">
<span className="mx_RoomSettings_powerLevelKey">{ _t('To remove other users\' messages, you must be a') } </span>
<PowerSelector ref="redact" value={redact_level} controlled={false} disabled={!can_change_levels || current_user_level < redact_level} onChange={this.onPowerLevelsChanged} />
<PowerSelector ref="redact" value={redact_level} usersDefault={default_user_level} controlled={false} disabled={!can_change_levels || current_user_level < redact_level} onChange={this.onPowerLevelsChanged} />
</div>
{ Object.keys(events_levels).map(function(event_type, i) {
@ -944,7 +935,7 @@ module.exports = React.createClass({
return (
<div className="mx_RoomSettings_powerLevel" key={event_type}>
<span className="mx_RoomSettings_powerLevelKey">{ label } </span>
<PowerSelector ref={"event_levels_"+event_type} value={events_levels[event_type]} onChange={self.onPowerLevelsChanged}
<PowerSelector ref={"event_levels_"+event_type} value={events_levels[event_type]} usersDefault={default_user_level} onChange={self.onPowerLevelsChanged}
controlled={false} disabled={!can_change_levels || current_user_level < events_levels[event_type]} />
</div>
);

View file

@ -27,7 +27,6 @@ const ContextualMenu = require('../../structures/ContextualMenu');
const RoomNotifs = require('../../../RoomNotifs');
const FormattingUtils = require('../../../utils/FormattingUtils');
import AccessibleButton from '../elements/AccessibleButton';
const UserSettingsStore = require('../../../UserSettingsStore');
import ActiveRoomObserver from '../../../ActiveRoomObserver';
import RoomViewStore from '../../../stores/RoomViewStore';

View file

@ -23,7 +23,7 @@ import classNames from 'classnames';
import sdk from '../../../index';
import dis from '../../../dispatcher';
import UserSettingsStore from '../../../UserSettingsStore';
import SettingsStore from "../../../settings/SettingsStore";
module.exports = React.createClass({
displayName: 'VideoView',
@ -113,7 +113,7 @@ module.exports = React.createClass({
const maxVideoHeight = fullscreenElement ? null : this.props.maxHeight;
const localVideoFeedClasses = classNames("mx_VideoView_localVideoFeed",
{ "mx_VideoView_localVideoFeed_flipped":
UserSettingsStore.getSyncedSetting('VideoView.flipVideoHorizontally', false),
SettingsStore.getValue('VideoView.flipVideoHorizontally'),
},
);
return (