<commit_before>/*
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2017 Vector Creations Ltd
Copyright 2017, 2018 New Vector Ltd

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import SettingsStore, {SettingLevel} from "../../settings/SettingsStore";

const React = require('react');
const ReactDOM = require('react-dom');
import PropTypes from 'prop-types';
const sdk = require('../../index');
const MatrixClientPeg = require("../../MatrixClientPeg");
const PlatformPeg = require("../../PlatformPeg");
const Modal = require('../../Modal');
const dis = require("../../dispatcher");
import sessionStore from '../../stores/SessionStore';
import Promise from 'bluebird';
const packageJson = require('../../../package.json');
const UserSettingsStore = require('../../UserSettingsStore');
const CallMediaHandler = require('../../CallMediaHandler');
const Email = require('../../email');
const AddThreepid = require('../../AddThreepid');
const SdkConfig = require('../../SdkConfig');
import Analytics from '../../Analytics';
import AccessibleButton from '../views/elements/AccessibleButton';
import { _t, _td } from '../../languageHandler';
import * as languageHandler from '../../languageHandler';
import * as FormattingUtils from '../../utils/FormattingUtils';

// if this looks like a release, use the 'version' from package.json; else use
// the git sha. Prepend version with v, to look like riot-web version
const REACT_SDK_VERSION = 'dist' in packageJson ? packageJson.version : packageJson.gitHead || '<local>';

// Simple method to help prettify GH Release Tags and Commit Hashes.
const semVerRegex = /^v?(\d+\.\d+\.\d+(?:-rc.+)?)(?:-(?:\d+-g)?([0-9a-fA-F]+))?(?:-dirty)?$/i;
const gHVersionLabel = function(repo, token='') {
    const match = token.match(semVerRegex);
    let url;
    if (match && match[1]) { // basic semVer string possibly with commit hash
        url = (match.length > 1 && match[2])
            ? `https://github.com/${repo}/commit/${match[2]}`
            : `https://github.com/${repo}/releases/tag/v${match[1]}`;
    } else {
        url = `https://github.com/${repo}/commit/${token.split('-')[0]}`;
    }
    return <a target="_blank" rel="noopener" href={url}>{ token }</a>;
};

// Enumerate some simple 'flip a bit' UI settings (if any). The strings provided here
// must be settings defined in SettingsStore.
const SIMPLE_SETTINGS = [
    { id: "urlPreviewsEnabled" },
    { id: "autoplayGifsAndVideos" },
    { id: "alwaysShowEncryptionIcons" },
    { id: "hideReadReceipts" },
    { id: "dontSendTypingNotifications" },
    { id: "alwaysShowTimestamps" },
    { id: "showTwelveHourTimestamps" },
    { id: "hideJoinLeaves" },
    { id: "hideAvatarChanges" },
    { id: "hideDisplaynameChanges" },
    { id: "useCompactLayout" },
    { id: "hideRedactions" },
    { id: "enableSyntaxHighlightLanguageDetection" },
    { id: "MessageComposerInput.autoReplaceEmoji" },
    { id: "MessageComposerInput.dontSuggestEmoji" },
    { id: "Pill.shouldHidePillAvatar" },
    { id: "TextualBody.disableBigEmoji" },
    { id: "VideoView.flipVideoHorizontally" },
    { id: "TagPanel.disableTagPanel" },
    { id: "enableWidgetScreenshots" },
];

// These settings must be defined in SettingsStore
const ANALYTICS_SETTINGS = [
    {
        id: 'analyticsOptIn',
        fn: function(checked) {
            checked ? Analytics.enable() : Analytics.disable();
        },
    },
];

// These settings must be defined in SettingsStore
const WEBRTC_SETTINGS = [
    {
        id: 'webRtcForceTURN',
        fn: (val) => {
            MatrixClientPeg.get().setForceTURN(val);
        },
    },
];

// These settings must be defined in SettingsStore
const CRYPTO_SETTINGS = [
    {
        id: 'blacklistUnverifiedDevices',
        fn: function(checked) {
            MatrixClientPeg.get().setGlobalBlacklistUnverifiedDevices(checked);
        },
    },
];

// Enumerate the available themes, with a nice human text label.
// 'label' is how we describe it in the UI.
// 'value' is the value for the theme setting
//
// XXX: Ideally we would have a theme manifest or something and they'd be nicely
// packaged up in a single directory, and/or located at the application layer.
// But for now for expedience we just hardcode them here.
const THEMES = [
    { label: _td('Light theme'), value: 'light' },
    { label: _td('Dark theme'), value: 'dark' },
    { label: _td('Status.im theme'), value: 'status' },
];

const IgnoredUser = React.createClass({
    propTypes: {
        userId: PropTypes.string.isRequired,
        onUnignored: PropTypes.func.isRequired,
    },

    _onUnignoreClick: function() {
        const ignoredUsers = MatrixClientPeg.get().getIgnoredUsers();
        const index = ignoredUsers.indexOf(this.props.userId);
        if (index !== -1) {
            ignoredUsers.splice(index, 1);
            MatrixClientPeg.get().setIgnoredUsers(ignoredUsers)
                .then(() => this.props.onUnignored(this.props.userId));
        } else this.props.onUnignored(this.props.userId);
    },

    render: function() {
        return (
            <li>
                <AccessibleButton onClick={this._onUnignoreClick} className="mx_textButton">
                    { _t("Unignore") }
                </AccessibleButton>
                { this.props.userId }
            </li>
        );
    },
});

module.exports = React.createClass({
    displayName: 'UserSettings',

    propTypes: {
        onClose: PropTypes.func,
        // The brand string given when creating email pushers
        brand: PropTypes.string,

        // The base URL to use in the referral link. Defaults to window.location.origin.
        referralBaseUrl: PropTypes.string,

        // Team token for the referral link. If falsy, the referral section will
        // not appear
        teamToken: PropTypes.string,
    },

    getDefaultProps: function() {
        return {
            onClose: function() {},
        };
    },

    getInitialState: function() {
        return {
            avatarUrl: null,
            threepids: [],
            phase: "UserSettings.LOADING", // LOADING, DISPLAY
            email_add_pending: false,
            vectorVersion: undefined,
            rejectingInvites: false,
            mediaDevices: null,
            ignoredUsers: [],
        };
    },

    componentWillMount: function() {
        this._unmounted = false;
        this._addThreepid = null;

        if (PlatformPeg.get()) {
            Promise.resolve().then(() => {
                return PlatformPeg.get().getAppVersion();
            }).done((appVersion) => {
                if (this._unmounted) return;
                this.setState({
                    vectorVersion: appVersion,
                });
            }, (e) => {
                console.log("Failed to fetch app version", e);
            });
        }

        this._refreshMediaDevices();
        this._refreshIgnoredUsers();

        // Bulk rejecting invites:
        // /sync won't have had time to return when UserSettings re-renders from state changes, so getRooms()
        // will still return rooms with invites. To get around this, add a listener for
        // membership updates and kick the UI.
        MatrixClientPeg.get().on("RoomMember.membership", this._onInviteStateChange);

        dis.dispatch({
            action: 'panel_disable',
            sideDisabled: true,
            middleDisabled: true,
        });
        this._refreshFromServer();

        if (PlatformPeg.get().isElectron()) {
            const {ipcRenderer} = require('electron');

            ipcRenderer.on('settings', this._electronSettings);
            ipcRenderer.send('settings_get');
        }

        this.setState({
            language: languageHandler.getCurrentLanguage(),
        });

        this._sessionStore = sessionStore;
        this._sessionStoreToken = this._sessionStore.addListener(
            this._setStateFromSessionStore,
        );
        this._setStateFromSessionStore();
    },

    componentDidMount: function() {
        this.dispatcherRef = dis.register(this.onAction);
        this._me = MatrixClientPeg.get().credentials.userId;
    },

    componentWillUnmount: function() {
        this._unmounted = true;
        dis.dispatch({
            action: 'panel_disable',
            sideDisabled: false,
            middleDisabled: false,
        });
        dis.unregister(this.dispatcherRef);
        const cli = MatrixClientPeg.get();
        if (cli) {
            cli.removeListener("RoomMember.membership", this._onInviteStateChange);
        }

        if (PlatformPeg.get().isElectron()) {
            const {ipcRenderer} = require('electron');
            ipcRenderer.removeListener('settings', this._electronSettings);
        }
    },

    // `UserSettings` assumes that the client peg will not be null, so give it some
    // sort of assurance here by only allowing a re-render if the client is truthy.
    //
    // This is required because `UserSettings` maintains its own state and if this state
    // updates (e.g. during _setStateFromSessionStore) after the client peg has been made
    // null (during logout), then it will attempt to re-render and throw errors.
    shouldComponentUpdate: function() {
        return Boolean(MatrixClientPeg.get());
    },

    _setStateFromSessionStore: function() {
        this.setState({
            userHasGeneratedPassword: Boolean(this._sessionStore.getCachedPassword()),
        });
    },

    _electronSettings: function(ev, settings) {
        this.setState({ electron_settings: settings });
    },

    _refreshMediaDevices: function(stream) {
        if (stream) {
            // kill stream so that we don't leave it lingering around with webcam enabled etc
            stream.getTracks().forEach((track) => track.stop());<commit_msg>improve comment<commit_after>/*
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2017 Vector Creations Ltd
Copyright 2017, 2018 New Vector Ltd

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import SettingsStore, {SettingLevel} from "../../settings/SettingsStore";

const React = require('react');
const ReactDOM = require('react-dom');
import PropTypes from 'prop-types';
const sdk = require('../../index');
const MatrixClientPeg = require("../../MatrixClientPeg");
const PlatformPeg = require("../../PlatformPeg");
const Modal = require('../../Modal');
const dis = require("../../dispatcher");
import sessionStore from '../../stores/SessionStore';
import Promise from 'bluebird';
const packageJson = require('../../../package.json');
const UserSettingsStore = require('../../UserSettingsStore');
const CallMediaHandler = require('../../CallMediaHandler');
const Email = require('../../email');
const AddThreepid = require('../../AddThreepid');
const SdkConfig = require('../../SdkConfig');
import Analytics from '../../Analytics';
import AccessibleButton from '../views/elements/AccessibleButton';
import { _t, _td } from '../../languageHandler';
import * as languageHandler from '../../languageHandler';
import * as FormattingUtils from '../../utils/FormattingUtils';

// if this looks like a release, use the 'version' from package.json; else use
// the git sha. Prepend version with v, to look like riot-web version
const REACT_SDK_VERSION = 'dist' in packageJson ? packageJson.version : packageJson.gitHead || '<local>';

// Simple method to help prettify GH Release Tags and Commit Hashes.
const semVerRegex = /^v?(\d+\.\d+\.\d+(?:-rc.+)?)(?:-(?:\d+-g)?([0-9a-fA-F]+))?(?:-dirty)?$/i;
const gHVersionLabel = function(repo, token='') {
    const match = token.match(semVerRegex);
    let url;
    if (match && match[1]) { // basic semVer string possibly with commit hash
        url = (match.length > 1 && match[2])
            ? `https://github.com/${repo}/commit/${match[2]}`
            : `https://github.com/${repo}/releases/tag/v${match[1]}`;
    } else {
        url = `https://github.com/${repo}/commit/${token.split('-')[0]}`;
    }
    return <a target="_blank" rel="noopener" href={url}>{ token }</a>;
};

// Enumerate some simple 'flip a bit' UI settings (if any). The strings provided here
// must be settings defined in SettingsStore.
const SIMPLE_SETTINGS = [
    { id: "urlPreviewsEnabled" },
    { id: "autoplayGifsAndVideos" },
    { id: "alwaysShowEncryptionIcons" },
    { id: "hideReadReceipts" },
    { id: "dontSendTypingNotifications" },
    { id: "alwaysShowTimestamps" },
    { id: "showTwelveHourTimestamps" },
    { id: "hideJoinLeaves" },
    { id: "hideAvatarChanges" },
    { id: "hideDisplaynameChanges" },
    { id: "useCompactLayout" },
    { id: "hideRedactions" },
    { id: "enableSyntaxHighlightLanguageDetection" },
    { id: "MessageComposerInput.autoReplaceEmoji" },
    { id: "MessageComposerInput.dontSuggestEmoji" },
    { id: "Pill.shouldHidePillAvatar" },
    { id: "TextualBody.disableBigEmoji" },
    { id: "VideoView.flipVideoHorizontally" },
    { id: "TagPanel.disableTagPanel" },
    { id: "enableWidgetScreenshots" },
];

// These settings must be defined in SettingsStore
const ANALYTICS_SETTINGS = [
    {
        id: 'analyticsOptIn',
        fn: function(checked) {
            checked ? Analytics.enable() : Analytics.disable();
        },
    },
];

// These settings must be defined in SettingsStore
const WEBRTC_SETTINGS = [
    {
        id: 'webRtcForceTURN',
        fn: (val) => {
            MatrixClientPeg.get().setForceTURN(val);
        },
    },
];

// These settings must be defined in SettingsStore
const CRYPTO_SETTINGS = [
    {
        id: 'blacklistUnverifiedDevices',
        fn: function(checked) {
            MatrixClientPeg.get().setGlobalBlacklistUnverifiedDevices(checked);
        },
    },
];

// Enumerate the available themes, with a nice human text label.
// 'label' is how we describe it in the UI.
// 'value' is the value for the theme setting
//
// XXX: Ideally we would have a theme manifest or something and they'd be nicely
// packaged up in a single directory, and/or located at the application layer.
// But for now for expedience we just hardcode them here.
const THEMES = [
    { label: _td('Light theme'), value: 'light' },
    { label: _td('Dark theme'), value: 'dark' },
    { label: _td('Status.im theme'), value: 'status' },
];

const IgnoredUser = React.createClass({
    propTypes: {
        userId: PropTypes.string.isRequired,
        onUnignored: PropTypes.func.isRequired,
    },

    _onUnignoreClick: function() {
        const ignoredUsers = MatrixClientPeg.get().getIgnoredUsers();
        const index = ignoredUsers.indexOf(this.props.userId);
        if (index !== -1) {
            ignoredUsers.splice(index, 1);
            MatrixClientPeg.get().setIgnoredUsers(ignoredUsers)
                .then(() => this.props.onUnignored(this.props.userId));
        } else this.props.onUnignored(this.props.userId);
    },

    render: function() {
        return (
            <li>
                <AccessibleButton onClick={this._onUnignoreClick} className="mx_textButton">
                    { _t("Unignore") }
                </AccessibleButton>
                { this.props.userId }
            </li>
        );
    },
});

module.exports = React.createClass({
    displayName: 'UserSettings',

    propTypes: {
        onClose: PropTypes.func,
        // The brand string given when creating email pushers
        brand: PropTypes.string,

        // The base URL to use in the referral link. Defaults to window.location.origin.
        referralBaseUrl: PropTypes.string,

        // Team token for the referral link. If falsy, the referral section will
        // not appear
        teamToken: PropTypes.string,
    },

    getDefaultProps: function() {
        return {
            onClose: function() {},
        };
    },

    getInitialState: function() {
        return {
            avatarUrl: null,
            threepids: [],
            phase: "UserSettings.LOADING", // LOADING, DISPLAY
            email_add_pending: false,
            vectorVersion: undefined,
            rejectingInvites: false,
            mediaDevices: null,
            ignoredUsers: [],
        };
    },

    componentWillMount: function() {
        this._unmounted = false;
        this._addThreepid = null;

        if (PlatformPeg.get()) {
            Promise.resolve().then(() => {
                return PlatformPeg.get().getAppVersion();
            }).done((appVersion) => {
                if (this._unmounted) return;
                this.setState({
                    vectorVersion: appVersion,
                });
            }, (e) => {
                console.log("Failed to fetch app version", e);
            });
        }

        this._refreshMediaDevices();
        this._refreshIgnoredUsers();

        // Bulk rejecting invites:
        // /sync won't have had time to return when UserSettings re-renders from state changes, so getRooms()
        // will still return rooms with invites. To get around this, add a listener for
        // membership updates and kick the UI.
        MatrixClientPeg.get().on("RoomMember.membership", this._onInviteStateChange);

        dis.dispatch({
            action: 'panel_disable',
            sideDisabled: true,
            middleDisabled: true,
        });
        this._refreshFromServer();

        if (PlatformPeg.get().isElectron()) {
            const {ipcRenderer} = require('electron');

            ipcRenderer.on('settings', this._electronSettings);
            ipcRenderer.send('settings_get');
        }

        this.setState({
            language: languageHandler.getCurrentLanguage(),
        });

        this._sessionStore = sessionStore;
        this._sessionStoreToken = this._sessionStore.addListener(
            this._setStateFromSessionStore,
        );
        this._setStateFromSessionStore();
    },

    componentDidMount: function() {
        this.dispatcherRef = dis.register(this.onAction);
        this._me = MatrixClientPeg.get().credentials.userId;
    },

    componentWillUnmount: function() {
        this._unmounted = true;
        dis.dispatch({
            action: 'panel_disable',
            sideDisabled: false,
            middleDisabled: false,
        });
        dis.unregister(this.dispatcherRef);
        const cli = MatrixClientPeg.get();
        if (cli) {
            cli.removeListener("RoomMember.membership", this._onInviteStateChange);
        }

        if (PlatformPeg.get().isElectron()) {
            const {ipcRenderer} = require('electron');
            ipcRenderer.removeListener('settings', this._electronSettings);
        }
    },

    // `UserSettings` assumes that the client peg will not be null, so give it some
    // sort of assurance here by only allowing a re-render if the client is truthy.
    //
    // This is required because `UserSettings` maintains its own state and if this state
    // updates (e.g. during _setStateFromSessionStore) after the client peg has been made
    // null (during logout), then it will attempt to re-render and throw errors.
    shouldComponentUpdate: function() {
        return Boolean(MatrixClientPeg.get());
    },

    _setStateFromSessionStore: function() {
        this.setState({
            userHasGeneratedPassword: Boolean(this._sessionStore.getCachedPassword()),
        });
    },

    _electronSettings: function(ev, settings) {
        this.setState({ electron_settings: settings });
    },

    _refreshMediaDevices: function(stream) {
        if (stream) {
            // kill stream so that we don't leave it lingering around with webcam enabled etc
            // as here we called gUM to ask user for permission to their device names only
            stream.getTracks().forEach((track) => track.stop());
{'fraction_non_alphanumeric': 0.12962873323975024, 'fraction_numerical': 0.003291543637155719, 'mean_word_length': 2.563297688858227, 'pattern_counts': {'":': 0, '<': 17, '<?xml version=': 0, '>': 31, 'https://': 6, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7546069', 'n_tokens_mistral': 5815, 'n_tokens_neox': 5672, 'n_words': 1561}