...
 
Commits (13)
......@@ -259,6 +259,14 @@ export default class AdminConsole extends React.Component {
schema: AdminDefinition.settings.authentication.gitlab.schema,
}}
/>
<SCRoute
path={`${props.match.url}/phabricator`}
component={SchemaAdminSettings}
extraProps={{
...extraProps,
schema: AdminDefinition.settings.authentication.phabricator.schema,
}}
/>
<SCRoute
path={`${props.match.url}/oauth`}
component={SchemaAdminSettings}
......
......@@ -498,6 +498,11 @@ export default {
display_name: t('admin.team.showUsername'),
display_name_default: 'Show username (default)',
},
{
value: Constants.TEAMMATE_NAME_DISPLAY.SHOW_NICKNAME_USERNAME,
display_name: t('admin.team.showNicknameOrUsername'),
display_name_default: 'Show nickname if one exists, otherwise show username',
},
{
value: Constants.TEAMMATE_NAME_DISPLAY.SHOW_NICKNAME_FULLNAME,
display_name: t('admin.team.showNickname'),
......@@ -835,6 +840,106 @@ export default {
],
},
},
phabricator: {
schema: {
id: 'PhabricatorSettings',
name: t('admin.authentication.phabricator'),
name_default: 'Phabricator',
onConfigLoad: (config) => {
const newState = {};
newState['PhabricatorSettings.Url'] = config.PhabricatorSettings.UserApiEndpoint.replace('/api/v4/user', '');
return newState;
},
onConfigSave: (config) => {
const newConfig = {...config};
newConfig.PhabricatorSettings.UserApiEndpoint = config.PhabricatorSettings.Url.replace(/\/$/, '') + '/api/user.whoami';
return newConfig;
},
settings: [
{
type: Constants.SettingsTypes.TYPE_BOOL,
key: 'PhabricatorSettings.Enable',
label: t('admin.phabricator.enableTitle'),
label_default: 'Enable authentication with Phabricator: ',
help_text: t('admin.phabricator.enableDescription'),
help_text_default: 'When true, Mattermost allows team creation and account signup using Phabricator OAuth.\n \n1. Log in to your Phabricator account and go to Applications → OAuth Server → Create OAuth Server.\n2. Enter Redirect URIs "<your-mattermost-url>/login/phabricator/complete" (example: http://localhost:8065/login/phabricator/complete) and "<your-mattermost-url>/signup/phabricator/complete".\n3. Then use "Show Application Secret" and "Client PHID" fields from Phabricator to complete the options below.\n4. Complete the Endpoint URLs below.',
help_text_markdown: true,
},
{
type: Constants.SettingsTypes.TYPE_TEXT,
key: 'PhabricatorSettings.Id',
label: t('admin.phabricator.clientIdTitle'),
label_default: 'Application ID:',
help_text: t('admin.phabricator.clientIdDescription'),
help_text_default: 'Obtain this value via the instructions above for logging into Phabricator.',
placeholder: t('admin.phabricator.clientIdExample'),
placeholder_default: 'E.g.: "jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY"',
isDisabled: needsUtils.stateValueFalse('PhabricatorSettings.Enable'),
},
{
type: Constants.SettingsTypes.TYPE_TEXT,
key: 'PhabricatorSettings.Secret',
label: t('admin.phabricator.clientSecretTitle'),
label_default: 'Application Secret Key:',
help_text: t('admin.phabricator.clientSecretDescription'),
help_text_default: 'Obtain this value via the instructions above for logging into Phabricator.',
placeholder: t('admin.phabricator.clientSecretExample'),
placeholder_default: 'E.g.: "jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY"',
isDisabled: needsUtils.stateValueFalse('PhabricatorSettings.Enable'),
},
{
type: Constants.SettingsTypes.TYPE_TEXT,
key: 'PhabricatorSettings.Url',
label: t('admin.phabricator.siteUrl'),
label_default: 'Phabricator Site URL:',
help_text: t('admin.phabricator.siteUrlDescription'),
help_text_default: 'Enter the URL of your Phabricator instance, e.g. https://phabricator.example.com/. If your Phabricator instance is not set up with SSL, start the URL with http:// instead of https://.',
placeholder: t('admin.phabricator.siteUrlExample'),
placeholder_default: 'E.g.: https://',
isDisabled: needsUtils.stateValueFalse('PhabricatorSettings.Enable'),
},
{
type: Constants.SettingsTypes.TYPE_TEXT,
key: 'PhabricatorSettings.UserApiEndpoint',
label: t('admin.phabricator.userTitle'),
label_default: 'User API Endpoint:',
dynamic_value: (value, config, state) => {
if (state['PhabricatorSettings.Url']) {
return state['PhabricatorSettings.Url'].replace(/\/$/, '') + '/api/user.whoami';
}
return '';
},
isDisabled: () => true,
},
{
type: Constants.SettingsTypes.TYPE_TEXT,
key: 'PhabricatorSettings.AuthEndpoint',
label: t('admin.phabricator.authTitle'),
label_default: 'Auth Endpoint:',
dynamic_value: (value, config, state) => {
if (state['PhabricatorSettings.Url']) {
return state['PhabricatorSettings.Url'].replace(/\/$/, '') + '/oauthserver/auth/';
}
return '';
},
isDisabled: () => true,
},
{
type: Constants.SettingsTypes.TYPE_TEXT,
key: 'PhabricatorSettings.TokenEndpoint',
label: t('admin.phabricator.tokenTitle'),
label_default: 'Token Endpoint:',
dynamic_value: (value, config, state) => {
if (state['PhabricatorSettings.Url']) {
return state['PhabricatorSettings.Url'].replace(/\/$/, '') + '/oauthserver/token/';
}
return '';
},
isDisabled: () => true,
},
],
},
},
oauth: {
schema: {
id: 'OAuthSettings',
......@@ -1107,7 +1212,7 @@ export default {
label: t('admin.email.allowSignupTitle'),
label_default: 'Enable account creation with email:',
help_text: t('admin.email.allowSignupDescription'),
help_text_default: 'When true, Mattermost allows account creation using email and password. This value should be false only when you want to limit sign up to a single sign-on service like AD/LDAP, SAML or GitLab.',
help_text_default: 'When true, Mattermost allows account creation using email and password. This value should be false only when you want to limit sign up to a single sign-on service like AD/LDAP, SAML, GitLab or Phabricator.',
},
{
type: Constants.SettingsTypes.TYPE_BOOL,
......@@ -1911,7 +2016,7 @@ export default {
label: t('admin.service.ssoSessionDays'),
label_default: 'Session Length SSO (days):',
help_text: t('admin.service.ssoSessionDaysDesc'),
help_text_default: 'The number of days from the last time a user entered their credentials to the expiry of the users session. If the authentication method is SAML or GitLab, the user may automatically be logged back in to Mattermost if they are already logged in to SAML or GitLab. After changing this setting, the setting will take effect after the next time the user enters their credentials.',
help_text_default: 'The number of days from the last time a user entered their credentials to the expiry of the users session. If the authentication method is SAML, GitLab or Phabricator, the user may automatically be logged back in to Mattermost if they are already logged in to SAML, GitLab or Phabricator. After changing this setting, the setting will take effect after the next time the user enters their credentials.',
placeholder: t('admin.service.sessionDaysEx'),
placeholder_default: 'E.g.: "30"',
},
......
......@@ -72,6 +72,8 @@ export default class AdminSidebar extends React.Component {
let oauthSettings = null;
let ldapSettings = null;
let samlSettings = null;
let gitlabSettings = null;
let phabricatorSettings = null;
let clusterSettings = null;
let metricsSettings = null;
let complianceSettings = null;
......@@ -234,7 +236,7 @@ export default class AdminSidebar extends React.Component {
/>
);
} else {
oauthSettings = (
gitlabSettings = (
<AdminSidebarSection
name='gitlab'
title={
......@@ -245,6 +247,18 @@ export default class AdminSidebar extends React.Component {
}
/>
);
phabricatorSettings = (
<AdminSidebarSection
name='phabricator'
title={
<FormattedMessage
id='admin.sidebar.phabricator'
defaultMessage='Phabricator'
/>
}
/>
);
}
if (this.props.license.IsLicensed === 'true') {
......@@ -534,6 +548,8 @@ export default class AdminSidebar extends React.Component {
}
/>
{oauthSettings}
{gitlabSettings}
{phabricatorSettings}
{ldapSettings}
{samlSettings}
{mfaSettings}
......
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React from 'react';
import {FormattedHTMLMessage, FormattedMessage} from 'react-intl';
import * as Utils from 'utils/utils.jsx';
import AdminSettings from './admin_settings.jsx';
import BooleanSetting from './boolean_setting.jsx';
import SettingsGroup from './settings_group.jsx';
import TextSetting from './text_setting.jsx';
export default class PhabricatorSettings extends AdminSettings {
constructor(props) {
super(props);
this.getConfigFromState = this.getConfigFromState.bind(this);
this.renderSettings = this.renderSettings.bind(this);
this.updatePhabricatorUrl = this.updatePhabricatorUrl.bind(this);
}
getConfigFromState(config) {
config.PhabricatorSettings.Enable = this.state.enable;
config.PhabricatorSettings.Id = this.state.id;
config.PhabricatorSettings.Secret = this.state.secret;
config.PhabricatorSettings.UserApiEndpoint = this.state.userApiEndpoint;
config.PhabricatorSettings.AuthEndpoint = this.state.authEndpoint;
config.PhabricatorSettings.TokenEndpoint = this.state.tokenEndpoint;
return config;
}
getStateFromConfig(config) {
return {
enable: config.PhabricatorSettings.Enable,
id: config.PhabricatorSettings.Id,
secret: config.PhabricatorSettings.Secret,
phabricatorUrl: config.PhabricatorSettings.UserApiEndpoint.replace('/api/user.whoami', ''),
userApiEndpoint: config.PhabricatorSettings.UserApiEndpoint,
authEndpoint: config.PhabricatorSettings.AuthEndpoint,
tokenEndpoint: config.PhabricatorSettings.TokenEndpoint,
};
}
updatePhabricatorUrl(id, value) {
let trimmedValue = value;
if (value.endsWith('/')) {
trimmedValue = value.slice(0, -1);
}
this.setState({
saveNeeded: true,
phabricatorUrl: value,
userApiEndpoint: trimmedValue + '/api/user.whoami',
authEndpoint: trimmedValue + '/oauthserver/auth/',
tokenEndpoint: trimmedValue + '/oauthserver/token/',
});
}
isPhabricatorURLSetByEnv = () => {
// Assume that if one of these has been set using an environment variable,
// all of them have been set that way
return this.isSetByEnv('PhabricatorSettings.AuthEndpoint') ||
this.isSetByEnv('PhabricatorSettings.TokenEndpoint') ||
this.isSetByEnv('PhabricatorSettings.UserApiEndpoint');
};
renderTitle() {
return (
<FormattedMessage
id='admin.authentication.phabricator'
defaultMessage='Phabricator'
/>
);
}
renderSettings() {
return (
<SettingsGroup>
<BooleanSetting
id='enable'
label={
<FormattedMessage
id='admin.phabricator.enableTitle'
defaultMessage='Enable authentication with Phabricator: '
/>
}
helpText={
<div>
<FormattedMessage
id='admin.phabricator.enableDescription'
defaultMessage='When true, Mattermost allows team creation and account signup using Phabricator OAuth.'
/>
<br/>
<FormattedHTMLMessage
id='admin.phabricator.EnableHtmlDesc'
defaultMessage='<ol><li>Log in to your Phabricator account and go to Applications -> OAuth Server -> Create OAuth Server.</li><li>Enter Redirect URIs "<your-mattermost-url>/login/phabricator/complete" (example: http://localhost:8065/login/phabricator/complete) and "<your-mattermost-url>/signup/phabricator/complete". </li><li>Then use "Show Application Secret" and "Client PHID" fields from Phabricator to complete the options below.</li><li>Complete the Endpoint URLs below. </li></ol>'
/>
</div>
}
value={this.state.enable}
onChange={this.handleChange}
setByEnv={this.isSetByEnv('PhabricatorSettings.Enable')}
/>
<TextSetting
id='id'
label={
<FormattedMessage
id='admin.phabricator.clientIdTitle'
defaultMessage='Application ID:'
/>
}
placeholder={Utils.localizeMessage('admin.phabricator.clientIdExample', 'E.g.: "jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY"')}
helpText={
<FormattedMessage
id='admin.phabricator.clientIdDescription'
defaultMessage='Obtain this value via the instructions above for logging into Phabricator'
/>
}
value={this.state.id}
onChange={this.handleChange}
disabled={!this.state.enable}
setByEnv={this.isSetByEnv('PhabricatorSettings.Id')}
/>
<TextSetting
id='secret'
label={
<FormattedMessage
id='admin.phabricator.clientSecretTitle'
defaultMessage='Application Secret Key:'
/>
}
placeholder={Utils.localizeMessage('admin.phabricator.clientSecretExample', 'E.g.: "jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY"')}
helpText={
<FormattedMessage
id='admin.phabricator.clientSecretDescription'
defaultMessage='Obtain this value via the instructions above for logging into Phabricator.'
/>
}
value={this.state.secret}
onChange={this.handleChange}
disabled={!this.state.enable}
setByEnv={this.isSetByEnv('PhabricatorSettings.Secret')}
/>
<TextSetting
id='phabricatorUrl'
label={
<FormattedMessage
id='admin.phabricator.siteUrl'
defaultMessage='Phabricator Site URL:'
/>
}
placeholder={Utils.localizeMessage('admin.phabricator.siteUrlExample', 'E.g.: https://')}
helpText={
<FormattedMessage
id='admin.phabricator.siteUrlDescription'
defaultMessage='Enter the URL of your Phabricator instance, e.g. https://example.com:3000. If your Phabricator instance is not set up with SSL, start the URL with http:// instead of https://.'
/>
}
value={this.state.phabricatorUrl}
onChange={this.updatePhabricatorUrl}
disabled={!this.state.enable}
setByEnv={this.isPhabricatorURLSetByEnv()}
/>
<TextSetting
id='userApiEndpoint'
label={
<FormattedMessage
id='admin.phabricator.userTitle'
defaultMessage='User API Endpoint:'
/>
}
placeholder={''}
value={this.state.userApiEndpoint}
disabled={true}
setByEnv={false}
/>
<TextSetting
id='authEndpoint'
label={
<FormattedMessage
id='admin.phabricator.authTitle'
defaultMessage='Auth Endpoint:'
/>
}
placeholder={''}
value={this.state.authEndpoint}
disabled={true}
setByEnv={false}
/>
<TextSetting
id='tokenEndpoint'
label={
<FormattedMessage
id='admin.phabricator.tokenTitle'
defaultMessage='Token Endpoint:'
/>
}
placeholder={''}
value={this.state.tokenEndpoint}
disabled={true}
setByEnv={false}
/>
</SettingsGroup>
);
}
}
......@@ -131,6 +131,21 @@ export default function ErrorMessage({type, message, service}) {
}}
/>
</p>
<p>
<FormattedMessage
id='error.oauth_missing_code.phabricator'
defaultMessage='For {link} please make sure you followed the setup instructions.'
values={{
link: (
<ErrorLink
url={'https://docs.mattermost.com/deployment/sso-phabricator.html'}
messageId={t('error.oauth_missing_code.phabricator.link')}
defaultMessage={'Phabricator'}
/>
),
}}
/>
</p>
<p>
<FormattedMessage
id='error.oauth_missing_code.forum'
......
......@@ -32,6 +32,7 @@ function mapStateToProps(state) {
const enableSignInWithUsername = config.EnableSignInWithUsername === 'true';
const enableSignUpWithEmail = config.EnableSignUpWithEmail === 'true';
const enableSignUpWithGitLab = config.EnableSignUpWithGitLab === 'true';
const enableSignUpWithPhabricator = config.EnableSignUpWithPhabricator === 'true';
const enableSignUpWithGoogle = config.EnableSignUpWithGoogle === 'true';
const enableSignUpWithOffice365 = config.EnableSignUpWithOffice365 === 'true';
const ldapLoginFieldName = config.LdapLoginFieldName;
......@@ -66,6 +67,7 @@ function mapStateToProps(state) {
enableSignInWithUsername,
enableSignUpWithEmail,
enableSignUpWithGitLab,
enableSignUpWithPhabricator,
enableSignUpWithGoogle,
enableSignUpWithOffice365,
experimentalPrimaryTeam,
......
......@@ -46,6 +46,7 @@ class LoginController extends React.Component {
enableSignInWithUsername: PropTypes.bool.isRequired,
enableSignUpWithEmail: PropTypes.bool.isRequired,
enableSignUpWithGitLab: PropTypes.bool.isRequired,
enableSignUpWithPhabricator: PropTypes.bool.isRequired,
enableSignUpWithGoogle: PropTypes.bool.isRequired,
enableSignUpWithOffice365: PropTypes.bool.isRequired,
experimentalPrimaryTeam: PropTypes.string,
......@@ -365,7 +366,7 @@ class LoginController extends React.Component {
src={Client4.getBrandImageUrl(0)}
/>
<div>
{messageHtmlToComponent(formattedText, false, {mentions: false})}
{messageHtmlToComponent(formattedText, false, {mentions: false, imagesMetadata: null})}
</div>
</div>
);
......@@ -410,6 +411,7 @@ class LoginController extends React.Component {
checkSignUpEnabled = () => {
return this.props.enableSignUpWithEmail ||
this.props.enableSignUpWithGitLab ||
this.props.enableSignUpWithPhabricator ||
this.props.enableSignUpWithOffice365 ||
this.props.enableSignUpWithGoogle ||
this.props.enableLdap ||
......@@ -534,6 +536,7 @@ class LoginController extends React.Component {
const ldapEnabled = this.state.ldapEnabled;
const gitlabSigninEnabled = this.props.enableSignUpWithGitLab;
const phabricatorSigninEnabled = this.props.enableSignUpWithPhabricator;
const googleSigninEnabled = this.props.enableSignUpWithGoogle;
const office365SigninEnabled = this.props.enableSignUpWithOffice365;
const samlSigninEnabled = this.state.samlEnabled;
......@@ -662,7 +665,7 @@ class LoginController extends React.Component {
);
}
if ((emailSigninEnabled || usernameSigninEnabled || ldapEnabled) && (gitlabSigninEnabled || googleSigninEnabled || samlSigninEnabled || office365SigninEnabled)) {
if ((emailSigninEnabled || usernameSigninEnabled || ldapEnabled) && (gitlabSigninEnabled || phabricatorSigninEnabled || googleSigninEnabled || samlSigninEnabled || office365SigninEnabled)) {
loginControls.push(
<div
key='divider'
......@@ -705,6 +708,26 @@ class LoginController extends React.Component {
);
}
if (phabricatorSigninEnabled) {
loginControls.push(
<a
className='btn btn-custom-login phabricator'
key='phabricator'
href={Client4.getOAuthRoute() + '/phabricator/login' + this.props.location.search}
>
<span>
<span className='icon'/>
<span>
<FormattedMessage
id='login.phabricator'
defaultMessage='Phabricator'
/>
</span>
</span>
</a>
);
}
if (googleSigninEnabled) {
loginControls.push(
<a
......
......@@ -10,9 +10,7 @@ import FastClick from 'fastclick';
import {Route, Switch, Redirect} from 'react-router-dom';
import {setUrl} from 'mattermost-redux/actions/general';
import {setSystemEmojis} from 'mattermost-redux/actions/emojis';
import {getConfig} from 'mattermost-redux/selectors/entities/general';
import * as UserAgent from 'utils/user_agent.jsx';
import {EmojiIndicesByAlias} from 'utils/emoji.jsx';
import {trackLoadTime} from 'actions/diagnostics_actions.jsx';
import * as GlobalActions from 'actions/global_actions.jsx';
......@@ -197,20 +195,6 @@ export default class Root extends React.Component {
}
loadRecentlyUsedCustomEmojis()(store.dispatch, store.getState);
const iosDownloadLink = getConfig(store.getState()).IosAppDownloadLink;
const androidDownloadLink = getConfig(store.getState()).AndroidAppDownloadLink;
const toResetPasswordScreen = this.props.location.pathname === '/reset_password_complete';
// redirect to the mobile landing page if the user hasn't seen it before
if (iosDownloadLink && UserAgent.isIosWeb() && !BrowserStore.hasSeenLandingPage() && !toResetPasswordScreen) {
this.props.history.push('/get_ios_app?redirect_to=' + encodeURIComponent(this.props.location.pathname) + encodeURIComponent(this.props.location.search));
BrowserStore.setLandingPageSeen(true);
} else if (androidDownloadLink && UserAgent.isAndroidWeb() && !BrowserStore.hasSeenLandingPage() && !toResetPasswordScreen) {
this.props.history.push('/get_android_app?redirect_to=' + encodeURIComponent(this.props.location.pathname) + encodeURIComponent(this.props.location.search));
BrowserStore.setLandingPageSeen(true);
}
}
redirectIfNecessary = (props) => {
......
......@@ -3,13 +3,13 @@
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import {createSelector} from 'reselect';
import {Preferences} from 'mattermost-redux/constants/index';
import {
getCurrentChannel,
getSortedUnreadChannelIds,
getOrderedChannelIds,
getUnreads,
} from 'mattermost-redux/selectors/entities/channels';
import Permissions from 'mattermost-redux/constants/permissions';
......@@ -17,7 +17,7 @@ import {getConfig} from 'mattermost-redux/selectors/entities/general';
import {getBool as getBoolPreference, getSidebarPreferences} from 'mattermost-redux/selectors/entities/preferences';
import {getCurrentUser} from 'mattermost-redux/selectors/entities/users';
import {haveITeamPermission} from 'mattermost-redux/selectors/entities/roles';
import {getCurrentTeam, getMyTeams, getTeamMemberships} from 'mattermost-redux/selectors/entities/teams';
import {getCurrentTeam} from 'mattermost-redux/selectors/entities/teams';
import {switchToChannelById} from 'actions/views/channel';
import {openModal} from 'actions/views/modals';
......@@ -26,20 +26,6 @@ import {getIsLhsOpen} from 'selectors/lhs';
import Sidebar from './sidebar.jsx';
const getTotalUnreads = createSelector(
getMyTeams,
getTeamMemberships,
(myTeams, myTeamMemberships) => {
return myTeams.reduce((acc, team) => {
const member = myTeamMemberships[team.id];
acc.messageCount += member.msg_count;
acc.mentionCount += member.mention_count;
return acc;
}, {messageCount: 0, mentionCount: 0});
}
);
function mapStateToProps(state) {
const config = getConfig(state);
const currentChannel = getCurrentChannel(state);
......@@ -80,7 +66,7 @@ function mapStateToProps(state) {
canCreatePublicChannel,
canCreatePrivateChannel,
isOpen: getIsLhsOpen(state),
unreads: getTotalUnreads(state),
unreads: getUnreads(state),
};
}
......
......@@ -23,6 +23,7 @@ function mapStateToProps(state, ownProps) {
const noAccounts = config.NoAccounts === 'true';
const enableSignUpWithEmail = config.EnableSignUpWithEmail === 'true';
const enableSignUpWithGitLab = config.EnableSignUpWithGitLab === 'true';
const enableSignUpWithPhabricator = config.EnableSignUpWithPhabricator === 'true';
const enableSignUpWithGoogle = config.EnableSignUpWithGoogle === 'true';
const enableSignUpWithOffice365 = config.EnableSignUpWithOffice365 === 'true';
const enableLDAP = config.EnableLdap === 'true';
......@@ -48,6 +49,7 @@ function mapStateToProps(state, ownProps) {
noAccounts,
enableSignUpWithEmail,
enableSignUpWithGitLab,
enableSignUpWithPhabricator,
enableSignUpWithGoogle,
enableSignUpWithOffice365,
enableLDAP,
......
......@@ -189,6 +189,26 @@ export default class SignupController extends React.Component {
);
}
if (this.props.enableSignUpWithPhabricator) {
signupControls.push(
<a
className='btn btn-custom-login btn--full phabricator'
key='phabricator'
href={Client4.getOAuthRoute() + '/phabricator/signup' + window.location.search}
>
<span>
<span className='icon'/>
<span>
<FormattedMessage
id='signup.phabricator'
defaultMessage='Phabricator Single Sign-On'
/>
</span>
</span>
</a>
);
}
if (this.props.isLicensed && this.props.enableSignUpWithGoogle) {
signupControls.push(
<a
......
......@@ -204,6 +204,7 @@ export default class UserSettingsDisplay extends React.Component {
firstOption,
secondOption,
thirdOption,
fourthOption,
description,
} = props;
......@@ -257,6 +258,16 @@ export default class UserSettingsDisplay extends React.Component {
);
}
let fourthMessage;
if (fourthOption) {
fourthMessage = (
<FormattedMessage
id={fourthOption.radionButtonText.id}
defaultMessage={fourthOption.radionButtonText.message}
/>
);
}
const messageTitle = (
<FormattedMessage
id={title.id}
......@@ -272,13 +283,15 @@ export default class UserSettingsDisplay extends React.Component {
);
if (this.props.activeSection === section) {
const format = [false, false, false];
const format = [false, false, false, false];
if (value === firstOption.value) {
format[0] = true;
} else if (value === secondOption.value) {
format[1] = true;
} else {
} else if (thirdOption && value === thirdOption.value) {
format[2] = true;
} else {
format[3] = true;
}
const name = section + 'Format';
......@@ -295,6 +308,11 @@ export default class UserSettingsDisplay extends React.Component {
thirdDisplay[display] = thirdOption.value;
}
const fourthDisplay = {};
if (fourthOption) {
fourthDisplay[display] = fourthOption.value;
}
let thirdSection;
if (thirdMessage) {
thirdSection = (
......@@ -314,6 +332,25 @@ export default class UserSettingsDisplay extends React.Component {
);
}
let fourthSection;
if (fourthMessage) {
fourthSection = (
<div className='radio'>
<label>
<input
id={name + 'D'}
type='radio'
name={name}
checked={format[3]}
onChange={() => this.handleOnChange(fourthDisplay)}
/>
{fourthMessage}
</label>
<br/>
</div>
);
}
const inputs = [
<div key={key}>
<div className='radio'>
......@@ -347,6 +384,7 @@ export default class UserSettingsDisplay extends React.Component {
<br/>
</div>
{thirdSection}
{fourthSection}
<div>
<br/>
{messageDesc}
......@@ -517,6 +555,13 @@ export default class UserSettingsDisplay extends React.Component {
message: 'Show first and last name',
},
},
fourthOption: {
value: Constants.TEAMMATE_NAME_DISPLAY.SHOW_NICKNAME_USERNAME,
radionButtonText: {
id: 'user.settings.display.teammateNameDisplayNicknameUsername',
message: 'Show nickname if one exists, otherwise show username',
},
},
description: {
id: t('user.settings.display.teammateNameDisplayDescription'),
message: 'Set how to display other user\'s names in posts and the Direct Messages list.',
......
......@@ -551,6 +551,24 @@ class UserSettingsGeneralTab extends React.Component {
{helpText}
</div>
);
} else if (this.props.user.auth_service === Constants.PHABRICATOR_SERVICE) {
inputs.push(
<div
key='oauthEmailInfo'
className='form-group'
>
<div className='setting-list__hint col-sm-12'>
<FormattedMessage
id='user.settings.general.emailPhabricatorCantUpdate'
defaultMessage='Login occurs through Phabricator. Email cannot be updated. Email address used for notifications is {email}.'
values={{
email: this.state.originalEmail,
}}
/>
</div>
{helpText}
</div>
);
} else if (this.props.user.auth_service === Constants.GOOGLE_SERVICE) {
inputs.push(
<div
......@@ -678,6 +696,16 @@ class UserSettingsGeneralTab extends React.Component {
}}
/>
);
} else if (this.props.user.auth_service === Constants.PHABRICATOR_SERVICE) {
describe = (
<FormattedMessage
id='user.settings.general.loginPhabricator'
defaultMessage='Login done through Phabricator ({email})'
values={{
email: this.state.originalEmail,
}}
/>
);
} else if (this.props.user.auth_service === Constants.GOOGLE_SERVICE) {
describe = (
<FormattedMessage
......
......@@ -22,6 +22,7 @@ function mapStateToProps(state, ownProps) {
const enableOAuthServiceProvider = config.EnableOAuthServiceProvider === 'true';
const enableSignUpWithEmail = config.EnableSignUpWithEmail === 'true';
const enableSignUpWithGitLab = config.EnableSignUpWithGitLab === 'true';
const enableSignUpWithPhabricator = config.EnableSignUpWithPhabricator === 'true';
const enableSignUpWithGoogle = config.EnableSignUpWithGoogle === 'true';
const enableLdap = config.EnableLdap === 'true';
const enableSaml = config.EnableSaml === 'true';
......@@ -33,6 +34,7 @@ function mapStateToProps(state, ownProps) {
enableOAuthServiceProvider,
enableSignUpWithEmail,
enableSignUpWithGitLab,
enableSignUpWithPhabricator,
enableSignUpWithGoogle,
enableLdap,
enableSaml,
......
......@@ -48,6 +48,9 @@ export default class SecurityTab extends React.Component {
// Whether or not sign-up with GitLab is enabled.
enableSignUpWithGitLab: PropTypes.bool,
// Whether or not sign-up with Phabricator is enabled.
enableSignUpWithPhabricator: PropTypes.bool,
// Whether or not sign-up with Google is enabled.
enableSignUpWithGoogle: PropTypes.bool,
......@@ -301,6 +304,20 @@ export default class SecurityTab extends React.Component {
</div>
</div>
);
} else if (this.props.user.auth_service === Constants.PHABRICATOR_SERVICE) {
inputs.push(
<div
key='oauthEmailInfo'
className='form-group'
>
<div className='setting-list__hint col-sm-12'>
<FormattedMessage
id='user.settings.security.passwordPhabricatorCantUpdate'
defaultMessage='Login occurs through Phabricator. Password cannot be updated.'
/>
</div>
</div>
);
} else if (this.props.user.auth_service === Constants.LDAP_SERVICE) {
inputs.push(
<div
......@@ -413,6 +430,13 @@ export default class SecurityTab extends React.Component {
defaultMessage='Login done through GitLab'
/>
);
} else if (this.props.user.auth_service === Constants.PHABRICATOR_SERVICE) {
describe = (
<FormattedMessage
id='user.settings.security.loginPhabricator'
defaultMessage='Login done through Phabricator'
/>
);
} else if (this.props.user.auth_service === Constants.LDAP_SERVICE) {
describe = (
<FormattedMessage
......@@ -464,6 +488,7 @@ export default class SecurityTab extends React.Component {
if (this.props.activeSection === SECTION_SIGNIN) {
let emailOption;
let gitlabOption;
let phabricatorOption;
let googleOption;
let office365Option;
let ldapOption;
......@@ -487,6 +512,23 @@ export default class SecurityTab extends React.Component {
);
}
if (this.props.enableSignUpWithPhabricator) {
phabricatorOption = (
<div className='padding-bottom x2'>
<Link
className='btn btn-primary'
to={'/claim/email_to_oauth?email=' + encodeURIComponent(user.email) + '&old_type=' + user.auth_service + '&new_type=' + Constants.PHABRICATOR_SERVICE}
>
<FormattedMessage
id='user.settings.security.switchPhabricator'
defaultMessage='Switch to using Phabricator SSO'
/>
</Link>
<br/>
</div>
);
}
if (this.props.enableSignUpWithGoogle) {
googleOption = (
<div className='padding-bottom x2'>
......@@ -583,6 +625,7 @@ export default class SecurityTab extends React.Component {
<div key='userSignInOption'>
{emailOption}
{gitlabOption}
{phabricatorOption}
{googleOption}
{office365Option}
{ldapOption}
......@@ -623,6 +666,13 @@ export default class SecurityTab extends React.Component {
defaultMessage='GitLab'
/>
);
} else if (this.props.user.auth_service === Constants.PHABRICATOR_SERVICE) {
describe = (
<FormattedMessage
id='user.settings.security.phabricator'
defaultMessage='Phabricator'
/>
);
} else if (this.props.user.auth_service === Constants.GOOGLE_SERVICE) {
describe = (
<FormattedMessage
......@@ -801,6 +851,7 @@ export default class SecurityTab extends React.Component {
let numMethods = 0;
numMethods = this.props.enableSignUpWithGitLab ? numMethods + 1 : numMethods;
numMethods = this.props.enableSignUpWithPhabricator ? numMethods + 1 : numMethods;
numMethods = this.props.enableSignUpWithGoogle ? numMethods + 1 : numMethods;
numMethods = this.props.enableSignUpWithOffice365 ? numMethods + 1 : numMethods;
numMethods = this.props.enableLdap ? numMethods + 1 : numMethods;
......
......@@ -1269,6 +1269,7 @@
"admin.team.restrictTitle": "Restrict account creation to specified email domains:",
"admin.team.showFullname": "Show first and last name",
"admin.team.showNickname": "Show nickname if one exists, otherwise show first and last name",
"admin.team.showNicknameOrUsername": "Show nickname if one exists, otherwise show username",
"admin.team.showUsername": "Show username (default)",
"admin.team.siteNameDescription": "Name of service shown in login screens and UI.",
"admin.team.siteNameExample": "E.g.: \"Mattermost\"",
......@@ -1310,9 +1311,9 @@
"admin.user_item.sysAdmin": "System Admin",
"admin.user_item.teamAdmin": "Team Admin",
"admin.user_item.teamMember": "Team Member",
"admin.user_item.userAccessTokenPostAll": "(with post:all personal access tokens)",
"admin.user_item.userAccessTokenPostAllPublic": "(with post:channels personal access tokens)",
"admin.user_item.userAccessTokenYes": "(with personal access tokens)",
"admin.user_item.userAccessTokenPostAll": "(can create post:all personal access tokens)",
"admin.user_item.userAccessTokenPostAllPublic": "(can create post:channels personal access tokens)",
"admin.user_item.userAccessTokenYes": "(can create personal access tokens)",
"admin.viewArchivedChannelsHelpText": "(Experimental) When true, allows users to share permalinks and search for content of channels that have been archived. Users can only view the content in channels of which they were a member before the channel was archived.",
"admin.viewArchivedChannelsTitle": "Allow users to view archived channels:",
"admin.webserverModeDisabled": "Disabled",
......@@ -2746,6 +2747,7 @@
"user.settings.display.teammateNameDisplayDescription": "Set how to display other user's names in posts and the Direct Messages list.",
"user.settings.display.teammateNameDisplayFullname": "Show first and last name",
"user.settings.display.teammateNameDisplayNicknameFullname": "Show nickname if one exists, otherwise show first and last name",
"user.settings.display.teammateNameDisplayNicknameUsername": "Show nickname if one exists, otherwise show username",
"user.settings.display.teammateNameDisplayTitle": "Teammate Name Display",
"user.settings.display.teammateNameDisplayUsername": "Show username",
"user.settings.display.theme.applyToAllTeams": "Apply new theme to all my teams",
......
......@@ -619,7 +619,7 @@
"admin.ldap.syncIntervalHelpText": "La synchronisation AD/LDAP met à jour les informations utilisateurs de Mattermost pour refléter les changements du serveur AD/LDAP. Par exemple, lorsque le nom d'un utilisateur change sur le serveur AD/LDAP, le changement est appliqué sur Mattermost lors de la synchronisation. Les comptes supprimés ou désactivés du serveur AD/LDAP auront leur compte Mattermost défini comme « Inactif » et auront leur session révoquée. Mattermost lance une synchronisation selon l'intervalle spécifié. Par exemple, si 60 est spécifié, Mattermost lancera une synchronisation toutes les 60 minutes.",
"admin.ldap.syncIntervalTitle": "Intervalle de synchronisation (en minutes)",
"admin.ldap.syncNowHelpText": "Initie immédiatement une synchronisation AD/LDAP. Veuillez consulter le tableau ci-dessous pour connaître le statut de chaque synchronisation. Veuillez consulter « Console système > Journaux » et notre [documentation](!https://mattermost.com/default-ldap-docs) pour trouver les solutions aux éventuels problèmes que vous rencontreriez.",
"admin.ldap.testFailure": "Echec du test AD/LDAP : {error}",
"admin.ldap.testFailure": "Une erreur s'est produite lors du test AD/LDAP : {error}",
"admin.ldap.testHelpText": "Teste si le serveur Mattermost peut se connecter au serveur AD/LDAP spécifié. Veuillez consulter « Console système > Journaux » et notre [documentation](!https://mattermost.com/default-ldap-docs) pour trouver les solutions aux éventuels problèmes que vous rencontreriez.",
"admin.ldap.testSuccess": "Test d'AD/LDAP réussi avec succès",
"admin.ldap.userFilterDisc": "(Facultatif) Spécifiez un filtre AD/LDAP à utiliser lors de la recherche d'objets utilisateur. Seuls les utilisateurs sélectionnés par la requête seront en mesure d'accéder à Mattermost . Pour Active Directory, la requête pour filtrer les utilisateurs désactivés est (&(objectCategory=Person)(!(UserAccountControl:1.2.840.113556.1.4.803:=2))).",
......@@ -922,7 +922,7 @@
"admin.privacy.showFullNameTitle": "Afficher le nom complet : ",
"admin.purge.button": "Purger tous les caches",
"admin.purge.purgeDescription": "Purge tous les caches en mémoire pour des éléments tels que les sessions, comptes, canaux, etc. Les déploiements qui utilisent la haute disponibilité tenteront de purger tous les serveurs du cluster. Purger les caches peut affecter les performances.",
"admin.purge.purgeFail": "Impossible de purger : {error}",
"admin.purge.purgeFail": "Une erreur s'est produite lors de la purge : {error}",
"admin.rate.enableLimiterDescription": "Lorsqu'activé, les APIs sont limitées selon les paramètres définis ci-dessous.",
"admin.rate.enableLimiterTitle": "Limiter l'accès aux API : ",
"admin.rate.httpHeaderDescription": "Lorsque défini, les limites de fréquence par l'entête HTTP spécifié varient (ex. : lorsque NGINX est configuré avec « X-Real-IP » ou lorsque AmazonELB est configuré avec « X-Forwarded-For »).",
......@@ -947,15 +947,15 @@
"admin.recycle.recycleDescription": "Les déploiements utilisant plusieurs bases de données peuvent basculer d'une base de donnée principale à une autre sans redémarrer le serveur Mattermost en définissant le fichier « config.json » sur la nouvelle configuration désirée, et en utilisant la fonctionnalité {reloadConfiguration} pour charger les nouveaux paramètres pendant que le serveur est en activité. L'administrateur devra donc ensuite utiliser la fonctionnalité {featureName} pour recycler les connexions aux bases de données en se basant sur les nouveaux paramètres.",
"admin.recycle.recycleDescription.featureName": "Recycler les connexions des bases de données",
"admin.recycle.recycleDescription.reloadConfiguration": "Configuration > Recharger la configuration à partir du disque",
"admin.recycle.reloadFail": "Echec du recyclage : {error}",
"admin.recycle.reloadFail": "Une erreur s'est produite lors du recyclage : {error}",
"admin.regenerate": "Regénérer",
"admin.reload.button": "Recharger le fichier de configuration",
"admin.reload.reloadDescription": "Les déploiements utilisant plusieurs bases de données peuvent basculer d'une base de donnée principale à une autre sans redémarrer le serveur Mattermost en définissant le fichier « config.json » sur la nouvelle configuration désirée, et en utilisant la fonctionnalité {featureName} pour charger les nouveaux paramètres pendant que le serveur est en activité. L'administrateur devra alors utiliser la fonctionnalité {recycleDatabaseConnections} pour recycler les connexions aux bases de données en se basant sur les nouveaux paramètres.",
"admin.reload.reloadDescription.featureName": "Recharger la configuration à partir du disque",
"admin.reload.reloadDescription.recycleDatabaseConnections": "Bases de données > Recycler les connexions des bases de données",
"admin.reload.reloadFail": "Échec de la connexion : {error}",
"admin.reload.reloadFail": "Une erreur s'est produite lors de la connexion : {error}",
"admin.requestButton.loading": " Chargement…",
"admin.requestButton.requestFailure": "Erreur de test : {error}",
"admin.requestButton.requestFailure": "Une erreur s'est produite lors du test : {error}",
"admin.requestButton.requestSuccess": "Test effectué avec succès",
"admin.reset_email.cancel": "Annuler",
"admin.reset_email.newEmail": "Nouvelle adresse e‑mail",
......@@ -970,7 +970,7 @@
"admin.reset_password.titleSwitch": "Basculez le type d'authentification sur le couple l’adresse e-mail / mot de passe",
"admin.revoke_token_button.delete": "Supprimer",
"admin.s3.connectionS3Test": "Tester la connexion",
"admin.s3.s3Fail": "Échec de la connexion : {error}",
"admin.s3.s3Fail": "Une erreur s'est produite lors de la connexion : {error}",
"admin.s3.s3Success": "Connexion réussie",
"admin.s3.testing": "Essai en cours...",
"admin.saml.assertionConsumerServiceURLEx": "Ex. : « https://<your-mattermost-url>/login/sso/saml »",
......@@ -1139,7 +1139,7 @@
"admin.sidebar.customBrand": "Image de marque personnalisée",
"admin.sidebar.customIntegrations": "Intégrations personnalisées",
"admin.sidebar.customization": "Personnalisation",
"admin.sidebar.customTermsOfService": "Texte des conditions d'utilisation personnalisées :",
"admin.sidebar.customTermsOfService": "Conditions d'utilisation personnalisées (Beta)",
"admin.sidebar.data_retention": "Politique de rétention de données",
"admin.sidebar.database": "Base de données",
"admin.sidebar.developer": "Développeur",
......@@ -1223,7 +1223,7 @@
"admin.support.termsOfServiceReAcceptanceTitle": "Durée d'acceptation :",
"admin.support.termsOfServiceTextHelp": "Le texte qui apparaîtra dans les conditions d'utilisation personnalisées. Supporte le formatage de texte Markdown.",
"admin.support.termsOfServiceTextTitle": "Texte des conditions d'utilisation personnalisées :",
"admin.support.termsOfServiceTitle": "Texte des conditions d'utilisation personnalisées :",
"admin.support.termsOfServiceTitle": "Conditions d'utilisation personnalisées (Beta)",
"admin.support.termsTitle": "Lien vers la page des conditions d'utilisation :",
"admin.system_users.allUsers": "Tous les utilisateurs",
"admin.system_users.noTeams": "Aucune équipe",
......@@ -1402,7 +1402,7 @@
"audit_table.channelCreated": "Le canal {channelName} a été créé",
"audit_table.channelDeleted": "Le canal portant l'URL {url} a été archivé",
"audit_table.establishedDM": "Canal de messages personnels établi avec {username}",
"audit_table.failedExpiredLicenseAdd": "Echec d'ajout d'une licence (elle a déjà expiré ou pas encore démarré)",
"audit_table.failedExpiredLicenseAdd": "Une erreur s'est produite lors de l'ajout d'une licence. La licence a déjà expiré ou sa période n'a pas encore débuté.",
"audit_table.failedInvalidLicenseAdd": "Échec de l'ajout d'une licence",
"audit_table.failedLogin": "ÉCHEC de la connexion",
"audit_table.failedOAuthAccess": "Impossible d'autoriser un nouvel accès à un service OAuth - L'URI de redirection ne correspond pas à l'URI de callback déjà enregistrée",
......@@ -2069,7 +2069,7 @@
"intro_messages.noCreator": "Ceci est le début de {name} {type}, créé le {date}.",
"intro_messages.offTopic": "Ceci est le début de {display_name}, un canal dédié aux conversations extra-professionnelles.",
"intro_messages.onlyInvited": " Seuls les membres invités peuvent voir ce canal privé.",
"intro_messages.purpose": "L'objectif de {type} est : {purpose}.",
"intro_messages.purpose": " L'objectif du {type} est : {purpose}.",
"intro_messages.readonly.default": "**Bienvenue dans {display_name} !**\n\nLes messages de ce canal peuvent seulement être publiés par des administrateurs système. Chaque utilisateur devient un membre permanent de ce canal lorsqu'il rejoint l'équipe.",
"intro_messages.setHeader": "Définir l'entête",
"intro_messages.teammate": "Vous êtes au début de votre historique de messages avec cet utilisateur. Les messages personnels et les fichiers partagés ici ne sont pas visibles par les autres utilisateurs.",
......@@ -2719,6 +2719,7 @@
"user.settings.display.teammateNameDisplayDescription": "Choisissez comment afficher les noms des autres utilisateurs dans les messages et la liste de messages personnels.",
"user.settings.display.teammateNameDisplayFullname": "Afficher le prénom d'abord puis le nom",
"user.settings.display.teammateNameDisplayNicknameFullname": "Afficher le pseudo s'il existe, sinon afficher le prénom d'abord puis le nom",
"user.settings.display.teammateNameDisplayNicknameUsername": "Afficher le pseudo s'il exists, sinon afficher le nom d'utilisateur",
"user.settings.display.teammateNameDisplayTitle": "Affichage des noms des membres de l'équipe",
"user.settings.display.teammateNameDisplayUsername": "Afficher le nom d'utilisateur",
"user.settings.display.theme.applyToAllTeams": "Appliquer le nouveau thème à toutes mes équipes",
......
......@@ -2719,6 +2719,7 @@
"user.settings.display.teammateNameDisplayDescription": "投稿やダイレクトメッセージ中の他のユーザーの名前の表示方法を設定します。",
"user.settings.display.teammateNameDisplayFullname": "氏名を表示する",
"user.settings.display.teammateNameDisplayNicknameFullname": "ニックネームがあればそれを表示する。無ければ氏名を表示する。",
"user.settings.display.teammateNameDisplayNicknameFullname": "ニックネームがあればそれを表示する。無ければユーザー名を表示する",
"user.settings.display.teammateNameDisplayTitle": "チームメイトの名前の表示",
"user.settings.display.teammateNameDisplayUsername": "ユーザー名を表示する",
"user.settings.display.theme.applyToAllTeams": "全ての自分のチームに新しいテーマを適用する",
......
......@@ -2499,7 +2499,7 @@
"sidebar.types.public": "공개 채널",
"sidebar.types.recent": "RECENT ACTIVITY",
"sidebar.types.unreads": "읽지 않음",
"sidebar.unreads": "More unreads",
"sidebar.unreads": "읽지 않은 글 더보기",
"signup_team_system_console": "관리자 도구로 바로가기",
"signup_team.join_open": "가입할 수 있는 팀: ",
"signup_team.no_open_teams": "참가 가능한 팀이 없습니다. 관리자에게 초대를 요청하세요.",
......@@ -2942,30 +2942,30 @@
"user.settings.sidebar.channelSwitcherSectionDesc.windows": "채널 스위처는 사이드 바의 하단에 표시되며 채널 사이를 빠르게 이동하는 데 사용됩니다. CTRL + K를 통해 사용할 수 있습니다.",
"user.settings.sidebar.channelSwitcherSectionTitle": "Channel Switcher",
"user.settings.sidebar.favorites": "Favorites grouped separately",
"user.settings.sidebar.favoritesDesc": "Channels marked as favorites will be grouped separately.",
"user.settings.sidebar.favoritesShort": "Favorites grouped separately",
"user.settings.sidebar.groupAndSortChannelsTitle": "Channel grouping and sorting",
"user.settings.sidebar.groupByNone": "Combine all channel types",
"user.settings.sidebar.favoritesDesc": "즐겨찾기로 표시 된 채널은 별도로 그룹화됩니다.",
"user.settings.sidebar.favoritesShort": "즐겨찾는 그룹 분류",
"user.settings.sidebar.groupAndSortChannelsTitle": "채널 그룹 및 정렬",
"user.settings.sidebar.groupByNone": "모든 채널 타입 결합",
"user.settings.sidebar.groupByNoneShort": "No grouping",
"user.settings.sidebar.groupByType": "Channels grouped by type",
"user.settings.sidebar.groupByType": "타입별로 그룹화 된 채널",
"user.settings.sidebar.groupByTypeShort": "Group by channel type",
"user.settings.sidebar.groupChannelsTitle": "Channel grouping",
"user.settings.sidebar.groupDesc": "Group channels by type, or combine all types into a list.",
"user.settings.sidebar.groupChannelsTitle": "채널 그룹",
"user.settings.sidebar.groupDesc": "타입별로 채널을 그룹화 하거나 모든 타입을 리스트에 결합합니다.",
"user.settings.sidebar.icon": "화면 설정",
"user.settings.sidebar.never": "알림 사용 안함",
"user.settings.sidebar.off": "끄기",
"user.settings.sidebar.on": "켜기",
"user.settings.sidebar.sortAlpha": "Alphabetically",
"user.settings.sidebar.sortAlphaShort": "sorted alphabetically",
"user.settings.sidebar.sortChannelsTitle": "Channel sorting",
"user.settings.sidebar.sortDesc": "Sort channels alphabetically, or by most recent post.",
"user.settings.sidebar.sortRecent": "Recency",
"user.settings.sidebar.sortRecentShort": "sorted by recency",
"user.settings.sidebar.sortAlpha": "알파벳순",
"user.settings.sidebar.sortAlphaShort": "알파벳순으로 정렬",
"user.settings.sidebar.sortChannelsTitle": "채널 정렬",
"user.settings.sidebar.sortDesc": "알파벳순 또는 가장 최근 게시물 정렬",
"user.settings.sidebar.sortRecent": "최신",
"user.settings.sidebar.sortRecentShort": "최근 순서로 정렬",
"user.settings.sidebar.title": "화면 설정",
"user.settings.sidebar.unreads": "Unreads grouped separately",
"user.settings.sidebar.unreadsDesc": "Group unread channels separately until read.",
"user.settings.sidebar.unreadsFavoritesShort": "Unreads and favorites grouped separately",
"user.settings.sidebar.unreadsShort": "Unreads grouped separately",
"user.settings.sidebar.unreads": "읽지않은 글 분류",
"user.settings.sidebar.unreadsDesc": "읽을 때 까지 읽지않은 채널을 그룹화합니다.",
"user.settings.sidebar.unreadsFavoritesShort": "읽지 않은 글과 즐겨찾기는 개별적으로 분류됩니다",
"user.settings.sidebar.unreadsShort": "읽지않은 글 분류",
"user.settings.timezones.automatic": "자동으로 설정",
"user.settings.timezones.change": "타임존 변경",
"user.settings.timezones.promote": "사용자 인터페이스 및 메일 알림 등에서 사용 할 표준 시간대를 선택하세요.",
......
......@@ -1304,9 +1304,9 @@
"admin.user_item.sysAdmin": "Systeem beheerder",
"admin.user_item.teamAdmin": "Team beheerder",
"admin.user_item.teamMember": "Team Member",
"admin.user_item.userAccessTokenPostAll": "(with post:all personal access tokens)",
"admin.user_item.userAccessTokenPostAllPublic": "(with post:channels personal access tokens)",
"admin.user_item.userAccessTokenYes": "(with personal access tokens)",
"admin.user_item.userAccessTokenPostAll": "(can create post:all personal access tokens)",
"admin.user_item.userAccessTokenPostAllPublic": "(can create post:channels personal access tokens)",
"admin.user_item.userAccessTokenYes": "(can create personal access tokens)",
"admin.viewArchivedChannelsHelpText": "(Experimental) When true, allows users to share permalinks and search for content of channels that have been archived. Users can only view the content in channels of which they were a member before the channel was archived.",
"admin.viewArchivedChannelsTitle": "Allow users to view archived channels:",
"admin.webserverModeDisabled": "Uitgeschakeld",
......
This diff is collapsed.
......@@ -1139,7 +1139,7 @@
"admin.sidebar.customBrand": "Marca Personalizada",
"admin.sidebar.customIntegrations": "Integrações Personalizadas",
"admin.sidebar.customization": "Customização",
"admin.sidebar.customTermsOfService": "Texto Termos de Serviço Personalizado:",
"admin.sidebar.customTermsOfService": "Termos de Serviço Personalizado (Beta)",
"admin.sidebar.data_retention": "Política de Retenção de Dados",
"admin.sidebar.database": "Banco de dados",
"admin.sidebar.developer": "Desenvolvedor",
......@@ -1223,7 +1223,7 @@
"admin.support.termsOfServiceReAcceptanceTitle": "Período de Re-Aceitação:",
"admin.support.termsOfServiceTextHelp": "Texto que aparecerá em seus Termos de Serviço personalizados. Suporta texto formatado com Markdown.",
"admin.support.termsOfServiceTextTitle": "Texto Termos de Serviço Personalizado:",
"admin.support.termsOfServiceTitle": "Texto Termos de Serviço Personalizado:",
"admin.support.termsOfServiceTitle": "Termos de Serviço Personalizado (Beta)",
"admin.support.termsTitle": "Link Termos do Serviço:",
"admin.system_users.allUsers": "Todos os Usuários",
"admin.system_users.noTeams": "Nenhuma Equipe",
......
......@@ -1470,7 +1470,7 @@
"channel_flow.handleTooShort": "Посилання на канал має бути не коротше 2-х буквених символів",
"channel_flow.invalidName": "Неприпустиме ім'я каналу",
"channel_flow.set_url_title": "Встановити адресу каналу",
"channel_header.addChannelHeader": "Додати опис каналу",
"channel_header.addChannelHeader": "Додати опис каналу ",
"channel_header.addMembers": "Додати учасників",
"channel_header.channelMembers": "Учасник",
"channel_header.convert": "Конвертувати в приватний канал",
......@@ -2101,7 +2101,7 @@
"last_users_message.others": "{numOthers} інших",
"last_users_message.removed_from_channel.type": "Ви були **видалені з каналу**.",
"last_users_message.removed_from_team.type": "Ви були **вилучені з команди**.",
"leave_private_channel_modal.leave": "Так, залишити конал",
"leave_private_channel_modal.leave": "Так, залишити канал",
"leave_private_channel_modal.message": "Ви впевнені, що хочете залишити приватний канал {channel}? Вас потрібно буде знову запросити, щоб знову приєднатися до цього каналу в майбутньому.",
"leave_private_channel_modal.title": "Покинути приватний канал {channel}",
"leave_team_modal.desc": "Ви будете видалені з усіх публічних і приватних каналів. Якщо команда приватна, то ви не зможете повернутися. Ви впевнені?",
......@@ -2168,7 +2168,7 @@
"mfa.setup.step2": "**Крок 2:** Використовуйте Google Authenticator для сканування QR коду або вручну введіть секретний ключ",
"mfa.setup.step3": "**Крок 3: **Введіть код, згенерований Google Authenticator",
"mfa.setupTitle": "Налаштування багатофакторної перевірки автентичності ",
"mobile.set_status.away.icon": "Кнопка далеко ",
"mobile.set_status.away.icon": "Кнопка не на місці",
"mobile.set_status.dnd.icon": "Значок не турбувати ",
"mobile.set_status.offline.icon": "Іконка Offline",
"mobile.set_status.online.icon": "Іконка Online ",
......@@ -2189,12 +2189,12 @@
"modal.manual_status.cancel_offline": "Ні, залишити статус \"Не в мережі\"",
"modal.manual_status.cancel_ooo": "Ні, тримайте його як \"Out of Office\"",
"modal.manual_status.message_": "Хочете ви змінити свій статус на \"{status}\"?",
"modal.manual_status.message_away": "Ви хочете змінити свій статус на \"Далеко\"?",
"modal.manual_status.message_away": "Ви хочете змінити свій статус на \"Не на місці\"?",
"modal.manual_status.message_dnd": "Ви хочете змінити свій статус на \"Не турбувати\"?",
"modal.manual_status.message_offline": "Ви хочете змінити свій статус на \"Offline\"?",
"modal.manual_status.message_online": "Хочете переключити свій статус на \"Online\"?",
"modal.manual_status.title_": "Ваш статус встановлено на \"{status}\"",
"modal.manual_status.title_away": "Ваш статус встановлено на \"Далеко\"",
"modal.manual_status.title_away": "Ваш статус встановлено на \"Не на місці\"",
"modal.manual_status.title_dnd": "Ваш статус встановлено на \"Не турбувати\"",
"modal.manual_status.title_offline": "Ваш статус встановлено на \"Offline\"",
"modal.manual_status.title_ooo": "Ваш статус встановлено на \"За межами офісу\"",
......@@ -2255,7 +2255,7 @@
"navbar.viewPinnedPosts": "Переглянути прикріплені повідомлення",
"notification.dm": "Пряме повідомлення",
"notify_all.confirm": "Підтвердьте",
"notify_all.question": "Використовуючи @all або @channel, ви збираєтеся надсилати сповіщення {totalMembers} людям. Ви впевнені, що хочете це зробити?",
"notify_all.question": "Використовуючи @all або @channel, ви збираєтеся надсилати сповіщення {totalMembers} людям. Ви впевнені, що хочете це зробити? ",
"notify_all.question_timezone": "Використовуючи @all або @channel, ви збираєтеся надсилати сповіщення** {totalMembers} людям** у **{timezones, number} {timezones, plural, one {timezone} other {timezones}}**. Ви впевнені, що хочете це зробити?",
"notify_all.title.confirm": "Підтвердьте надсилання повідомлень на весь канал",
"password_form.change": "Змінити пароль",
......@@ -2483,7 +2483,7 @@
"sidebar.mainMenu": "Головне меню",
"sidebar.moreElips": "Більше...",
"sidebar.removeList": "Видалити зі списку ",
"sidebar.tutorialScreen1.body": "**Канали** організовують розмови по різних темах. Вони відкриті для всіх у вашій команді. Для відправлення приватних повідомлень використовуйте **Прямі повідомлення** для однієї особи або **Приватні канали** для декількох людей.",
"sidebar.tutorialScreen1.body": "**Канали** організовують розмови по різних темах. Вони відкриті для всіх у вашій команді. Для відправлення приватних повідомлень використовуйте **Прямі повідомлення** для однієї особи або **Приватні канали** для декількох людей. ",
"sidebar.tutorialScreen1.title": "Канали",
"sidebar.tutorialScreen2.body1": "Нижче наведено два публічні канали:",
"sidebar.tutorialScreen2.body2": "**{citysquare}** є місцем спілкування в команді. Кожен у вашій команді є учасником цього каналу.",
......@@ -2532,7 +2532,7 @@
"signup.office365": "Office 365 ",
"signup.saml.icon": "Значок SAML",
"signup.title": "Створити обліковий запис за допомогою:",
"status_dropdown.set_away": "Далеко ",
"status_dropdown.set_away": "Не на місці ",
"status_dropdown.set_dnd": "Не турбувати ",
"status_dropdown.set_dnd.extra": "Відключає настільні і Push-повідомлення",
"status_dropdown.set_offline": "Offline ",
......@@ -2861,16 +2861,16 @@
"user.settings.push_notification.allActivityAway": "Для всієї активності, коли відійшов або offline",
"user.settings.push_notification.allActivityOffline": "Для всієї активності коли offline",
"user.settings.push_notification.allActivityOnline": "Для всієї активності коли online, відійшов або offline",
"user.settings.push_notification.away": "Відсутня або не в мережі",
"user.settings.push_notification.away": "Не на місці або не в мережі",
"user.settings.push_notification.disabled": "Email повідомлення відключені ",
"user.settings.push_notification.disabled_long": "Системний адміністратор не активував сповіщення електронною поштою. ",
"user.settings.push_notification.info": "Сигнали сповіщень надсилаються на ваш мобільний пристрій, коли в Mattermost є активність.",
"user.settings.push_notification.offline": "Не в мережі",
"user.settings.push_notification.online": "У мережі, немає на місці або не в мережі",
"user.settings.push_notification.onlyMentions": "Тільки для згадування та прямих повідомлень ",
"user.settings.push_notification.onlyMentionsAway": "При згадках і особистих повідомленнях, коли відійшов або не в мережі",
"user.settings.push_notification.onlyMentionsAway": "При згадках і особистих повідомленнях, якщо відійшов або не в мережі",
"user.settings.push_notification.onlyMentionsOffline": "При згадках і особистих повідомленнях, коли не в мережі",
"user.settings.push_notification.onlyMentionsOnline": "При згадках і особистих повідомленнях, коли в мережі, відійшов або не в мережі",
"user.settings.push_notification.onlyMentionsOnline": "При згадках і особистих повідомленнях, якщо в мережі, відійшов або не в мережі",
"user.settings.push_notification.send": "Надіслати мобільне push-повідомлення ",
"user.settings.push_notification.status_info": "Повідомлення на телефон будуть надсилатися тільки якщо ваш статус буде збігатися з обраним вище.",
"user.settings.security.active": "Активний",
......
......@@ -11581,8 +11581,8 @@
"dev": true
},
"mattermost-redux": {
"version": "github:mattermost/mattermost-redux#2219aee26c50f953e98c494ef158f832c73155b0",
"from": "github:mattermost/mattermost-redux#2219aee26c50f953e98c494ef158f832c73155b0",
"version": "git+https://gitlab.collabora.com/sysadmin/mattermost/mattermost-redux#collabora-5.7",
"from": "git+https://gitlab.collabora.com/sysadmin/mattermost/mattermost-redux#collabora-5.7",
"requires": {
"deep-equal": "1.0.1",
"eslint-plugin-header": "1.2.0",
......
......@@ -703,7 +703,7 @@
.post {
@include legacy-pie-clearfix;
max-width: 100%;
padding: 8px .5em 0 1em;
padding: 8px .5em 0 1.5em;
position: relative;
word-wrap: break-word;
......@@ -1126,7 +1126,7 @@
&.same--root {
&.same--user {
padding: 0 .5em 0 1em;
padding: 0 .5em 0 1.5em;
&:hover,
&.post--hovered {
......
......@@ -474,7 +474,7 @@
.flag-icon__container {
.sidebar--right & {
left: 26px;
left: 30px;
}
}
......@@ -490,7 +490,7 @@
width: 51px;
.sidebar--right & {
left: -24px;
left: -20px;
}
&:hover {
......
......@@ -327,6 +327,18 @@
}
}
&.phabricator {
background: #4a5f88;
&:hover {
background: darken(#4a5f88, 10%);
}
.icon {
background-image: url('../images/phabricatorLogo.png');
}
}
&.google {
background: #dd4b39;
......
......@@ -738,6 +738,7 @@ export const Constants = {
OFFTOPIC_CHANNEL: 'off-topic',
OFFTOPIC_CHANNEL_UI_NAME: 'Off-Topic',
GITLAB_SERVICE: 'gitlab',
PHABRICATOR_SERVICE: 'phabricator',
GOOGLE_SERVICE: 'google',
OFFICE365_SERVICE: 'office365',
EMAIL_SERVICE: 'email',
......@@ -1291,13 +1292,14 @@ export const Constants = {
AUTOCOMPLETE_TIMEOUT: 100,
ANIMATION_TIMEOUT: 1000,
SEARCH_TIMEOUT_MILLISECONDS: 100,
DIAGNOSTICS_SEGMENT_KEY: 'fwb7VPbFeQ7SKp3wHm1RzFUuXZudqVok',
DIAGNOSTICS_SEGMENT_KEY: '',
TEST_ID_COUNT: 0,
CENTER: 'center',
RHS: 'rhs',
RHS_ROOT: 'rhsroot',
TEAMMATE_NAME_DISPLAY: {
SHOW_USERNAME: 'username',
SHOW_NICKNAME_USERNAME: 'nickname_username',
SHOW_NICKNAME_FULLNAME: 'nickname_full_name',
SHOW_FULLNAME: 'full_name',
},
......
......@@ -17,6 +17,7 @@ import PostEmoji from 'components/post_emoji';
* - images - If specified, markdown images are replaced with the MarkdownImage component. Defaults to true.
* - imageProps - If specified, any extra props that should be passed into the MarkdownImage component.
* - latex - If specified, latex is replaced with the LatexBlock component. Defaults to true.
* - imagesMetadata - the dimensions of the image as retrieved from post.metadata.images.
*/
export function messageHtmlToComponent(html, isRHS, options = {}) {
if (!html) {
......@@ -81,7 +82,7 @@ export function messageHtmlToComponent(html, isRHS, options = {}) {
const callMarkdownImage = (
<MarkdownImage
className={className}
dimensions={options.imagesMetadata[attribs.src]}
dimensions={options.imagesMetadata && options.imagesMetadata[attribs.src]}
{...attribs}
{...options.imageProps}
/>
......
......@@ -262,6 +262,7 @@ var config = {
filename: 'root.html',
inject: 'head',
template: 'root.html',
favicon: 'images/favicon/favicon-16x16.png',
}),
new CopyWebpackPlugin([
{from: 'images/emoji', to: 'emoji'},
......