Convert synced settings to granular settings

Signed-off-by: Travis Ralston <travpc@gmail.com>
This commit is contained in:
Travis Ralston 2017-10-29 01:43:52 -06:00
parent bf815f4be9
commit ae10a11ac4
21 changed files with 177 additions and 161 deletions

View file

@ -15,7 +15,6 @@ limitations under the License.
*/ */
const MatrixClientPeg = require('./MatrixClientPeg'); const MatrixClientPeg = require('./MatrixClientPeg');
import UserSettingsStore from './UserSettingsStore';
import shouldHideEvent from './shouldHideEvent'; import shouldHideEvent from './shouldHideEvent';
const sdk = require('./index'); const sdk = require('./index');
@ -64,7 +63,6 @@ module.exports = {
// we have and the read receipt. We could fetch more history to try & find out, // we have and the read receipt. We could fetch more history to try & find out,
// but currently we just guess. // but currently we just guess.
const syncedSettings = UserSettingsStore.getSyncedSettings();
// Loop through messages, starting with the most recent... // Loop through messages, starting with the most recent...
for (let i = room.timeline.length - 1; i >= 0; --i) { for (let i = room.timeline.length - 1; i >= 0; --i) {
const ev = room.timeline[i]; const ev = room.timeline[i];
@ -74,7 +72,7 @@ module.exports = {
// that counts and we can stop looking because the user's read // that counts and we can stop looking because the user's read
// this and everything before. // this and everything before.
return false; return false;
} else if (!shouldHideEvent(ev, syncedSettings) && this.eventTriggersUnreadCount(ev)) { } else if (!shouldHideEvent(ev) && this.eventTriggersUnreadCount(ev)) {
// We've found a message that counts before we hit // We've found a message that counts before we hit
// the read marker, so this room is definitely unread. // the read marker, so this room is definitely unread.
return true; return true;

View file

@ -137,23 +137,6 @@ export default {
}); });
}, },
getSyncedSettings: function() {
const event = MatrixClientPeg.get().getAccountData('im.vector.web.settings');
return event ? event.getContent() : {};
},
getSyncedSetting: function(type, defaultValue = null) {
const settings = this.getSyncedSettings();
return settings.hasOwnProperty(type) ? settings[type] : defaultValue;
},
setSyncedSetting: function(type, value) {
const settings = this.getSyncedSettings();
settings[type] = value;
// FIXME: handle errors
return MatrixClientPeg.get().setAccountData('im.vector.web.settings', settings);
},
getLocalSettings: function() { getLocalSettings: function() {
const localSettingsString = localStorage.getItem('mx_local_settings') || '{}'; const localSettingsString = localStorage.getItem('mx_local_settings') || '{}';
return JSON.parse(localSettingsString); return JSON.parse(localSettingsString);

View file

@ -25,7 +25,7 @@ import {PillCompletion} from './Components';
import type {SelectionRange, Completion} from './Autocompleter'; import type {SelectionRange, Completion} from './Autocompleter';
import _uniq from 'lodash/uniq'; import _uniq from 'lodash/uniq';
import _sortBy from 'lodash/sortBy'; import _sortBy from 'lodash/sortBy';
import UserSettingsStore from '../UserSettingsStore'; import SettingsStore from "../settings/SettingsStore";
import EmojiData from '../stripped-emoji.json'; import EmojiData from '../stripped-emoji.json';
@ -97,7 +97,7 @@ export default class EmojiProvider extends AutocompleteProvider {
} }
async getCompletions(query: string, selection: SelectionRange) { async getCompletions(query: string, selection: SelectionRange) {
if (UserSettingsStore.getSyncedSetting("MessageComposerInput.dontSuggestEmoji")) { if (SettingsStore.getValue("MessageComposerInput.dontSuggestEmoji")) {
return []; // don't give any suggestions if the user doesn't want them return []; // don't give any suggestions if the user doesn't want them
} }

View file

@ -19,7 +19,6 @@ limitations under the License.
import * as Matrix from 'matrix-js-sdk'; import * as Matrix from 'matrix-js-sdk';
import React from 'react'; import React from 'react';
import UserSettingsStore from '../../UserSettingsStore';
import KeyCode from '../../KeyCode'; import KeyCode from '../../KeyCode';
import Notifier from '../../Notifier'; import Notifier from '../../Notifier';
import PageTypes from '../../PageTypes'; import PageTypes from '../../PageTypes';
@ -28,6 +27,7 @@ import sdk from '../../index';
import dis from '../../dispatcher'; import dis from '../../dispatcher';
import sessionStore from '../../stores/SessionStore'; import sessionStore from '../../stores/SessionStore';
import MatrixClientPeg from '../../MatrixClientPeg'; import MatrixClientPeg from '../../MatrixClientPeg';
import SettingsStore from "../../settings/SettingsStore";
/** /**
* This is what our MatrixChat shows when we are logged in. The precise view is * This is what our MatrixChat shows when we are logged in. The precise view is
@ -74,7 +74,7 @@ export default React.createClass({
getInitialState: function() { getInitialState: function() {
return { return {
// use compact timeline view // use compact timeline view
useCompactLayout: UserSettingsStore.getSyncedSetting('useCompactLayout'), useCompactLayout: SettingsStore.getValue('useCompactLayout'),
}; };
}, },

View file

@ -17,7 +17,6 @@ limitations under the License.
import React from 'react'; import React from 'react';
import ReactDOM from 'react-dom'; import ReactDOM from 'react-dom';
import classNames from 'classnames'; import classNames from 'classnames';
import UserSettingsStore from '../../UserSettingsStore';
import shouldHideEvent from '../../shouldHideEvent'; import shouldHideEvent from '../../shouldHideEvent';
import dis from "../../dispatcher"; import dis from "../../dispatcher";
import sdk from '../../index'; import sdk from '../../index';
@ -110,8 +109,6 @@ module.exports = React.createClass({
// Velocity requires // Velocity requires
this._readMarkerGhostNode = null; this._readMarkerGhostNode = null;
this._syncedSettings = UserSettingsStore.getSyncedSettings();
this._isMounted = true; this._isMounted = true;
}, },
@ -251,7 +248,7 @@ module.exports = React.createClass({
// Always show highlighted event // Always show highlighted event
if (this.props.highlightedEventId === mxEv.getId()) return true; if (this.props.highlightedEventId === mxEv.getId()) return true;
return !shouldHideEvent(mxEv, this._syncedSettings); return !shouldHideEvent(mxEv);
}, },
_getEventTiles: function() { _getEventTiles: function() {

View file

@ -29,7 +29,6 @@ const classNames = require("classnames");
const Matrix = require("matrix-js-sdk"); const Matrix = require("matrix-js-sdk");
import { _t } from '../../languageHandler'; import { _t } from '../../languageHandler';
const UserSettingsStore = require('../../UserSettingsStore');
const MatrixClientPeg = require("../../MatrixClientPeg"); const MatrixClientPeg = require("../../MatrixClientPeg");
const ContentMessages = require("../../ContentMessages"); const ContentMessages = require("../../ContentMessages");
const Modal = require("../../Modal"); const Modal = require("../../Modal");
@ -48,6 +47,7 @@ import UserProvider from '../../autocomplete/UserProvider';
import RoomViewStore from '../../stores/RoomViewStore'; import RoomViewStore from '../../stores/RoomViewStore';
import RoomScrollStateStore from '../../stores/RoomScrollStateStore'; import RoomScrollStateStore from '../../stores/RoomScrollStateStore';
import SettingsStore from "../../settings/SettingsStore";
const DEBUG = false; const DEBUG = false;
let debuglog = function() {}; let debuglog = function() {};
@ -151,8 +151,6 @@ module.exports = React.createClass({
MatrixClientPeg.get().on("RoomMember.membership", this.onRoomMemberMembership); MatrixClientPeg.get().on("RoomMember.membership", this.onRoomMemberMembership);
MatrixClientPeg.get().on("accountData", this.onAccountData); MatrixClientPeg.get().on("accountData", this.onAccountData);
this._syncedSettings = UserSettingsStore.getSyncedSettings();
// Start listening for RoomViewStore updates // Start listening for RoomViewStore updates
this._roomStoreToken = RoomViewStore.addListener(this._onRoomViewStoreUpdate); this._roomStoreToken = RoomViewStore.addListener(this._onRoomViewStoreUpdate);
this._onRoomViewStoreUpdate(true); this._onRoomViewStoreUpdate(true);
@ -535,7 +533,7 @@ module.exports = React.createClass({
// update unread count when scrolled up // update unread count when scrolled up
if (!this.state.searchResults && this.state.atEndOfLiveTimeline) { if (!this.state.searchResults && this.state.atEndOfLiveTimeline) {
// no change // no change
} else if (!shouldHideEvent(ev, this._syncedSettings)) { } else if (!shouldHideEvent(ev)) {
this.setState((state, props) => { this.setState((state, props) => {
return {numUnreadMessages: state.numUnreadMessages + 1}; return {numUnreadMessages: state.numUnreadMessages + 1};
}); });
@ -1778,7 +1776,7 @@ module.exports = React.createClass({
const messagePanel = ( const messagePanel = (
<TimelinePanel ref={this._gatherTimelinePanelRef} <TimelinePanel ref={this._gatherTimelinePanelRef}
timelineSet={this.state.room.getUnfilteredTimelineSet()} timelineSet={this.state.room.getUnfilteredTimelineSet()}
showReadReceipts={!UserSettingsStore.getSyncedSetting('hideReadReceipts', false)} showReadReceipts={!SettingsStore.getValue('hideReadReceipts')}
manageReadReceipts={!this.state.isPeeking} manageReadReceipts={!this.state.isPeeking}
manageReadMarkers={!this.state.isPeeking} manageReadMarkers={!this.state.isPeeking}
hidden={hideMessagePanel} hidden={hideMessagePanel}

View file

@ -15,6 +15,8 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import SettingsStore from "../../settings/SettingsStore";
const React = require('react'); const React = require('react');
const ReactDOM = require("react-dom"); const ReactDOM = require("react-dom");
import Promise from 'bluebird'; import Promise from 'bluebird';
@ -30,7 +32,6 @@ const ObjectUtils = require('../../ObjectUtils');
const Modal = require("../../Modal"); const Modal = require("../../Modal");
const UserActivity = require("../../UserActivity"); const UserActivity = require("../../UserActivity");
const KeyCode = require('../../KeyCode'); const KeyCode = require('../../KeyCode');
import UserSettingsStore from '../../UserSettingsStore';
const PAGINATE_SIZE = 20; const PAGINATE_SIZE = 20;
const INITIAL_SIZE = 20; const INITIAL_SIZE = 20;
@ -129,8 +130,6 @@ var TimelinePanel = React.createClass({
} }
} }
const syncedSettings = UserSettingsStore.getSyncedSettings();
return { return {
events: [], events: [],
timelineLoading: true, // track whether our room timeline is loading timelineLoading: true, // track whether our room timeline is loading
@ -175,10 +174,10 @@ var TimelinePanel = React.createClass({
clientSyncState: MatrixClientPeg.get().getSyncState(), clientSyncState: MatrixClientPeg.get().getSyncState(),
// should the event tiles have twelve hour times // should the event tiles have twelve hour times
isTwelveHour: syncedSettings.showTwelveHourTimestamps, isTwelveHour: SettingsStore.getValue("showTwelveHourTimestamps"),
// always show timestamps on event tiles? // always show timestamps on event tiles?
alwaysShowTimestamps: syncedSettings.alwaysShowTimestamps, alwaysShowTimestamps: SettingsStore.getValue("alwaysShowTimestamps"),
}; };
}, },

View file

@ -58,80 +58,29 @@ const gHVersionLabel = function(repo, token='') {
return <a target="_blank" rel="noopener" href={url}>{ token }</a>; return <a target="_blank" rel="noopener" href={url}>{ token }</a>;
}; };
// Enumerate some simple 'flip a bit' UI settings (if any). // Enumerate some simple 'flip a bit' UI settings (if any). The strings provided here
// 'id' gives the key name in the im.vector.web.settings account data event // must be settings defined in SettingsStore.
// 'label' is how we describe it in the UI. const SIMPLE_SETTINGS = [
// Warning: Each "label" string below must be added to i18n/strings/en_EN.json, { id: "autoplayGifsAndVideos" },
// since they will be translated when rendered. { id: "hideReadReceipts" },
const SETTINGS_LABELS = [ { id: "dontSendTypingNotifications" },
{ { id: "alwaysShowTimestamps" },
id: 'autoplayGifsAndVideos', { id: "showTwelveHourTimestamps" },
label: _td('Autoplay GIFs and videos'), { id: "hideJoinLeaves" },
}, { id: "hideAvatarDisplaynameChanges" },
{ { id: "useCompactLayout" },
id: 'hideReadReceipts', { id: "hideRedactions" },
label: _td('Hide read receipts'), { id: "enableSyntaxHighlightLanguageDetection" },
}, { id: "MessageComposerInput.autoReplaceEmoji" },
{ { id: "MessageComposerInput.dontSuggestEmoji" },
id: 'dontSendTypingNotifications', { id: "Pill.shouldHidePillAvatar" },
label: _td("Don't send typing notifications"), { id: "TextualBody.disableBigEmoji" },
}, { id: "VideoView.flipVideoHorizontally" },
{
id: 'alwaysShowTimestamps',
label: _td('Always show message timestamps'),
},
{
id: 'showTwelveHourTimestamps',
label: _td('Show timestamps in 12 hour format (e.g. 2:30pm)'),
},
{
id: 'hideJoinLeaves',
label: _td('Hide join/leave messages (invites/kicks/bans unaffected)'),
},
{
id: 'hideAvatarDisplaynameChanges',
label: _td('Hide avatar and display name changes'),
},
{
id: 'useCompactLayout',
label: _td('Use compact timeline layout'),
},
{
id: 'hideRedactions',
label: _td('Hide removed messages'),
},
{
id: 'enableSyntaxHighlightLanguageDetection',
label: _td('Enable automatic language detection for syntax highlighting'),
},
{
id: 'MessageComposerInput.autoReplaceEmoji',
label: _td('Automatically replace plain text Emoji'),
},
{
id: 'MessageComposerInput.dontSuggestEmoji',
label: _td('Disable Emoji suggestions while typing'),
},
{
id: 'Pill.shouldHidePillAvatar',
label: _td('Hide avatars in user and room mentions'),
},
{
id: 'TextualBody.disableBigEmoji',
label: _td('Disable big emoji in chat'),
},
{
id: 'VideoView.flipVideoHorizontally',
label: _td('Mirror local video feed'),
},
/*
{
id: 'useFixedWidthFont',
label: 'Use fixed width font',
},
*/
]; ];
// TODO: {Travis} Consider making generic setting handler to support `label` and `fn` optionally (backed by SettingsStore)
// TODO: {Travis} Convert
const ANALYTICS_SETTINGS_LABELS = [ const ANALYTICS_SETTINGS_LABELS = [
{ {
id: 'analyticsOptOut', id: 'analyticsOptOut',
@ -142,6 +91,7 @@ const ANALYTICS_SETTINGS_LABELS = [
}, },
]; ];
// TODO: {Travis} Convert
const WEBRTC_SETTINGS_LABELS = [ const WEBRTC_SETTINGS_LABELS = [
{ {
id: 'webRtcForceTURN', id: 'webRtcForceTURN',
@ -149,6 +99,7 @@ const WEBRTC_SETTINGS_LABELS = [
}, },
]; ];
// TODO: {Travis} Convert
// Warning: Each "label" string below must be added to i18n/strings/en_EN.json, // Warning: Each "label" string below must be added to i18n/strings/en_EN.json,
// since they will be translated when rendered. // since they will be translated when rendered.
const CRYPTO_SETTINGS_LABELS = [ const CRYPTO_SETTINGS_LABELS = [
@ -283,12 +234,6 @@ module.exports = React.createClass({
}); });
this._refreshFromServer(); this._refreshFromServer();
const syncedSettings = UserSettingsStore.getSyncedSettings();
if (!syncedSettings.theme) {
syncedSettings.theme = 'light';
}
this._syncedSettings = syncedSettings;
this._localSettings = UserSettingsStore.getLocalSettings(); this._localSettings = UserSettingsStore.getLocalSettings();
if (PlatformPeg.get().isElectron()) { if (PlatformPeg.get().isElectron()) {
@ -723,7 +668,7 @@ module.exports = React.createClass({
<h3>{ _t("User Interface") }</h3> <h3>{ _t("User Interface") }</h3>
<div className="mx_UserSettings_section"> <div className="mx_UserSettings_section">
{ this._renderUrlPreviewSelector() } { this._renderUrlPreviewSelector() }
{ SETTINGS_LABELS.map( this._renderSyncedSetting ) } { SIMPLE_SETTINGS.map( this._renderSyncedSetting ) }
{ THEMES.map( this._renderThemeSelector ) } { THEMES.map( this._renderThemeSelector ) }
<table> <table>
<tbody> <tbody>
@ -767,18 +712,19 @@ module.exports = React.createClass({
// to rebind the onChange each time we render // to rebind the onChange each time we render
const onChange = (e) => { const onChange = (e) => {
UserSettingsStore.setSyncedSetting(setting.id, e.target.checked); SettingsStore.setValue(setting.id, null, "account", e.target.checked);
if (setting.fn) setting.fn(e.target.checked); if (setting.fn) setting.fn(e.target.checked);
}; };
return <div className="mx_UserSettings_toggle" key={setting.id}> return <div className="mx_UserSettings_toggle" key={setting.id}>
<input id={setting.id} <input id={setting.id}
type="checkbox" type="checkbox"
defaultChecked={this._syncedSettings[setting.id]} // TODO: {Travis} Support atLevel=account
defaultChecked={SettingsStore.getValue(setting.id)}
onChange={onChange} onChange={onChange}
/> />
<label htmlFor={setting.id}> <label htmlFor={setting.id}>
{ _t(setting.label) } { SettingsStore.getDisplayName(setting.id) }
</label> </label>
</div>; </div>;
}, },
@ -788,8 +734,7 @@ module.exports = React.createClass({
// to rebind the onChange each time we render // to rebind the onChange each time we render
const onChange = (e) => { const onChange = (e) => {
if (e.target.checked) { if (e.target.checked) {
this._syncedSettings[setting.id] = setting.value; SettingsStore.setValue(setting.id, null, "account", setting.value);
UserSettingsStore.setSyncedSetting(setting.id, setting.value);
} }
dis.dispatch({ dis.dispatch({
action: 'set_theme', action: 'set_theme',
@ -801,7 +746,8 @@ module.exports = React.createClass({
type="radio" type="radio"
name={setting.id} name={setting.id}
value={setting.value} value={setting.value}
checked={this._syncedSettings[setting.id] === setting.value} // TODO: {Travis} Support atLevel=account
checked={SettingsStore.getValue(setting.id) === setting.value}
onChange={onChange} onChange={onChange}
/> />
<label htmlFor={setting.id + "_" + setting.value}> <label htmlFor={setting.id + "_" + setting.value}>

View file

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

View file

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

View file

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

View file

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

View file

@ -22,7 +22,7 @@ import Modal from '../../../Modal';
import sdk from '../../../index'; import sdk from '../../../index';
import dis from '../../../dispatcher'; import dis from '../../../dispatcher';
import Autocomplete from './Autocomplete'; import Autocomplete from './Autocomplete';
import UserSettingsStore from '../../../UserSettingsStore'; import SettingsStore from "../../../settings/SettingsStore";
export default class MessageComposer extends React.Component { export default class MessageComposer extends React.Component {
@ -49,10 +49,10 @@ export default class MessageComposer extends React.Component {
inputState: { inputState: {
style: [], style: [],
blockType: null, blockType: null,
isRichtextEnabled: UserSettingsStore.getSyncedSetting('MessageComposerInput.isRichTextEnabled', false), isRichtextEnabled: SettingsStore.getValue('MessageComposerInput.isRichTextEnabled'),
wordCount: 0, 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() { onToggleFormattingClicked() {
UserSettingsStore.setSyncedSetting('MessageComposer.showFormatting', !this.state.showFormatting); SettingsStore.setValue("MessageComposer.showFormatting", null, "account", !this.state.showFormatting);
this.setState({showFormatting: !this.state.showFormatting}); this.setState({showFormatting: !this.state.showFormatting});
} }

View file

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

View file

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

View file

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

View file

@ -46,8 +46,7 @@ export default class RoomSettingsHandler extends SettingsHandler {
_getSettings(roomId) { _getSettings(roomId) {
const room = MatrixClientPeg.get().getRoom(roomId); const room = MatrixClientPeg.get().getRoom(roomId);
if (!room) return {}; if (!room) return {};
const event = room.currentState.getStateEvents("im.vector.web.settings", "");
const event = room.currentState.getStateEvents("im.vector.web.settings");
if (!event || !event.getContent()) return {}; if (!event || !event.getContent()) return {};
return event.getContent(); return event.getContent();
} }

View file

@ -38,8 +38,11 @@ const LEVELS_PRESET_FEATURE = ['device'];
const SETTINGS = { const SETTINGS = {
// EXAMPLE SETTING: // EXAMPLE SETTING:
// "my-setting": { // "my-setting": {
// isFeature: false, // optional // // Required by features, optional otherwise
// isFeature: false,
// displayName: _td("Cool Name"), // displayName: _td("Cool Name"),
//
// // Required.
// supportedLevels: [ // supportedLevels: [
// // The order does not matter. // // The order does not matter.
// //
@ -51,6 +54,8 @@ const SETTINGS = {
// //
// // "default" and "config" are always supported and do not get listed here. // // "default" and "config" are always supported and do not get listed here.
// ], // ],
//
// // Optional. Any data type.
// default: { // default: {
// your: "value", // your: "value",
// }, // },
@ -65,8 +70,93 @@ const SETTINGS = {
displayName: _td("Message Pinning"), displayName: _td("Message Pinning"),
supportedLevels: LEVELS_PRESET_FEATURE, supportedLevels: LEVELS_PRESET_FEATURE,
}, },
"MessageComposerInput.dontSuggestEmoji": {
// TODO: Populate this supportedLevels: LEVELS_PRESET_ACCOUNT,
default: false,
displayName: _td('Disable Emoji suggestions while typing'),
},
"useCompactLayout": {
supportedLevels: LEVELS_PRESET_ACCOUNT,
default: false,
displayName: _td('Use compact timeline layout'),
},
"hideRedactions": {
supportedLevels: LEVELS_PRESET_ROOM.concat("room"),
default: false,
displayName: _td('Hide removed messages'),
},
"hideJoinLeaves": {
supportedLevels: LEVELS_PRESET_ROOM.concat("room"),
default: false,
displayName: _td('Hide join/leave messages (invites/kicks/bans unaffected)'),
},
"hideAvatarDisplaynameChanges": {
supportedLevels: LEVELS_PRESET_ROOM.concat("room"),
default: false,
displayName: _td('Hide avatar and display name changes'),
},
"hideReadReceipts": {
supportedLevels: LEVELS_PRESET_ROOM,
default: false,
displayName: _td('Hide read receipts'),
},
"showTwelveHourTimestamps": {
supportedLevels: LEVELS_PRESET_ACCOUNT,
default: false,
displayName: _td('Show timestamps in 12 hour format (e.g. 2:30pm)'),
},
"alwaysShowTimestamps": {
supportedLevels: LEVELS_PRESET_ACCOUNT,
default: false,
displayName: _td('Always show message timestamps'),
},
"autoplayGifsAndVideos": {
supportedLevels: LEVELS_PRESET_ACCOUNT,
default: false,
displayName: _td('Autoplay GIFs and videos'),
},
"enableSyntaxHighlightLanguageDetection": {
supportedLevels: LEVELS_PRESET_ACCOUNT,
default: false,
displayName: _td('Enable automatic language detection for syntax highlighting'),
},
"Pill.shouldHidePillAvatar": {
supportedLevels: LEVELS_PRESET_ACCOUNT,
default: false,
displayName: _td('Hide avatars in user and room mentions'),
},
"TextualBody.disableBigEmoji": {
supportedLevels: LEVELS_PRESET_ACCOUNT,
default: false,
displayName: _td('Disable big emoji in chat'),
},
"MessageComposerInput.isRichTextEnabled": {
supportedLevels: LEVELS_PRESET_ACCOUNT,
default: false,
},
"MessageComposer.showFormatting": {
supportedLevels: LEVELS_PRESET_ACCOUNT,
default: false,
},
"dontSendTypingNotifications": {
supportedLevels: LEVELS_PRESET_ACCOUNT,
default: false,
displayName: _td("Don't send typing notifications"),
},
"MessageComposerInput.autoReplaceEmoji": {
supportedLevels: LEVELS_PRESET_ACCOUNT,
default: false,
displayName: _td('Automatically replace plain text Emoji'),
},
"VideoView.flipVideoHorizontally": {
supportedLevels: LEVELS_PRESET_ACCOUNT,
default: false,
displayName: _td('Mirror local video feed'),
},
"theme": {
supportedLevels: LEVELS_PRESET_ACCOUNT,
default: "light",
},
}; };
// Convert the above into simpler formats for the handlers // Convert the above into simpler formats for the handlers
@ -176,7 +266,7 @@ export default class SettingsStore {
* @param {String} roomId The room ID to read the setting value in, may be null. * @param {String} roomId The room ID to read the setting value in, may be null.
* @return {*} The value, or null if not found * @return {*} The value, or null if not found
*/ */
static getValue(settingName, roomId) { static getValue(settingName, roomId = null) {
const levelOrder = [ const levelOrder = [
'device', 'room-device', 'room-account', 'account', 'room', 'config', 'default', 'device', 'room-device', 'room-account', 'account', 'room', 'config', 'default',
]; ];
@ -195,7 +285,7 @@ export default class SettingsStore {
if (!handler) continue; if (!handler) continue;
const value = handler.getValue(settingName, roomId); const value = handler.getValue(settingName, roomId);
if (!value) continue; if (value === null || value === undefined) continue;
return value; return value;
} }
return null; return null;

View file

@ -14,6 +14,8 @@
limitations under the License. limitations under the License.
*/ */
import SettingsStore from "./settings/SettingsStore";
function memberEventDiff(ev) { function memberEventDiff(ev) {
const diff = { const diff = {
isMemberEvent: ev.getType() === 'm.room.member', isMemberEvent: ev.getType() === 'm.room.member',
@ -34,16 +36,19 @@ function memberEventDiff(ev) {
return diff; return diff;
} }
export default function shouldHideEvent(ev, syncedSettings) { export default function shouldHideEvent(ev) {
// Wrap getValue() for readability
const isEnabled = (name) => SettingsStore.getValue(name, ev.getRoomId());
// Hide redacted events // Hide redacted events
if (syncedSettings['hideRedactions'] && ev.isRedacted()) return true; if (isEnabled('hideRedactions') && ev.isRedacted()) return true;
const eventDiff = memberEventDiff(ev); const eventDiff = memberEventDiff(ev);
if (eventDiff.isMemberEvent) { if (eventDiff.isMemberEvent) {
if (syncedSettings['hideJoinLeaves'] && (eventDiff.isJoin || eventDiff.isPart)) return true; if (isEnabled('hideJoinLeaves') && (eventDiff.isJoin || eventDiff.isPart)) return true;
const isMemberAvatarDisplaynameChange = eventDiff.isAvatarChange || eventDiff.isDisplaynameChange; const isMemberAvatarDisplaynameChange = eventDiff.isAvatarChange || eventDiff.isDisplaynameChange;
if (syncedSettings['hideAvatarDisplaynameChanges'] && isMemberAvatarDisplaynameChange) return true; if (isEnabled('hideAvatarDisplaynameChanges') && isMemberAvatarDisplaynameChange) return true;
} }
return false; return false;

View file

@ -14,6 +14,8 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import SettingsStore from "../../../src/settings/SettingsStore";
const React = require('react'); const React = require('react');
const ReactDOM = require("react-dom"); const ReactDOM = require("react-dom");
const TestUtils = require('react-addons-test-utils'); const TestUtils = require('react-addons-test-utils');
@ -23,7 +25,6 @@ import sinon from 'sinon';
const sdk = require('matrix-react-sdk'); const sdk = require('matrix-react-sdk');
const MessagePanel = sdk.getComponent('structures.MessagePanel'); const MessagePanel = sdk.getComponent('structures.MessagePanel');
import UserSettingsStore from '../../../src/UserSettingsStore';
import MatrixClientPeg from '../../../src/MatrixClientPeg'; import MatrixClientPeg from '../../../src/MatrixClientPeg';
const test_utils = require('test-utils'); const test_utils = require('test-utils');
@ -59,7 +60,10 @@ describe('MessagePanel', function() {
sandbox = test_utils.stubClient(); sandbox = test_utils.stubClient();
client = MatrixClientPeg.get(); client = MatrixClientPeg.get();
client.credentials = {userId: '@me:here'}; client.credentials = {userId: '@me:here'};
UserSettingsStore.getSyncedSettings = sinon.stub().returns({});
// HACK: We assume all settings want to be disabled
// TODO: {Travis} Run the tests to verify this works
SettingsStore.getValue = sinon.stub().returns(false);
}); });
afterEach(function() { afterEach(function() {

View file

@ -6,7 +6,6 @@ import sinon from 'sinon';
import Promise from 'bluebird'; import Promise from 'bluebird';
import * as testUtils from '../../../test-utils'; import * as testUtils from '../../../test-utils';
import sdk from 'matrix-react-sdk'; import sdk from 'matrix-react-sdk';
import UserSettingsStore from '../../../../src/UserSettingsStore';
const MessageComposerInput = sdk.getComponent('views.rooms.MessageComposerInput'); const MessageComposerInput = sdk.getComponent('views.rooms.MessageComposerInput');
import MatrixClientPeg from '../../../../src/MatrixClientPeg'; import MatrixClientPeg from '../../../../src/MatrixClientPeg';
import RoomMember from 'matrix-js-sdk'; import RoomMember from 'matrix-js-sdk';