summaryrefslogtreecommitdiffstats
path: root/webapp
diff options
context:
space:
mode:
Diffstat (limited to 'webapp')
-rw-r--r--webapp/actions/admin_actions.jsx404
-rw-r--r--webapp/actions/channel_actions.jsx172
-rw-r--r--webapp/actions/file_actions.jsx26
-rw-r--r--webapp/actions/global_actions.jsx26
-rw-r--r--webapp/actions/post_actions.jsx89
-rw-r--r--webapp/actions/team_actions.jsx53
-rw-r--r--webapp/actions/user_actions.jsx326
-rw-r--r--webapp/actions/webrtc_actions.jsx16
-rw-r--r--webapp/client/client.jsx35
-rw-r--r--webapp/components/activity_log_modal.jsx20
-rw-r--r--webapp/components/admin_console/admin_settings.jsx5
-rw-r--r--webapp/components/admin_console/admin_sidebar_header.jsx2
-rw-r--r--webapp/components/admin_console/admin_team_members_dropdown.jsx18
-rw-r--r--webapp/components/admin_console/brand_image_setting.jsx3
-rw-r--r--webapp/components/admin_console/cluster_table_container.jsx10
-rw-r--r--webapp/components/admin_console/compliance_reports.jsx3
-rw-r--r--webapp/components/admin_console/email_connection_test.jsx5
-rw-r--r--webapp/components/admin_console/ldap_test_button.jsx5
-rw-r--r--webapp/components/admin_console/license_settings.jsx8
-rw-r--r--webapp/components/admin_console/logs.jsx3
-rw-r--r--webapp/components/admin_console/policy_settings.jsx49
-rw-r--r--webapp/components/admin_console/post_edit_setting.jsx99
-rw-r--r--webapp/components/admin_console/purge_caches.jsx10
-rw-r--r--webapp/components/admin_console/radio_setting.jsx63
-rw-r--r--webapp/components/admin_console/recycle_db.jsx5
-rw-r--r--webapp/components/admin_console/reload_config.jsx8
-rw-r--r--webapp/components/admin_console/reset_password_modal.jsx5
-rw-r--r--webapp/components/admin_console/saml_settings.jsx9
-rw-r--r--webapp/components/admin_console/sync_now_button.jsx5
-rw-r--r--webapp/components/admin_console/team_users.jsx8
-rw-r--r--webapp/components/analytics/system_analytics.jsx34
-rw-r--r--webapp/components/authorize.jsx7
-rw-r--r--webapp/components/channel_header.jsx21
-rw-r--r--webapp/components/channel_invite_modal.jsx8
-rw-r--r--webapp/components/channel_members_dropdown.jsx246
-rw-r--r--webapp/components/channel_members_modal.jsx153
-rw-r--r--webapp/components/channel_select.jsx14
-rw-r--r--webapp/components/channel_switch_modal.jsx13
-rw-r--r--webapp/components/claim/components/email_to_ldap.jsx4
-rw-r--r--webapp/components/claim/components/email_to_oauth.jsx4
-rw-r--r--webapp/components/claim/components/oauth_to_email.jsx12
-rw-r--r--webapp/components/create_comment.jsx7
-rw-r--r--webapp/components/create_post.jsx3
-rw-r--r--webapp/components/delete_channel_modal.jsx14
-rw-r--r--webapp/components/delete_post_modal.jsx24
-rw-r--r--webapp/components/do_verify_email.jsx15
-rw-r--r--webapp/components/edit_channel_header_modal.jsx12
-rw-r--r--webapp/components/edit_channel_purpose_modal.jsx7
-rw-r--r--webapp/components/edit_post_modal.jsx27
-rw-r--r--webapp/components/file_attachment_list.jsx2
-rw-r--r--webapp/components/file_preview.jsx5
-rw-r--r--webapp/components/file_upload.jsx40
-rw-r--r--webapp/components/integrations/components/installed_oauth_app.jsx4
-rw-r--r--webapp/components/invite_member_modal.jsx4
-rw-r--r--webapp/components/logged_in.jsx25
-rw-r--r--webapp/components/login/login_controller.jsx77
-rw-r--r--webapp/components/member_list_channel.jsx179
-rw-r--r--webapp/components/member_list_team.jsx16
-rw-r--r--webapp/components/mfa/mfa_controller.jsx23
-rw-r--r--webapp/components/more_channels.jsx9
-rw-r--r--webapp/components/navbar.jsx22
-rw-r--r--webapp/components/new_channel_modal.jsx4
-rw-r--r--webapp/components/password_reset_form.jsx7
-rw-r--r--webapp/components/popover_list_members.jsx94
-rw-r--r--webapp/components/post_view/components/post.jsx16
-rw-r--r--webapp/components/post_view/components/post_attachment_oembed.jsx108
-rw-r--r--webapp/components/post_view/components/post_attachment_opengraph.jsx212
-rw-r--r--webapp/components/post_view/components/post_body.jsx4
-rw-r--r--webapp/components/post_view/components/post_body_additional_content.jsx93
-rw-r--r--webapp/components/post_view/components/post_image.jsx6
-rw-r--r--webapp/components/post_view/components/post_info.jsx40
-rw-r--r--webapp/components/post_view/components/post_list.jsx23
-rw-r--r--webapp/components/post_view/components/post_message_container.jsx2
-rw-r--r--webapp/components/post_view/components/post_message_view.jsx38
-rw-r--r--webapp/components/post_view/components/post_time.jsx5
-rw-r--r--webapp/components/post_view/components/providers.json376
-rw-r--r--webapp/components/post_view/post_view_controller.jsx10
-rw-r--r--webapp/components/profile_popover.jsx35
-rw-r--r--webapp/components/rhs_comment.jsx28
-rw-r--r--webapp/components/rhs_root_post.jsx28
-rw-r--r--webapp/components/root.jsx27
-rw-r--r--webapp/components/search_bar.jsx31
-rw-r--r--webapp/components/search_results.jsx6
-rw-r--r--webapp/components/search_results_item.jsx4
-rw-r--r--webapp/components/setting_item_max.jsx4
-rw-r--r--webapp/components/setting_picture.jsx4
-rw-r--r--webapp/components/should_verify_email.jsx5
-rw-r--r--webapp/components/sidebar_header.jsx13
-rw-r--r--webapp/components/sidebar_header_dropdown.jsx5
-rw-r--r--webapp/components/signup/components/signup_email.jsx29
-rw-r--r--webapp/components/signup/components/signup_ldap.jsx19
-rw-r--r--webapp/components/signup/signup_controller.jsx14
-rw-r--r--webapp/components/suggestion/at_mention_provider.jsx5
-rw-r--r--webapp/components/suggestion/channel_mention_provider.jsx114
-rw-r--r--webapp/components/suggestion/emoticon_provider.jsx23
-rw-r--r--webapp/components/suggestion/search_user_provider.jsx2
-rw-r--r--webapp/components/suggestion/suggestion_box.jsx6
-rw-r--r--webapp/components/suggestion/switch_channel_provider.jsx2
-rw-r--r--webapp/components/team_general_tab.jsx108
-rw-r--r--webapp/components/user_list_row.jsx2
-rw-r--r--webapp/components/user_profile.jsx2
-rw-r--r--webapp/components/user_settings/user_settings_advanced.jsx2
-rw-r--r--webapp/components/user_settings/user_settings_display.jsx10
-rw-r--r--webapp/components/user_settings/user_settings_general.jsx6
-rw-r--r--webapp/components/user_settings/user_settings_notifications.jsx7
-rw-r--r--webapp/components/user_settings/user_settings_security.jsx62
-rw-r--r--webapp/components/webrtc/components/webrtc_notification.jsx2
-rw-r--r--webapp/components/webrtc/webrtc_controller.jsx8
-rw-r--r--webapp/i18n/de.json67
-rw-r--r--webapp/i18n/en.json83
-rw-r--r--webapp/i18n/es.json89
-rw-r--r--webapp/i18n/fr.json403
-rw-r--r--webapp/i18n/ja.json71
-rw-r--r--webapp/i18n/ko.json59
-rw-r--r--webapp/i18n/nl.json61
-rw-r--r--webapp/i18n/pt-BR.json95
-rw-r--r--webapp/i18n/ru.json83
-rw-r--r--webapp/i18n/zh_CN.json79
-rw-r--r--webapp/i18n/zh_TW.json57
-rw-r--r--webapp/package.json2
-rw-r--r--webapp/root.html45
-rw-r--r--webapp/sass/base/_typography.scss5
-rw-r--r--webapp/sass/layout/_post.scss48
-rw-r--r--webapp/sass/layout/_webhooks.scss20
-rw-r--r--webapp/sass/responsive/_mobile.scss5
-rw-r--r--webapp/sass/responsive/_tablet.scss2
-rw-r--r--webapp/stores/channel_store.jsx56
-rw-r--r--webapp/stores/notification_store.jsx2
-rw-r--r--webapp/stores/opengraph_store.jsx68
-rw-r--r--webapp/stores/post_store.jsx34
-rw-r--r--webapp/stores/team_store.jsx2
-rw-r--r--webapp/stores/user_store.jsx2
-rw-r--r--webapp/tests/client_team.test.jsx25
-rw-r--r--webapp/tests/utils_get_nearest_point.test.jsx35
-rw-r--r--webapp/utils/async_client.jsx8
-rw-r--r--webapp/utils/channel_intro_messages.jsx2
-rw-r--r--webapp/utils/channel_utils.jsx10
-rw-r--r--webapp/utils/commons.jsx36
-rw-r--r--webapp/utils/constants.jsx73
-rw-r--r--webapp/utils/markdown.jsx37
-rw-r--r--webapp/utils/post_utils.jsx45
-rw-r--r--webapp/utils/syntax_highlighting.jsx22
-rw-r--r--webapp/utils/text_formatting.jsx13
-rw-r--r--webapp/utils/utils.jsx20
144 files changed, 4178 insertions, 1758 deletions
diff --git a/webapp/actions/admin_actions.jsx b/webapp/actions/admin_actions.jsx
new file mode 100644
index 000000000..73b73c130
--- /dev/null
+++ b/webapp/actions/admin_actions.jsx
@@ -0,0 +1,404 @@
+// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import Client from 'client/web_client.jsx';
+import * as AsyncClient from 'utils/async_client.jsx';
+import {browserHistory} from 'react-router/es6';
+
+export function revokeSession(altId, success, error) {
+ Client.revokeSession(altId,
+ () => {
+ AsyncClient.getSessions();
+ if (success) {
+ success();
+ }
+ },
+ (err) => {
+ if (error) {
+ error(err);
+ }
+ }
+ );
+}
+
+export function saveConfig(config, success, error) {
+ Client.saveConfig(
+ config,
+ () => {
+ if (success) {
+ success();
+ }
+ },
+ (err) => {
+ if (error) {
+ error(err);
+ }
+ }
+ );
+}
+
+export function reloadConfig(success, error) {
+ Client.reloadConfig(
+ () => {
+ AsyncClient.getConfig();
+ if (success) {
+ success();
+ }
+ },
+ (err) => {
+ if (error) {
+ error(err);
+ }
+ }
+ );
+}
+
+export function adminResetMfa(userId, success, error) {
+ Client.adminResetMfa(
+ userId,
+ () => {
+ AsyncClient.getUser(userId);
+
+ if (success) {
+ success();
+ }
+ },
+ (err) => {
+ if (error) {
+ error(err);
+ }
+ }
+ );
+}
+
+export function getClusterStatus(success, error) {
+ Client.getClusterStatus(
+ (data) => {
+ if (success) {
+ success(data);
+ }
+ },
+ (err) => {
+ AsyncClient.dispatchError(err, 'getClusterStatus');
+ if (error) {
+ error(err);
+ }
+ }
+ );
+}
+
+export function saveComplianceReports(job, success, error) {
+ Client.saveComplianceReports(
+ job,
+ () => {
+ if (success) {
+ success();
+ }
+ },
+ (err) => {
+ if (error) {
+ error(err);
+ }
+ }
+ );
+}
+
+export function testEmail(config, success, error) {
+ Client.testEmail(
+ config,
+ () => {
+ if (success) {
+ success();
+ }
+ },
+ (err) => {
+ if (error) {
+ error(err);
+ }
+ }
+ );
+}
+
+export function ldapTest(success, error) {
+ Client.ldapTest(
+ () => {
+ if (success) {
+ success();
+ }
+ },
+ (err) => {
+ if (error) {
+ error(err);
+ }
+ }
+ );
+}
+
+export function invalidateAllCaches(success, error) {
+ Client.invalidateAllCaches(
+ () => {
+ if (success) {
+ success();
+ }
+ },
+ (err) => {
+ if (error) {
+ error(err);
+ }
+ }
+ );
+}
+
+export function recycleDatabaseConnection(success, error) {
+ Client.recycleDatabaseConnection(
+ () => {
+ if (success) {
+ success();
+ }
+ },
+ (err) => {
+ if (error) {
+ error(err);
+ }
+ }
+ );
+}
+
+export function adminResetPassword(userId, password, success, error) {
+ Client.adminResetPassword(
+ userId,
+ password,
+ () => {
+ if (success) {
+ success();
+ }
+ },
+ (err) => {
+ if (error) {
+ error(err);
+ }
+ }
+ );
+}
+
+export function samlCertificateStatus(success, error) {
+ Client.samlCertificateStatus(
+ (data) => {
+ if (success) {
+ success(data);
+ }
+ },
+ (err) => {
+ if (error) {
+ error(err);
+ }
+ }
+ );
+}
+
+export function ldapSyncNow(success, error) {
+ Client.ldapSyncNow(
+ () => {
+ if (success) {
+ success();
+ }
+ },
+ (err) => {
+ if (error) {
+ error(err);
+ }
+ }
+ );
+}
+
+export function getOAuthAppInfo(clientId, success, error) {
+ Client.getOAuthAppInfo(
+ clientId,
+ (data) => {
+ if (success) {
+ success(data);
+ }
+ },
+ (err) => {
+ if (error) {
+ error(err);
+ }
+ }
+ );
+}
+
+export function allowOAuth2(params, success, error) {
+ const responseType = params.response_type;
+ const clientId = params.client_id;
+ const redirectUri = params.redirect_uri;
+ const state = params.state;
+ const scope = params.scope;
+
+ Client.allowOAuth2(responseType, clientId, redirectUri, state, scope,
+ (data) => {
+ if (success) {
+ success(data);
+ }
+ },
+ (err) => {
+ if (error) {
+ error(err);
+ }
+ }
+ );
+}
+
+export function emailToLdap(loginId, password, token, ldapId, ldapPassword, success, error) {
+ Client.emailToLdap(
+ loginId,
+ password,
+ token,
+ ldapId,
+ ldapPassword,
+ (data) => {
+ if (success) {
+ success(data);
+ }
+ },
+ (err) => {
+ if (error) {
+ error(err);
+ }
+ }
+ );
+}
+
+export function emailToOAuth(loginId, password, token, newType, success, error) {
+ Client.emailToOAuth(
+ loginId,
+ password,
+ token,
+ newType,
+ (data) => {
+ if (success) {
+ success(data);
+ }
+ },
+ (err) => {
+ if (error) {
+ error(err);
+ }
+ }
+ );
+}
+
+export function oauthToEmail(email, password, success, error) {
+ Client.oauthToEmail(
+ email,
+ password,
+ (data) => {
+ if (data.follow_link) {
+ browserHistory.push(data.follow_link);
+ }
+
+ if (success) {
+ success(data);
+ }
+ },
+ (err) => {
+ if (error) {
+ error(err);
+ }
+ }
+ );
+}
+
+export function regenerateOAuthAppSecret(oauthAppId, success, error) {
+ Client.regenerateOAuthAppSecret(
+ oauthAppId,
+ (data) => {
+ if (success) {
+ success(data);
+ }
+ },
+ (err) => {
+ if (error) {
+ error(err);
+ }
+ }
+ );
+}
+
+export function uploadBrandImage(brandImage, success, error) {
+ Client.uploadBrandImage(
+ brandImage,
+ () => {
+ if (success) {
+ success();
+ }
+ },
+ (err) => {
+ if (error) {
+ error(err);
+ }
+ }
+ );
+}
+
+export function uploadLicenseFile(file, success, error) {
+ Client.uploadLicenseFile(
+ file,
+ () => {
+ if (success) {
+ success();
+ }
+ },
+ (err) => {
+ if (error) {
+ error(err);
+ }
+ }
+ );
+}
+
+export function removeLicenseFile(success, error) {
+ Client.removeLicenseFile(
+ () => {
+ if (success) {
+ success();
+ }
+ },
+ (err) => {
+ if (error) {
+ error(err);
+ }
+ }
+ );
+}
+
+export function uploadCertificateFile(certificateFile, success, error) {
+ Client.uploadCertificateFile(
+ certificateFile,
+ () => {
+ if (success) {
+ success();
+ }
+ },
+ (err) => {
+ if (error) {
+ error(err);
+ }
+ }
+ );
+}
+
+export function removeCertificateFile(certificateId, success, error) {
+ Client.removeCertificateFile(
+ certificateId,
+ () => {
+ if (success) {
+ success();
+ }
+ },
+ (err) => {
+ if (error) {
+ error(err);
+ }
+ }
+ );
+}
diff --git a/webapp/actions/channel_actions.jsx b/webapp/actions/channel_actions.jsx
index 204e6f9f1..4b4e3e10c 100644
--- a/webapp/actions/channel_actions.jsx
+++ b/webapp/actions/channel_actions.jsx
@@ -6,6 +6,7 @@ import AppDispatcher from 'dispatcher/app_dispatcher.jsx';
import TeamStore from 'stores/team_store.jsx';
import UserStore from 'stores/user_store.jsx';
import ChannelStore from 'stores/channel_store.jsx';
+import * as ChannelUtils from 'utils/channel_utils.jsx';
import PreferenceStore from 'stores/preference_store.jsx';
import {loadProfilesAndTeamMembersForDMSidebar} from 'actions/user_actions.jsx';
@@ -48,7 +49,14 @@ export function executeCommand(message, args, success, error) {
msg = '/shortcuts';
}
}
- Client.executeCommand(msg, args, success, error);
+ Client.executeCommand(msg, args, success,
+ (err) => {
+ AsyncClient.dispatchError(err, 'executeCommand');
+
+ if (error) {
+ error(err);
+ }
+ });
}
export function setChannelAsRead(channelIdParam) {
@@ -101,6 +109,9 @@ export function removeUserFromChannel(channelId, userId, success, error) {
}
UserStore.emitInChannelChange();
+ ChannelStore.removeMemberInChannel(channelId, userId);
+ ChannelStore.emitChange();
+
if (success) {
success(data);
}
@@ -115,6 +126,48 @@ export function removeUserFromChannel(channelId, userId, success, error) {
);
}
+export function makeUserChannelAdmin(channelId, userId, success, error) {
+ Client.updateChannelMemberRoles(
+ channelId,
+ userId,
+ 'channel_user channel_admin',
+ () => {
+ AsyncClient.getChannelMember(channelId, userId);
+ getChannelMembersForUserIds(channelId, [userId]);
+
+ if (success) {
+ success();
+ }
+ },
+ (err) => {
+ if (error) {
+ error(err);
+ }
+ }
+ );
+}
+
+export function makeUserChannelMember(channelId, userId, success, error) {
+ Client.updateChannelMemberRoles(
+ channelId,
+ userId,
+ 'channel_user',
+ () => {
+ AsyncClient.getChannelMember(channelId, userId);
+ getChannelMembersForUserIds(channelId, [userId]);
+
+ if (success) {
+ success();
+ }
+ },
+ (err) => {
+ if (error) {
+ error(err);
+ }
+ }
+ );
+}
+
export function openDirectChannelToUser(user, success, error) {
const channelName = Utils.getDirectChannelName(UserStore.getCurrentId(), user.id);
const channel = ChannelStore.getByName(channelName);
@@ -311,3 +364,120 @@ export function createChannel(channel, success, error) {
}
);
}
+
+export function updateChannelPurpose(channelId, purposeValue, success, error) {
+ Client.updateChannelPurpose(
+ channelId,
+ purposeValue,
+ () => {
+ AsyncClient.getChannel(channelId);
+
+ if (success) {
+ success();
+ }
+ },
+ (err) => {
+ if (error) {
+ error(err);
+ }
+ }
+ );
+}
+
+export function updateChannelHeader(channelId, header, success, error) {
+ Client.updateChannelHeader(
+ channelId,
+ header,
+ (channelData) => {
+ AppDispatcher.handleServerAction({
+ type: ActionTypes.RECEIVED_CHANNEL,
+ channel: channelData
+ });
+
+ if (success) {
+ success(channelData);
+ }
+ },
+ (err) => {
+ if (error) {
+ error(err);
+ }
+ }
+ );
+}
+
+export function getChannelMembersForUserIds(channelId, userIds, success, error) {
+ Client.getChannelMembersByIds(
+ channelId,
+ userIds,
+ (data) => {
+ const memberMap = {};
+ for (let i = 0; i < data.length; i++) {
+ memberMap[data[i].user_id] = data[i];
+ }
+
+ AppDispatcher.handleServerAction({
+ type: ActionTypes.RECEIVED_MEMBERS_IN_CHANNEL,
+ channel_id: channelId,
+ channel_members: memberMap
+ });
+
+ if (success) {
+ success(data);
+ }
+ },
+ (err) => {
+ AsyncClient.dispatchError(err, 'getChannelMembersByIds');
+
+ if (error) {
+ error(err);
+ }
+ }
+ );
+}
+
+export function leaveChannel(channelId, success, error) {
+ Client.leaveChannel(channelId,
+ () => {
+ loadChannelsForCurrentUser();
+
+ if (ChannelUtils.isFavoriteChannelId(channelId)) {
+ unmarkFavorite(channelId);
+ }
+
+ const townsquare = ChannelStore.getByName('town-square');
+ browserHistory.push(TeamStore.getCurrentTeamRelativeUrl() + '/channels/' + townsquare.name);
+
+ if (success) {
+ success();
+ }
+ },
+ (err) => {
+ AsyncClient.dispatchError(err, 'handleLeave');
+
+ if (error) {
+ error(err);
+ }
+ }
+ );
+}
+
+export function deleteChannel(channelId, success, error) {
+ Client.deleteChannel(
+ channelId,
+ () => {
+ loadChannelsForCurrentUser();
+
+ if (success) {
+ success();
+ }
+ },
+ (err) => {
+ AsyncClient.dispatchError(err, 'handleDelete');
+
+ if (error) {
+ error(err);
+ }
+ }
+ );
+}
diff --git a/webapp/actions/file_actions.jsx b/webapp/actions/file_actions.jsx
new file mode 100644
index 000000000..0399a2c28
--- /dev/null
+++ b/webapp/actions/file_actions.jsx
@@ -0,0 +1,26 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import * as AsyncClient from 'utils/async_client.jsx';
+import Client from 'client/web_client.jsx';
+
+export function uploadFile(file, name, channelId, clientId, success, error) {
+ Client.uploadFile(
+ file,
+ name,
+ channelId,
+ clientId,
+ (data) => {
+ if (success) {
+ success(data);
+ }
+ },
+ (err) => {
+ AsyncClient.dispatchError(err, 'uploadFile');
+
+ if (error) {
+ error(err);
+ }
+ }
+ );
+}
diff --git a/webapp/actions/global_actions.jsx b/webapp/actions/global_actions.jsx
index ea077d6eb..23e19f22f 100644
--- a/webapp/actions/global_actions.jsx
+++ b/webapp/actions/global_actions.jsx
@@ -256,7 +256,7 @@ export function emitLoadMorePostsFocusedTopEvent() {
}
export function loadMorePostsTop(id, isFocusPost) {
- const earliestPostId = PostStore.getEarliestPost(id).id;
+ const earliestPostId = PostStore.getEarliestPostFromPage(id).id;
if (PostStore.requestVisibilityIncrease(id, Constants.POST_CHUNK_SIZE)) {
loadPostsBefore(earliestPostId, 0, Constants.POST_CHUNK_SIZE, isFocusPost);
}
@@ -596,3 +596,27 @@ export function redirectUserToDefaultTeam() {
browserHistory.push('/select_team');
}
}
+
+requestOpenGraphMetadata.openGraphMetadataOnGoingRequests = {}; // Format: {<url>: true}
+export function requestOpenGraphMetadata(url) {
+ const onself = requestOpenGraphMetadata;
+
+ if (!onself.openGraphMetadataOnGoingRequests[url]) {
+ onself.openGraphMetadataOnGoingRequests[url] = true;
+
+ Client.getOpenGraphMetadata(url,
+ (data) => {
+ AppDispatcher.handleServerAction({
+ type: ActionTypes.RECIVED_OPEN_GRAPH_METADATA,
+ url,
+ data
+ });
+ delete onself.openGraphMetadataOnGoingRequests[url];
+ },
+ (err) => {
+ AsyncClient.dispatchError(err, 'getOpenGraphMetadata');
+ delete onself.openGraphMetadataOnGoingRequests[url];
+ }
+ );
+ }
+}
diff --git a/webapp/actions/post_actions.jsx b/webapp/actions/post_actions.jsx
index 0e48fb0e8..61f193b66 100644
--- a/webapp/actions/post_actions.jsx
+++ b/webapp/actions/post_actions.jsx
@@ -115,12 +115,16 @@ export function getFlaggedPosts() {
);
}
-export function loadPosts(channelId = ChannelStore.getCurrentId()) {
+export function loadPosts(channelId = ChannelStore.getCurrentId(), isPost = false) {
const postList = PostStore.getAllPosts(channelId);
const latestPostTime = PostStore.getLatestPostFromPageTime(channelId);
- if (!postList || Object.keys(postList).length === 0 || postList.order.length < Constants.POST_CHUNK_SIZE || latestPostTime === 0) {
- loadPostsPage(channelId, Constants.POST_CHUNK_SIZE);
+ if (
+ !postList || Object.keys(postList).length === 0 ||
+ (!isPost && postList.order.length < Constants.POST_CHUNK_SIZE) ||
+ latestPostTime === 0
+ ) {
+ loadPostsPage(channelId, Constants.POST_CHUNK_SIZE, isPost);
return;
}
@@ -133,7 +137,8 @@ export function loadPosts(channelId = ChannelStore.getCurrentId()) {
id: channelId,
before: true,
numRequested: 0,
- post_list: data
+ post_list: data,
+ isPost
});
loadProfilesForPosts(data.posts);
@@ -145,7 +150,7 @@ export function loadPosts(channelId = ChannelStore.getCurrentId()) {
);
}
-export function loadPostsPage(channelId = ChannelStore.getCurrentId(), max = Constants.POST_CHUNK_SIZE) {
+export function loadPostsPage(channelId = ChannelStore.getCurrentId(), max = Constants.POST_CHUNK_SIZE, isPost = false) {
const postList = PostStore.getAllPosts(channelId);
// if we already have more than POST_CHUNK_SIZE posts,
@@ -167,7 +172,9 @@ export function loadPostsPage(channelId = ChannelStore.getCurrentId(), max = Con
before: true,
numRequested: numPosts,
checkLatest: true,
- post_list: data
+ checkEarliest: true,
+ post_list: data,
+ isPost
});
loadProfilesForPosts(data.posts);
@@ -195,6 +202,7 @@ export function loadPostsBefore(postId, offset, numPost, isPost) {
type: ActionTypes.RECEIVED_POSTS,
id: channelId,
before: true,
+ checkEarliest: true,
numRequested: numPost,
post_list: data,
isPost
@@ -363,7 +371,76 @@ export function createPost(post, doLoadPost, success, error) {
);
}
+export function updatePost(post, success, isPost) {
+ Client.updatePost(
+ post,
+ () => {
+ loadPosts(post.channel_id, isPost);
+
+ if (success) {
+ success();
+ }
+ },
+ (err) => {
+ AsyncClient.dispatchError(err, 'updatePost');
+ });
+}
+
export function removePostFromStore(post) {
PostStore.removePost(post);
PostStore.emitChange();
}
+
+export function deletePost(channelId, post, success, error) {
+ Client.deletePost(
+ channelId,
+ post.id,
+ () => {
+ removePostFromStore(post);
+ if (post.id === PostStore.getSelectedPostId()) {
+ AppDispatcher.handleServerAction({
+ type: ActionTypes.RECEIVED_POST_SELECTED,
+ postId: null
+ });
+ }
+
+ if (success) {
+ success();
+ }
+ },
+ (err) => {
+ AsyncClient.dispatchError(err, 'deletePost');
+
+ if (error) {
+ error(err);
+ }
+ }
+ );
+}
+
+export function performSearch(terms, isMentionSearch, success, error) {
+ Client.search(
+ terms,
+ isMentionSearch,
+ (data) => {
+ AppDispatcher.handleServerAction({
+ type: ActionTypes.RECEIVED_SEARCH,
+ results: data,
+ is_mention_search: isMentionSearch
+ });
+
+ loadProfilesForPosts(data.posts);
+
+ if (success) {
+ success(data);
+ }
+ },
+ (err) => {
+ AsyncClient.dispatchError(err, 'search');
+
+ if (error) {
+ error(err);
+ }
+ }
+ );
+}
diff --git a/webapp/actions/team_actions.jsx b/webapp/actions/team_actions.jsx
index 3a86bada9..e23fe1e5d 100644
--- a/webapp/actions/team_actions.jsx
+++ b/webapp/actions/team_actions.jsx
@@ -60,7 +60,10 @@ export function removeUserFromTeam(teamId, userId, success, error) {
userId,
() => {
TeamStore.removeMemberInTeam(teamId, userId);
+ UserStore.removeProfileFromTeam(teamId, userId);
+ UserStore.emitInTeamChange();
AsyncClient.getUser(userId);
+ AsyncClient.getTeamStats(teamId);
if (success) {
success();
@@ -92,3 +95,53 @@ export function updateTeamMemberRoles(teamId, userId, newRoles, success, error)
}
);
}
+
+export function addUserToTeamFromInvite(data, hash, inviteId, success, error) {
+ Client.addUserToTeamFromInvite(
+ data,
+ hash,
+ inviteId,
+ (team) => {
+ if (success) {
+ success(team);
+ }
+ },
+ (err) => {
+ if (error) {
+ error(err);
+ }
+ }
+ );
+}
+
+export function getInviteInfo(inviteId, success, error) {
+ Client.getInviteInfo(
+ inviteId,
+ (inviteData) => {
+ if (success) {
+ success(inviteData);
+ }
+ },
+ (err) => {
+ if (error) {
+ error(err);
+ }
+ }
+ );
+}
+
+export function inviteMembers(data, success, error) {
+ Client.inviteMembers(
+ data,
+ () => {
+ if (success) {
+ success();
+ }
+ },
+ (err) => {
+ if (err) {
+ error(err);
+ }
+ }
+ );
+}
diff --git a/webapp/actions/user_actions.jsx b/webapp/actions/user_actions.jsx
index 0f5fb0731..73a84a7c6 100644
--- a/webapp/actions/user_actions.jsx
+++ b/webapp/actions/user_actions.jsx
@@ -4,17 +4,22 @@
import AppDispatcher from 'dispatcher/app_dispatcher.jsx';
import PreferenceStore from 'stores/preference_store.jsx';
+import BrowserStore from 'stores/browser_store.jsx';
import TeamStore from 'stores/team_store.jsx';
import UserStore from 'stores/user_store.jsx';
import ChannelStore from 'stores/channel_store.jsx';
+import * as GlobalActions from 'actions/global_actions.jsx';
+import {getChannelMembersForUserIds} from 'actions/channel_actions.jsx';
import {loadStatusesForProfilesList, loadStatusesForProfilesMap} from 'actions/status_actions.jsx';
import {getDirectChannelName} from 'utils/utils.jsx';
+
import * as AsyncClient from 'utils/async_client.jsx';
import Client from 'client/web_client.jsx';
import {ActionTypes, Preferences} from 'utils/constants.jsx';
+import {browserHistory} from 'react-router/es6';
export function switchFromLdapToEmail(email, password, token, ldapPassword, onSuccess, onError) {
Client.ldapToEmail(
@@ -58,6 +63,34 @@ export function loadProfilesAndTeamMembers(offset, limit, teamId = TeamStore.get
);
}
+export function loadProfilesAndTeamMembersAndChannelMembers(offset, limit, teamId = TeamStore.getCurrentId(), channelId = ChannelStore.getCurrentId(), success, error) {
+ Client.getProfilesInChannel(
+ channelId,
+ offset,
+ limit,
+ (data) => {
+ AppDispatcher.handleServerAction({
+ type: ActionTypes.RECEIVED_PROFILES_IN_CHANNEL,
+ profiles: data,
+ channel_id: channelId,
+ offset,
+ count: Object.keys(data).length
+ });
+
+ loadTeamMembersForProfilesMap(
+ data,
+ teamId,
+ () => {
+ loadChannelMembersForProfilesMap(data, channelId, success, error);
+ loadStatusesForProfilesMap(data);
+ });
+ },
+ (err) => {
+ AsyncClient.dispatchError(err, 'getProfilesInChannel');
+ }
+ );
+}
+
export function loadTeamMembersForProfilesMap(profiles, teamId = TeamStore.getCurrentId(), success, error) {
const membersToLoad = {};
for (const pid in profiles) {
@@ -132,6 +165,56 @@ function loadTeamMembersForProfiles(userIds, teamId, success, error) {
);
}
+export function loadChannelMembersForProfilesMap(profiles, channelId = ChannelStore.getCurrentId(), success, error) {
+ const membersToLoad = {};
+ for (const pid in profiles) {
+ if (!profiles.hasOwnProperty(pid)) {
+ continue;
+ }
+
+ if (!ChannelStore.hasActiveMemberInChannel(channelId, pid)) {
+ membersToLoad[pid] = true;
+ }
+ }
+
+ const list = Object.keys(membersToLoad);
+ if (list.length === 0) {
+ if (success) {
+ success({});
+ }
+ return;
+ }
+
+ getChannelMembersForUserIds(channelId, list, success, error);
+}
+
+export function loadTeamMembersAndChannelMembersForProfilesList(profiles, teamId = TeamStore.getCurrentId(), channelId = ChannelStore.getCurrentId(), success, error) {
+ loadTeamMembersForProfilesList(profiles, teamId, () => {
+ loadChannelMembersForProfilesList(profiles, channelId, success, error);
+ }, error);
+}
+
+export function loadChannelMembersForProfilesList(profiles, channelId = ChannelStore.getCurrentId(), success, error) {
+ const membersToLoad = {};
+ for (let i = 0; i < profiles.length; i++) {
+ const pid = profiles[i].id;
+
+ if (!ChannelStore.hasActiveMemberInChannel(channelId, pid)) {
+ membersToLoad[pid] = true;
+ }
+ }
+
+ const list = Object.keys(membersToLoad);
+ if (list.length === 0) {
+ if (success) {
+ success({});
+ }
+ return;
+ }
+
+ getChannelMembersForUserIds(channelId, list, success, error);
+}
+
function populateDMChannelsWithProfiles(userIds) {
const currentUserId = UserStore.getCurrentId();
@@ -348,10 +431,10 @@ export function updateUser(username, type, success, error) {
}
},
(err) => {
- AsyncClient.dispatchError(err, 'updateUser');
-
if (error) {
error(err);
+ } else {
+ AsyncClient.dispatchError(err, 'updateUser');
}
}
);
@@ -374,6 +457,24 @@ export function generateMfaSecret(success, error) {
);
}
+export function updateUserNotifyProps(data, success, error) {
+ Client.updateUserNotifyProps(
+ data,
+ () => {
+ AsyncClient.getMe();
+
+ if (success) {
+ success();
+ }
+ },
+ (err) => {
+ if (error) {
+ error(err);
+ }
+ }
+ );
+}
+
export function updateUserRoles(userId, newRoles, success, error) {
Client.updateUserRoles(
userId,
@@ -412,6 +513,25 @@ export function activateMfa(code, success, error) {
);
}
+export function deactivateMfa(success, error) {
+ Client.updateMfa(
+ '',
+ false,
+ () => {
+ AsyncClient.getMe();
+
+ if (success) {
+ success();
+ }
+ },
+ (err) => {
+ if (error) {
+ error(err);
+ }
+ }
+ );
+}
+
export function checkMfa(loginId, success, error) {
if (global.window.mm_config.EnableMultifactorAuthentication !== 'true') {
success(false);
@@ -449,3 +569,205 @@ export function updateActive(userId, active, success, error) {
}
);
}
+
+export function updatePassword(userId, currentPassword, newPassword, success, error) {
+ Client.updatePassword(userId, currentPassword, newPassword,
+ () => {
+ if (success) {
+ success();
+ }
+ },
+ (err) => {
+ if (error) {
+ error(err);
+ }
+ }
+ );
+}
+
+export function verifyEmail(uid, hid, success, error) {
+ Client.verifyEmail(
+ uid,
+ hid,
+ (data) => {
+ if (success) {
+ success(data);
+ }
+ },
+ (err) => {
+ if (error) {
+ error(err);
+ }
+ }
+ );
+}
+
+export function resetPassword(code, password, success, error) {
+ Client.resetPassword(
+ code,
+ password,
+ () => {
+ browserHistory.push('/login?extra=' + ActionTypes.PASSWORD_CHANGE);
+
+ if (success) {
+ success();
+ }
+ },
+ (err) => {
+ if (error) {
+ error(err);
+ }
+ }
+ );
+}
+
+export function resendVerification(email, success, error) {
+ Client.resendVerification(
+ email,
+ () => {
+ if (success) {
+ success();
+ }
+ },
+ (err) => {
+ if (error) {
+ error(err);
+ }
+ }
+ );
+}
+
+export function loginById(userId, password, mfaToken, hash, success, error) {
+ Client.loginById(
+ userId,
+ password,
+ mfaToken,
+ hash,
+ () => {
+ if (hash > 0) {
+ BrowserStore.setGlobalItem(hash, JSON.stringify({usedBefore: true}));
+ }
+
+ GlobalActions.emitInitialLoad(
+ () => {
+ const query = this.props.location.query;
+ if (query.redirect_to) {
+ browserHistory.push(query.redirect_to);
+ } else {
+ GlobalActions.redirectUserToDefaultTeam();
+ }
+ }
+ );
+
+ if (success) {
+ success();
+ }
+ },
+ (err) => {
+ if (error) {
+ error(err);
+ }
+ }
+ );
+}
+
+export function createUserWithInvite(user, data, emailHash, inviteId, success, error) {
+ Client.createUserWithInvite(
+ user,
+ data,
+ emailHash,
+ inviteId,
+ (response) => {
+ if (success) {
+ success(response);
+ }
+ },
+ (err) => {
+ if (error) {
+ error(err);
+ }
+ }
+ );
+}
+
+export function webLogin(loginId, password, token, success, error) {
+ Client.webLogin(
+ loginId,
+ password,
+ token,
+ () => {
+ if (success) {
+ success();
+ }
+ },
+ (err) => {
+ if (error) {
+ error(err);
+ }
+ }
+ );
+}
+
+export function webLoginByLdap(loginId, password, token, success, error) {
+ Client.webLoginByLdap(
+ loginId,
+ password,
+ token,
+ (data) => {
+ if (success) {
+ success(data);
+ }
+ },
+ (err) => {
+ if (error) {
+ error(err);
+ }
+ }
+ );
+}
+
+export function getAuthorizedApps(success, error) {
+ Client.getAuthorizedApps(
+ (authorizedApps) => {
+ if (success) {
+ success(authorizedApps);
+ }
+ },
+ (err) => {
+ if (error) {
+ error(err);
+ }
+ });
+}
+
+export function deauthorizeOAuthApp(appId, success, error) {
+ Client.deauthorizeOAuthApp(
+ appId,
+ () => {
+ if (success) {
+ success();
+ }
+ },
+ (err) => {
+ if (error) {
+ error(err);
+ }
+ });
+}
+
+export function uploadProfileImage(userPicture, success, error) {
+ Client.uploadProfileImage(
+ userPicture,
+ () => {
+ AsyncClient.getMe();
+ if (success) {
+ success();
+ }
+ },
+ (err) => {
+ if (error) {
+ error(err);
+ }
+ }
+ );
+}
diff --git a/webapp/actions/webrtc_actions.jsx b/webapp/actions/webrtc_actions.jsx
index 444eee241..b096e1c33 100644
--- a/webapp/actions/webrtc_actions.jsx
+++ b/webapp/actions/webrtc_actions.jsx
@@ -4,6 +4,8 @@
import AppDispatcher from 'dispatcher/app_dispatcher.jsx';
import {WebrtcActionTypes} from 'utils/constants.jsx';
+import Client from 'client/web_client.jsx';
+
export function initWebrtc(userId, isCalling) {
AppDispatcher.handleServerAction({
type: WebrtcActionTypes.INITIALIZE,
@@ -18,3 +20,17 @@ export function handle(message) {
message
});
}
+
+export function webrtcToken(success, error) {
+ Client.webrtcToken(
+ (data) => {
+ if (success) {
+ success(data);
+ }
+ },
+ () => {
+ if (error) {
+ error();
+ }
+ });
+}
diff --git a/webapp/client/client.jsx b/webapp/client/client.jsx
index ba42d7ae8..9f1bc926d 100644
--- a/webapp/client/client.jsx
+++ b/webapp/client/client.jsx
@@ -1496,6 +1496,16 @@ export default class Client {
end(this.handleResponse.bind(this, 'getChannelMember', success, error));
}
+ getChannelMembersByIds(channelId, userIds, success, error) {
+ request.
+ post(`${this.getChannelNeededRoute(channelId)}/members/ids`).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ send(userIds).
+ end(this.handleResponse.bind(this, 'getChannelMembersByIds', success, error));
+ }
+
addChannelMember(channelId, userId, success, error) {
request.
post(`${this.getChannelNeededRoute(channelId)}/add`).
@@ -1520,6 +1530,21 @@ export default class Client {
this.track('api', 'api_channels_remove_member');
}
+ updateChannelMemberRoles(channelId, userId, newRoles, success, error) {
+ var data = {
+ user_id: userId,
+ new_roles: newRoles
+ };
+
+ request.
+ post(`${this.getChannelNeededRoute(channelId)}/update_member_roles`).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ send(data).
+ end(this.handleResponse.bind(this, 'updateChannelMemberRoles', success, error));
+ }
+
// Routes for Commands
listCommands(success, error) {
@@ -1742,6 +1767,16 @@ export default class Client {
end(this.handleResponse.bind(this, 'getFileInfosForPost', success, error));
}
+ getOpenGraphMetadata(url, success, error) {
+ request.
+ post(`${this.getBaseRoute()}/get_opengraph_metadata`).
+ set(this.defaultHeaders).
+ type('application/json').
+ accept('application/json').
+ send({url}).
+ end(this.handleResponse.bind(this, 'getOpenGraphMetadata', success, error));
+ }
+
// Routes for Files
uploadFile(file, filename, channelId, clientId, success, error) {
diff --git a/webapp/components/activity_log_modal.jsx b/webapp/components/activity_log_modal.jsx
index b907668f0..cd369f742 100644
--- a/webapp/components/activity_log_modal.jsx
+++ b/webapp/components/activity_log_modal.jsx
@@ -5,7 +5,6 @@ import LoadingScreen from './loading_screen.jsx';
import UserStore from 'stores/user_store.jsx';
-import Client from 'client/web_client.jsx';
import * as AsyncClient from 'utils/async_client.jsx';
import * as Utils from 'utils/utils.jsx';
@@ -14,6 +13,8 @@ import React from 'react';
import {Modal} from 'react-bootstrap';
import {FormattedMessage, FormattedTime, FormattedDate} from 'react-intl';
+import {revokeSession} from 'actions/admin_actions.jsx';
+
export default class ActivityLogModal extends React.Component {
constructor(props) {
super(props);
@@ -46,10 +47,8 @@ export default class ActivityLogModal extends React.Component {
setTimeout(() => {
modalContent.removeClass('animation--highlight');
}, 1500);
- Client.revokeSession(altId,
- () => {
- AsyncClient.getSessions();
- },
+ revokeSession(altId,
+ null,
(err) => {
const state = this.getStateFromStores();
state.serverError = err;
@@ -134,6 +133,17 @@ export default class ActivityLogModal extends React.Component {
} else {
devicePicture = 'fa fa-linux';
}
+ } else if (currentSession.props.os.indexOf('Linux') !== -1) {
+ devicePicture = 'fa fa-linux';
+ }
+
+ if (currentSession.props.browser.indexOf('Desktop App') !== -1) {
+ devicePlatform = (
+ <FormattedMessage
+ id='activity_log_modal.desktop'
+ defaultMessage='Native Desktop App'
+ />
+ );
}
let moreInfo;
diff --git a/webapp/components/admin_console/admin_settings.jsx b/webapp/components/admin_console/admin_settings.jsx
index 9975a3975..b9883d7d8 100644
--- a/webapp/components/admin_console/admin_settings.jsx
+++ b/webapp/components/admin_console/admin_settings.jsx
@@ -4,11 +4,12 @@
import React from 'react';
import * as AsyncClient from 'utils/async_client.jsx';
-import Client from 'client/web_client.jsx';
import FormError from 'components/form_error.jsx';
import SaveButton from 'components/admin_console/save_button.jsx';
+import {saveConfig} from 'actions/admin_actions.jsx';
+
export default class AdminSettings extends React.Component {
static get propTypes() {
return {
@@ -53,7 +54,7 @@ export default class AdminSettings extends React.Component {
let config = JSON.parse(JSON.stringify(this.props.config));
config = this.getConfigFromState(config);
- Client.saveConfig(
+ saveConfig(
config,
() => {
AsyncClient.getConfig((savedConfig) => {
diff --git a/webapp/components/admin_console/admin_sidebar_header.jsx b/webapp/components/admin_console/admin_sidebar_header.jsx
index 86c2c6b0f..5725551bf 100644
--- a/webapp/components/admin_console/admin_sidebar_header.jsx
+++ b/webapp/components/admin_console/admin_sidebar_header.jsx
@@ -42,7 +42,7 @@ export default class SidebarHeader extends React.Component {
profilePicture = (
<img
className='user__picture'
- src={Client.getUsersRoute() + '/' + me.id + '/image?time=' + me.update_at}
+ src={Client.getUsersRoute() + '/' + me.id + '/image?time=' + me.last_picture_update}
/>
);
}
diff --git a/webapp/components/admin_console/admin_team_members_dropdown.jsx b/webapp/components/admin_console/admin_team_members_dropdown.jsx
index ee9e53f6c..01e94db16 100644
--- a/webapp/components/admin_console/admin_team_members_dropdown.jsx
+++ b/webapp/components/admin_console/admin_team_members_dropdown.jsx
@@ -6,12 +6,10 @@ import ConfirmModal from '../confirm_modal.jsx';
import UserStore from 'stores/user_store.jsx';
import TeamStore from 'stores/team_store.jsx';
-import Client from 'client/web_client.jsx';
import Constants from 'utils/constants.jsx';
import * as Utils from 'utils/utils.jsx';
-import * as AsyncClient from 'utils/async_client.jsx';
import {updateUserRoles, updateActive} from 'actions/user_actions.jsx';
-import {updateTeamMemberRoles} from 'actions/team_actions.jsx';
+import {updateTeamMemberRoles, removeUserFromTeam, adminResetMfa} from 'actions/team_actions.jsx';
import {FormattedMessage} from 'react-intl';
@@ -75,14 +73,10 @@ export default class AdminTeamMembersDropdown extends React.Component {
}
handleRemoveFromTeam() {
- Client.removeUserFromTeam(
+ removeUserFromTeam(
this.props.teamMember.team_id,
this.props.user.id,
- () => {
- AsyncClient.getTeamStats(this.props.teamMember.team_id);
- UserStore.removeProfileFromTeam(this.props.teamMember.team_id, this.props.user.id);
- UserStore.emitInTeamChange();
- },
+ null,
(err) => {
this.setState({serverError: err.message});
}
@@ -150,10 +144,8 @@ export default class AdminTeamMembersDropdown extends React.Component {
handleResetMfa(e) {
e.preventDefault();
- Client.adminResetMfa(this.props.user.id,
- () => {
- AsyncClient.getUser(this.props.user.id);
- },
+ adminResetMfa(this.props.user.id,
+ null,
(err) => {
this.setState({serverError: err.message});
}
diff --git a/webapp/components/admin_console/brand_image_setting.jsx b/webapp/components/admin_console/brand_image_setting.jsx
index 653073200..b58c0159c 100644
--- a/webapp/components/admin_console/brand_image_setting.jsx
+++ b/webapp/components/admin_console/brand_image_setting.jsx
@@ -7,6 +7,7 @@ import ReactDOM from 'react-dom';
import Client from 'client/web_client.jsx';
import * as Utils from 'utils/utils.jsx';
+import {uploadBrandImage} from 'actions/admin_actions.jsx';
import FormError from 'components/form_error.jsx';
import {FormattedHTMLMessage, FormattedMessage} from 'react-intl';
@@ -81,7 +82,7 @@ export default class BrandImageSetting extends React.Component {
error: ''
});
- Client.uploadBrandImage(
+ uploadBrandImage(
this.state.brandImage,
() => {
$(ReactDOM.findDOMNode(this.refs.upload)).button('complete');
diff --git a/webapp/components/admin_console/cluster_table_container.jsx b/webapp/components/admin_console/cluster_table_container.jsx
index aad5753b7..8dba80cce 100644
--- a/webapp/components/admin_console/cluster_table_container.jsx
+++ b/webapp/components/admin_console/cluster_table_container.jsx
@@ -4,8 +4,8 @@
import React from 'react';
import ClusterTable from './cluster_table.jsx';
import LoadingScreen from '../loading_screen.jsx';
-import Client from 'client/web_client.jsx';
-import * as AsyncClient from 'utils/async_client.jsx';
+
+import {getClusterStatus} from 'actions/admin_actions.jsx';
export default class ClusterTableContainer extends React.Component {
constructor(props) {
@@ -19,15 +19,13 @@ export default class ClusterTableContainer extends React.Component {
}
load() {
- Client.getClusterStatus(
+ getClusterStatus(
(data) => {
this.setState({
clusterInfos: data
});
},
- (err) => {
- AsyncClient.dispatchError(err, 'getClusterStatus');
- }
+ null
);
}
diff --git a/webapp/components/admin_console/compliance_reports.jsx b/webapp/components/admin_console/compliance_reports.jsx
index aac09c0de..7274e6774 100644
--- a/webapp/components/admin_console/compliance_reports.jsx
+++ b/webapp/components/admin_console/compliance_reports.jsx
@@ -9,6 +9,7 @@ import UserStore from '../../stores/user_store.jsx';
import Client from 'client/web_client.jsx';
import * as AsyncClient from '../../utils/async_client.jsx';
+import {saveComplianceReports} from 'actions/admin_actions.jsx';
import {FormattedMessage, FormattedDate, FormattedTime} from 'react-intl';
@@ -72,7 +73,7 @@ export default class ComplianceReports extends React.Component {
job.start_at = Date.parse(ReactDOM.findDOMNode(this.refs.from).value);
job.end_at = Date.parse(ReactDOM.findDOMNode(this.refs.to).value);
- Client.saveComplianceReports(
+ saveComplianceReports(
job,
() => {
ReactDOM.findDOMNode(this.refs.emails).value = '';
diff --git a/webapp/components/admin_console/email_connection_test.jsx b/webapp/components/admin_console/email_connection_test.jsx
index 8e11a0bb4..b99633eec 100644
--- a/webapp/components/admin_console/email_connection_test.jsx
+++ b/webapp/components/admin_console/email_connection_test.jsx
@@ -3,11 +3,12 @@
import React from 'react';
-import Client from 'client/web_client.jsx';
import * as Utils from 'utils/utils.jsx';
import {FormattedMessage} from 'react-intl';
+import {testEmail} from 'actions/admin_actions.jsx';
+
export default class EmailConnectionTestButton extends React.Component {
static get propTypes() {
return {
@@ -41,7 +42,7 @@ export default class EmailConnectionTestButton extends React.Component {
const config = JSON.parse(JSON.stringify(this.props.config));
this.props.getConfigFromState(config);
- Client.testEmail(
+ testEmail(
config,
() => {
this.setState({
diff --git a/webapp/components/admin_console/ldap_test_button.jsx b/webapp/components/admin_console/ldap_test_button.jsx
index e077aec5f..a564fa42a 100644
--- a/webapp/components/admin_console/ldap_test_button.jsx
+++ b/webapp/components/admin_console/ldap_test_button.jsx
@@ -3,11 +3,12 @@
import React from 'react';
-import Client from 'client/web_client.jsx';
import * as Utils from 'utils/utils.jsx';
import {FormattedMessage, FormattedHTMLMessage} from 'react-intl';
+import {ldapTest} from 'actions/admin_actions.jsx';
+
export default class LdapTestButton extends React.Component {
static get propTypes() {
return {
@@ -38,7 +39,7 @@ export default class LdapTestButton extends React.Component {
});
const doRequest = () => { //eslint-disable-line func-style
- Client.ldapTest(
+ ldapTest(
() => {
this.setState({
buisy: false,
diff --git a/webapp/components/admin_console/license_settings.jsx b/webapp/components/admin_console/license_settings.jsx
index d98309f80..6c14394b7 100644
--- a/webapp/components/admin_console/license_settings.jsx
+++ b/webapp/components/admin_console/license_settings.jsx
@@ -4,7 +4,8 @@
import $ from 'jquery';
import ReactDOM from 'react-dom';
import * as Utils from 'utils/utils.jsx';
-import Client from 'client/web_client.jsx';
+
+import {uploadLicenseFile, removeLicenseFile} from 'actions/admin_actions.jsx';
import {injectIntl, intlShape, defineMessages, FormattedMessage, FormattedHTMLMessage} from 'react-intl';
@@ -54,7 +55,8 @@ class LicenseSettings extends React.Component {
$('#upload-button').button('loading');
- Client.uploadLicenseFile(file,
+ uploadLicenseFile(
+ file,
() => {
Utils.clearFileInput(element[0]);
$('#upload-button').button('reset');
@@ -74,7 +76,7 @@ class LicenseSettings extends React.Component {
$('#remove-button').button('loading');
- Client.removeLicenseFile(
+ removeLicenseFile(
() => {
$('#remove-button').button('reset');
this.setState({fileSelected: false, fileName: null, serverError: null});
diff --git a/webapp/components/admin_console/logs.jsx b/webapp/components/admin_console/logs.jsx
index 8dc0c1e2e..5846c91db 100644
--- a/webapp/components/admin_console/logs.jsx
+++ b/webapp/components/admin_console/logs.jsx
@@ -24,12 +24,14 @@ export default class Logs extends React.Component {
componentDidMount() {
AdminStore.addLogChangeListener(this.onLogListenerChange);
AsyncClient.getLogs();
+ this.refs.logPanel.focus();
}
componentDidUpdate() {
// Scroll Down to get the latest logs
var node = this.refs.logPanel;
node.scrollTop = node.scrollHeight;
+ node.focus();
}
componentWillUnmount() {
@@ -100,6 +102,7 @@ export default class Logs extends React.Component {
/>
</button>
<div
+ tabIndex='-1'
ref='logPanel'
className='log__panel'
>
diff --git a/webapp/components/admin_console/policy_settings.jsx b/webapp/components/admin_console/policy_settings.jsx
index 0e224af73..391726a93 100644
--- a/webapp/components/admin_console/policy_settings.jsx
+++ b/webapp/components/admin_console/policy_settings.jsx
@@ -6,6 +6,8 @@ import React from 'react';
import AdminSettings from './admin_settings.jsx';
import SettingsGroup from './settings_group.jsx';
import DropdownSetting from './dropdown_setting.jsx';
+import RadioSetting from './radio_setting.jsx';
+import PostEditSetting from './post_edit_setting.jsx';
import Constants from 'utils/constants.jsx';
import * as Utils from 'utils/utils.jsx';
@@ -22,6 +24,9 @@ export default class PolicySettings extends AdminSettings {
}
getConfigFromState(config) {
+ config.ServiceSettings.RestrictPostDelete = this.state.restrictPostDelete;
+ config.ServiceSettings.AllowEditPost = this.state.allowEditPost;
+ config.ServiceSettings.PostEditTimeLimit = this.parseIntNonZero(this.state.postEditTimeLimit, Constants.DEFAULT_POST_EDIT_TIME_LIMIT);
config.TeamSettings.RestrictTeamInvite = this.state.restrictTeamInvite;
config.TeamSettings.RestrictPublicChannelCreation = this.state.restrictPublicChannelCreation;
config.TeamSettings.RestrictPrivateChannelCreation = this.state.restrictPrivateChannelCreation;
@@ -35,6 +40,9 @@ export default class PolicySettings extends AdminSettings {
getStateFromConfig(config) {
return {
+ restrictPostDelete: config.ServiceSettings.RestrictPostDelete,
+ allowEditPost: config.ServiceSettings.AllowEditPost,
+ postEditTimeLimit: config.ServiceSettings.PostEditTimeLimit,
restrictTeamInvite: config.TeamSettings.RestrictTeamInvite,
restrictPublicChannelCreation: config.TeamSettings.RestrictPublicChannelCreation,
restrictPrivateChannelCreation: config.TeamSettings.RestrictPrivateChannelCreation,
@@ -241,6 +249,47 @@ export default class PolicySettings extends AdminSettings {
/>
}
/>
+ <RadioSetting
+ id='restrictPostDelete'
+ values={[
+ {value: Constants.PERMISSIONS_DELETE_POST_ALL, text: Utils.localizeMessage('admin.general.policy.permissionsDeletePostAll', 'Message authors can delete their own messages, and Administrators can delete any message')},
+ {value: Constants.PERMISSIONS_DELETE_POST_TEAM_ADMIN, text: Utils.localizeMessage('admin.general.policy.permissionsDeletePostAdmin', 'Team Admins and System Admins')},
+ {value: Constants.PERMISSIONS_DELETE_POST_SYSTEM_ADMIN, text: Utils.localizeMessage('admin.general.policy.permissionsDeletePostSystemAdmin', 'System Admins')}
+ ]}
+ label={
+ <FormattedMessage
+ id='admin.general.policy.restrictPostDeleteTitle'
+ defaultMessage='Allow which users to delete messages:'
+ />
+ }
+ value={this.state.restrictPostDelete}
+ onChange={this.handleChange}
+ helpText={
+ <FormattedHTMLMessage
+ id='admin.general.policy.restrictPostDeleteDescription'
+ defaultMessage='Set policy on who has permission to delete messages.'
+ />
+ }
+ />
+ <PostEditSetting
+ id='allowEditPost'
+ timeLimitId='postEditTimeLimit'
+ label={
+ <FormattedMessage
+ id='admin.general.policy.allowEditPostTitle'
+ defaultMessage='Allow users to edit their messages:'
+ />
+ }
+ value={this.state.allowEditPost}
+ timeLimitValue={this.state.postEditTimeLimit}
+ onChange={this.handleChange}
+ helpText={
+ <FormattedHTMLMessage
+ id='admin.general.policy.allowEditPostDescription'
+ defaultMessage='Set policy on the length of time authors have to edit their messages after posting.'
+ />
+ }
+ />
</SettingsGroup>
);
}
diff --git a/webapp/components/admin_console/post_edit_setting.jsx b/webapp/components/admin_console/post_edit_setting.jsx
new file mode 100644
index 000000000..282a1b6c5
--- /dev/null
+++ b/webapp/components/admin_console/post_edit_setting.jsx
@@ -0,0 +1,99 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import React from 'react';
+
+import Setting from './setting.jsx';
+
+import Constants from 'utils/constants.jsx';
+import * as Utils from 'utils/utils.jsx';
+
+export default class PostEditSetting extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.handleChange = this.handleChange.bind(this);
+ this.handleTimeLimitChange = this.handleTimeLimitChange.bind(this);
+ }
+
+ handleChange(e) {
+ this.props.onChange(this.props.id, e.target.value);
+ }
+
+ handleTimeLimitChange(e) {
+ this.props.onChange(this.props.timeLimitId, e.target.value);
+ }
+
+ render() {
+ return (
+ <Setting
+ label={this.props.label}
+ inputId={this.props.id}
+ helpText={this.props.helpText}
+ >
+ <div className='radio'>
+ <label>
+ <input
+ type='radio'
+ value={Constants.ALLOW_EDIT_POST_ALWAYS}
+ name={this.props.id}
+ checked={this.props.value === Constants.ALLOW_EDIT_POST_ALWAYS}
+ onChange={this.handleChange}
+ disabled={this.props.disabled}
+ />
+ {Utils.localizeMessage('admin.general.policy.allowEditPostAlways', 'Any time')}
+ </label>
+ </div>
+ <div className='radio'>
+ <label>
+ <input
+ type='radio'
+ value={Constants.ALLOW_EDIT_POST_NEVER}
+ name={this.props.id}
+ checked={this.props.value === Constants.ALLOW_EDIT_POST_NEVER}
+ onChange={this.handleChange}
+ disabled={this.props.disabled}
+ />
+ {Utils.localizeMessage('admin.general.policy.allowEditPostNever', 'Never')}
+ </label>
+ </div>
+ <div className='radio form-inline'>
+ <label>
+ <input
+ type='radio'
+ value={Constants.ALLOW_EDIT_POST_TIME_LIMIT}
+ name={this.props.id}
+ checked={this.props.value === Constants.ALLOW_EDIT_POST_TIME_LIMIT}
+ onChange={this.handleChange}
+ disabled={this.props.disabled}
+ />
+ <input
+ type='text'
+ value={this.props.timeLimitValue}
+ className='form-control'
+ name={this.props.timeLimitId}
+ onChange={this.handleTimeLimitChange}
+ disabled={this.props.disabled || this.props.value !== Constants.ALLOW_EDIT_POST_TIME_LIMIT}
+ />
+ <span> {Utils.localizeMessage('admin.general.policy.allowEditPostTimeLimit', 'seconds after posting')}</span>
+ </label>
+ </div>
+ </Setting>
+ );
+ }
+}
+
+PostEditSetting.defaultProps = {
+ isDisabled: false
+};
+
+PostEditSetting.propTypes = {
+ id: React.PropTypes.string.isRequired,
+ timeLimitId: React.PropTypes.string.isRequired,
+ label: React.PropTypes.node.isRequired,
+ value: React.PropTypes.string.isRequired,
+ timeLimitValue: React.PropTypes.number.isRequired,
+ onChange: React.PropTypes.func.isRequired,
+ disabled: React.PropTypes.bool,
+ helpText: React.PropTypes.node
+};
diff --git a/webapp/components/admin_console/purge_caches.jsx b/webapp/components/admin_console/purge_caches.jsx
index a999f090e..9f52433d5 100644
--- a/webapp/components/admin_console/purge_caches.jsx
+++ b/webapp/components/admin_console/purge_caches.jsx
@@ -3,10 +3,10 @@
import React from 'react';
-import Client from 'client/web_client.jsx';
-
import {FormattedMessage} from 'react-intl';
+import {invalidateAllCaches} from 'actions/admin_actions.jsx';
+
export default class PurgeCachesButton extends React.Component {
constructor(props) {
super(props);
@@ -27,7 +27,7 @@ export default class PurgeCachesButton extends React.Component {
fail: null
});
- Client.invalidateAllCaches(
+ invalidateAllCaches(
() => {
this.setState({
loading: false
@@ -43,10 +43,6 @@ export default class PurgeCachesButton extends React.Component {
}
render() {
- if (global.window.mm_license.IsLicensed !== 'true') {
- return <div/>;
- }
-
let testMessage = null;
if (this.state.fail) {
testMessage = (
diff --git a/webapp/components/admin_console/radio_setting.jsx b/webapp/components/admin_console/radio_setting.jsx
new file mode 100644
index 000000000..dd45a5a26
--- /dev/null
+++ b/webapp/components/admin_console/radio_setting.jsx
@@ -0,0 +1,63 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import React from 'react';
+
+import Setting from './setting.jsx';
+
+export default class RadioSetting extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.handleChange = this.handleChange.bind(this);
+ }
+
+ handleChange(e) {
+ this.props.onChange(this.props.id, e.target.value);
+ }
+
+ render() {
+ const options = [];
+ for (const {value, text} of this.props.values) {
+ options.push(
+ <div className='radio'>
+ <label>
+ <input
+ type='radio'
+ value={value}
+ name={this.props.id}
+ checked={value === this.props.value}
+ onChange={this.handleChange}
+ disabled={this.props.disabled}
+ />
+ {text}
+ </label>
+ </div>
+ );
+ }
+
+ return (
+ <Setting
+ label={this.props.label}
+ inputId={this.props.id}
+ helpText={this.props.helpText}
+ >
+ {options}
+ </Setting>
+ );
+ }
+}
+
+RadioSetting.defaultProps = {
+ isDisabled: false
+};
+
+RadioSetting.propTypes = {
+ id: React.PropTypes.string.isRequired,
+ values: React.PropTypes.array.isRequired,
+ label: React.PropTypes.node.isRequired,
+ value: React.PropTypes.string.isRequired,
+ onChange: React.PropTypes.func.isRequired,
+ disabled: React.PropTypes.bool,
+ helpText: React.PropTypes.node
+};
diff --git a/webapp/components/admin_console/recycle_db.jsx b/webapp/components/admin_console/recycle_db.jsx
index 53e8e7436..5683f97e2 100644
--- a/webapp/components/admin_console/recycle_db.jsx
+++ b/webapp/components/admin_console/recycle_db.jsx
@@ -3,11 +3,12 @@
import React from 'react';
-import Client from 'client/web_client.jsx';
import * as Utils from 'utils/utils.jsx';
import {FormattedMessage, FormattedHTMLMessage} from 'react-intl';
+import {recycleDatabaseConnection} from 'actions/admin_actions.jsx';
+
export default class RecycleDbButton extends React.Component {
constructor(props) {
super(props);
@@ -28,7 +29,7 @@ export default class RecycleDbButton extends React.Component {
fail: null
});
- Client.recycleDatabaseConnection(
+ recycleDatabaseConnection(
() => {
this.setState({
loading: false
diff --git a/webapp/components/admin_console/reload_config.jsx b/webapp/components/admin_console/reload_config.jsx
index 0b50d5803..25e9463d3 100644
--- a/webapp/components/admin_console/reload_config.jsx
+++ b/webapp/components/admin_console/reload_config.jsx
@@ -3,13 +3,12 @@
import React from 'react';
-import Client from 'client/web_client.jsx';
import * as Utils from 'utils/utils.jsx';
-import {getConfig} from 'utils/async_client.jsx';
-
import {FormattedMessage, FormattedHTMLMessage} from 'react-intl';
+import {reloadConfig} from 'actions/admin_actions.jsx';
+
export default class ReloadConfigButton extends React.Component {
constructor(props) {
super(props);
@@ -30,9 +29,8 @@ export default class ReloadConfigButton extends React.Component {
fail: null
});
- Client.reloadConfig(
+ reloadConfig(
() => {
- getConfig();
this.setState({
loading: false
});
diff --git a/webapp/components/admin_console/reset_password_modal.jsx b/webapp/components/admin_console/reset_password_modal.jsx
index e3fd2bf00..757f85517 100644
--- a/webapp/components/admin_console/reset_password_modal.jsx
+++ b/webapp/components/admin_console/reset_password_modal.jsx
@@ -1,12 +1,13 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-import Client from 'client/web_client.jsx';
import * as Utils from 'utils/utils.jsx';
import {Modal} from 'react-bootstrap';
import {injectIntl, intlShape, FormattedMessage} from 'react-intl';
+import {adminResetPassword} from 'actions/admin_actions.jsx';
+
import React from 'react';
class ResetPasswordModal extends React.Component {
@@ -32,7 +33,7 @@ class ResetPasswordModal extends React.Component {
}
this.setState({serverError: null});
- Client.adminResetPassword(
+ adminResetPassword(
this.props.user.id,
password,
() => {
diff --git a/webapp/components/admin_console/saml_settings.jsx b/webapp/components/admin_console/saml_settings.jsx
index ad7a82553..7b9ed38b8 100644
--- a/webapp/components/admin_console/saml_settings.jsx
+++ b/webapp/components/admin_console/saml_settings.jsx
@@ -12,9 +12,10 @@ import RemoveFileSetting from './remove_file_setting.jsx';
import {FormattedMessage, FormattedHTMLMessage} from 'react-intl';
import SettingsGroup from './settings_group.jsx';
-import Client from 'client/web_client.jsx';
import * as Utils from 'utils/utils.jsx';
+import {samlCertificateStatus, uploadCertificateFile, removeCertificateFile} from 'actions/admin_actions.jsx';
+
export default class SamlSettings extends AdminSettings {
constructor(props) {
super(props);
@@ -73,7 +74,7 @@ export default class SamlSettings extends AdminSettings {
}
componentWillMount() {
- Client.samlCertificateStatus(
+ samlCertificateStatus(
(data) => {
const files = {};
if (!data.IdpCertificateFile) {
@@ -93,7 +94,7 @@ export default class SamlSettings extends AdminSettings {
}
uploadCertificate(id, file, callback) {
- Client.uploadCertificateFile(
+ uploadCertificateFile(
file,
() => {
const fileName = file.name;
@@ -112,7 +113,7 @@ export default class SamlSettings extends AdminSettings {
}
removeCertificate(id, callback) {
- Client.removeCertificateFile(
+ removeCertificateFile(
this.state[id],
() => {
this.handleChange(id, '');
diff --git a/webapp/components/admin_console/sync_now_button.jsx b/webapp/components/admin_console/sync_now_button.jsx
index 95d126291..f1197b216 100644
--- a/webapp/components/admin_console/sync_now_button.jsx
+++ b/webapp/components/admin_console/sync_now_button.jsx
@@ -3,11 +3,12 @@
import React from 'react';
-import Client from 'client/web_client.jsx';
import * as Utils from 'utils/utils.jsx';
import {FormattedMessage, FormattedHTMLMessage} from 'react-intl';
+import {ldapSyncNow} from 'actions/admin_actions.jsx';
+
export default class SyncNowButton extends React.Component {
static get propTypes() {
return {
@@ -33,7 +34,7 @@ export default class SyncNowButton extends React.Component {
fail: null
});
- Client.ldapSyncNow(
+ ldapSyncNow(
() => {
this.setState({
buisy: false
diff --git a/webapp/components/admin_console/team_users.jsx b/webapp/components/admin_console/team_users.jsx
index 4517e241b..547002a5b 100644
--- a/webapp/components/admin_console/team_users.jsx
+++ b/webapp/components/admin_console/team_users.jsx
@@ -158,13 +158,17 @@ export default class UserList extends React.Component {
clearTimeout(this.searchTimeoutId);
- this.searchTimeoutId = setTimeout(
+ const searchTimeoutId = setTimeout(
() => {
searchUsers(
term,
this.props.params.team,
options,
(users) => {
+ if (searchTimeoutId !== this.searchTimeoutId) {
+ return;
+ }
+
this.setState({loading: true, search: true, users});
loadTeamMembersForProfilesList(users, this.props.params.team, this.loadComplete);
}
@@ -172,6 +176,8 @@ export default class UserList extends React.Component {
},
Constants.SEARCH_TIMEOUT_MILLISECONDS
);
+
+ this.searchTimeoutId = searchTimeoutId;
}
render() {
diff --git a/webapp/components/analytics/system_analytics.jsx b/webapp/components/analytics/system_analytics.jsx
index dd7b90260..89cc98f0b 100644
--- a/webapp/components/analytics/system_analytics.jsx
+++ b/webapp/components/analytics/system_analytics.jsx
@@ -358,6 +358,32 @@ class SystemAnalytics extends React.Component {
/>
);
+ const dailyActiveUsers = (
+ <StatisticCount
+ title={
+ <FormattedMessage
+ id='analytics.system.dailyActiveUsers'
+ defaultMessage='Daily Active Users'
+ />
+ }
+ icon='fa-users'
+ count={stats[StatTypes.DAILY_ACTIVE_USERS]}
+ />
+ );
+
+ const monthlyActiveUsers = (
+ <StatisticCount
+ title={
+ <FormattedMessage
+ id='analytics.system.monthlyActiveUsers'
+ defaultMessage='Monthly Active Users'
+ />
+ }
+ icon='fa-users'
+ count={stats[StatTypes.MONTHLY_ACTIVE_USERS]}
+ />
+ );
+
let firstRow;
let secondRow;
if (isLicensed && skippedIntensiveQueries) {
@@ -406,6 +432,13 @@ class SystemAnalytics extends React.Component {
);
}
+ const thirdRow = (
+ <div className='row'>
+ {dailyActiveUsers}
+ {monthlyActiveUsers}
+ </div>
+ );
+
return (
<div className='wrapper--fixed team_statistics'>
<h3>
@@ -417,6 +450,7 @@ class SystemAnalytics extends React.Component {
{banner}
{firstRow}
{secondRow}
+ {thirdRow}
{advancedStats}
{advancedGraphs}
{postTotalGraph}
diff --git a/webapp/components/authorize.jsx b/webapp/components/authorize.jsx
index 684bae589..f3f5770de 100644
--- a/webapp/components/authorize.jsx
+++ b/webapp/components/authorize.jsx
@@ -1,7 +1,6 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-import Client from 'client/web_client.jsx';
import FormError from 'components/form_error.jsx';
import {FormattedMessage, FormattedHTMLMessage} from 'react-intl';
@@ -9,6 +8,8 @@ import React from 'react';
import icon50 from 'images/icon50x50.png';
+import {getOAuthAppInfo, allowOAuth2} from 'actions/admin_actions.jsx';
+
export default class Authorize extends React.Component {
static get propTypes() {
return {
@@ -27,7 +28,7 @@ export default class Authorize extends React.Component {
}
componentWillMount() {
- Client.getOAuthAppInfo(
+ getOAuthAppInfo(
this.props.location.query.client_id,
(app) => {
this.setState({app});
@@ -46,7 +47,7 @@ export default class Authorize extends React.Component {
handleAllow() {
const params = this.props.location.query;
- Client.allowOAuth2(params.response_type, params.client_id, params.redirect_uri, params.state, params.scope,
+ allowOAuth2(params,
(data) => {
if (data.redirect) {
window.location.href = data.redirect;
diff --git a/webapp/components/channel_header.jsx b/webapp/components/channel_header.jsx
index fc0ec132e..e83d493f4 100644
--- a/webapp/components/channel_header.jsx
+++ b/webapp/components/channel_header.jsx
@@ -29,15 +29,12 @@ import * as ChannelActions from 'actions/channel_actions.jsx';
import * as Utils from 'utils/utils.jsx';
import * as ChannelUtils from 'utils/channel_utils.jsx';
import * as TextFormatting from 'utils/text_formatting.jsx';
-import Client from 'client/web_client.jsx';
-import * as AsyncClient from 'utils/async_client.jsx';
import {getFlaggedPosts} from 'actions/post_actions.jsx';
import {Constants, Preferences, UserStatuses} from 'utils/constants.jsx';
import React from 'react';
import {FormattedMessage} from 'react-intl';
-import {browserHistory} from 'react-router/es6';
import {Tooltip, OverlayTrigger, Popover} from 'react-bootstrap';
const PreReleaseFeatures = Constants.PRE_RELEASE_FEATURES;
@@ -131,21 +128,7 @@ export default class ChannelHeader extends React.Component {
}
handleLeave() {
- Client.leaveChannel(this.state.channel.id,
- () => {
- const channelId = this.state.channel.id;
-
- if (this.state.isFavorite) {
- ChannelActions.unmarkFavorite(channelId);
- }
-
- const townsquare = ChannelStore.getByName('town-square');
- browserHistory.push(TeamStore.getCurrentTeamRelativeUrl() + '/channels/' + townsquare.name);
- },
- (err) => {
- AsyncClient.dispatchError(err, 'handleLeave');
- }
- );
+ ChannelActions.leaveChannel(this.state.channel.id);
}
toggleFavorite = (e) => {
@@ -438,7 +421,7 @@ export default class ChannelHeader extends React.Component {
dialogProps={{channel, currentUser: this.state.currentUser}}
>
<FormattedMessage
- id='chanel_header.addMembers'
+ id='channel_header.addMembers'
defaultMessage='Add Members'
/>
</ToggleModalButton>
diff --git a/webapp/components/channel_invite_modal.jsx b/webapp/components/channel_invite_modal.jsx
index 355d23d53..5deec0794 100644
--- a/webapp/components/channel_invite_modal.jsx
+++ b/webapp/components/channel_invite_modal.jsx
@@ -117,19 +117,25 @@ export default class ChannelInviteModal extends React.Component {
clearTimeout(this.searchTimeoutId);
- this.searchTimeoutId = setTimeout(
+ const searchTimeoutId = setTimeout(
() => {
searchUsers(
term,
TeamStore.getCurrentId(),
{not_in_channel_id: this.props.channel.id},
(users) => {
+ if (searchTimeoutId !== this.searchTimeoutId) {
+ return;
+ }
+
this.setState({search: true, users});
}
);
},
Constants.SEARCH_TIMEOUT_MILLISECONDS
);
+
+ this.searchTimeoutId = searchTimeoutId;
}
render() {
diff --git a/webapp/components/channel_members_dropdown.jsx b/webapp/components/channel_members_dropdown.jsx
new file mode 100644
index 000000000..a7b3259af
--- /dev/null
+++ b/webapp/components/channel_members_dropdown.jsx
@@ -0,0 +1,246 @@
+// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import ChannelStore from 'stores/channel_store.jsx';
+import TeamStore from 'stores/team_store.jsx';
+import UserStore from 'stores/user_store.jsx';
+
+import {removeUserFromChannel, makeUserChannelAdmin, makeUserChannelMember} from 'actions/channel_actions.jsx';
+
+import * as AsyncClient from 'utils/async_client.jsx';
+import * as Utils from 'utils/utils.jsx';
+
+import React from 'react';
+import {FormattedMessage} from 'react-intl';
+
+export default class ChannelMembersDropdown extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.handleRemoveFromChannel = this.handleRemoveFromChannel.bind(this);
+ this.handleMakeChannelMember = this.handleMakeChannelMember.bind(this);
+ this.handleMakeChannelAdmin = this.handleMakeChannelAdmin.bind(this);
+
+ this.state = {
+ serverError: null,
+ user: null,
+ role: null
+ };
+ }
+
+ handleRemoveFromChannel() {
+ removeUserFromChannel(
+ this.props.channel.id,
+ this.props.user.id,
+ () => {
+ AsyncClient.getChannelStats(this.props.channel.id);
+ },
+ (err) => {
+ this.setState({serverError: err.message});
+ }
+ );
+ }
+
+ handleMakeChannelMember() {
+ makeUserChannelMember(
+ this.props.channel.id,
+ this.props.user.id,
+ () => {
+ AsyncClient.getChannelStats(this.props.channel.id);
+ },
+ (err) => {
+ this.setState({serverError: err.message});
+ }
+ );
+ }
+
+ handleMakeChannelAdmin() {
+ makeUserChannelAdmin(
+ this.props.channel.id,
+ this.props.user.id,
+ () => {
+ AsyncClient.getChannelStats(this.props.channel.id);
+ },
+ (err) => {
+ this.setState({serverError: err.message});
+ }
+ );
+ }
+
+ // Checks if the user this menu is for is a channel admin or not.
+ isChannelAdmin() {
+ if (Utils.isChannelAdmin(this.props.channelMember.roles)) {
+ return true;
+ }
+
+ return false;
+ }
+
+ // Checks if the current user has the power to change the roles of this member.
+ canChangeMemberRoles() {
+ if (UserStore.isSystemAdminForCurrentUser()) {
+ return true;
+ } else if (TeamStore.isTeamAdminForCurrentTeam()) {
+ return true;
+ } else if (ChannelStore.isChannelAdminForCurrentChannel()) {
+ return true;
+ }
+
+ return false;
+ }
+
+ // Checks if the current user has the power to remove this member from the channel.
+ canRemoveMember() {
+ // TODO: This will be implemented as part of PLT-5047.
+ return true;
+ }
+
+ render() {
+ let serverError = null;
+ if (this.state.serverError) {
+ serverError = (
+ <div className='has-error'>
+ <label className='has-error control-label'>{this.state.serverError}</label>
+ </div>
+ );
+ }
+
+ if (this.props.user.id === UserStore.getCurrentId()) {
+ return null;
+ }
+
+ if (this.canChangeMemberRoles()) {
+ let role = (
+ <FormattedMessage
+ id='channel_members_dropdown.channel_member'
+ defaultMessage='Channel Member'
+ />
+ );
+
+ if (this.isChannelAdmin()) {
+ role = (
+ <FormattedMessage
+ id='channel_members_dropdown.channel_admin'
+ defaultMessage='Channel Admin'
+ />
+ );
+ }
+
+ let removeFromChannel = null;
+ if (this.canRemoveMember()) {
+ removeFromChannel = (
+ <li role='presentation'>
+ <a
+ role='menuitem'
+ href='#'
+ onClick={this.handleRemoveFromChannel}
+ >
+ <FormattedMessage
+ id='channel_members_dropdown.remove_from_channel'
+ defaultMessage='Remove From Channel'
+ />
+ </a>
+ </li>
+ );
+ }
+
+ let makeChannelMember = null;
+ if (this.isChannelAdmin()) {
+ makeChannelMember = (
+ <li role='presentation'>
+ <a
+ role='menuitem'
+ href='#'
+ onClick={this.handleMakeChannelMember}
+ >
+ <FormattedMessage
+ id='channel_members_dropdown.make_channel_member'
+ defaultMessage='Make Channel Member'
+ />
+ </a>
+ </li>
+ );
+ }
+
+ let makeChannelAdmin = null;
+ if (!this.isChannelAdmin()) {
+ makeChannelAdmin = (
+ <li role='presentation'>
+ <a
+ role='menuitem'
+ href='#'
+ onClick={this.handleMakeChannelAdmin}
+ >
+ <FormattedMessage
+ id='channel_members_dropdown.make_channel_admin'
+ defaultMessage='Make Channel Admin'
+ />
+ </a>
+ </li>
+ );
+ }
+
+ return (
+ <div className='dropdown member-drop'>
+ <a
+ href='#'
+ className='dropdown-toggle theme'
+ type='button'
+ data-toggle='dropdown'
+ aria-expanded='true'
+ >
+ <span>{role} </span>
+ <span className='fa fa-chevron-down'/>
+ </a>
+ <ul
+ className='dropdown-menu member-menu'
+ role='menu'
+ >
+ {makeChannelMember}
+ {makeChannelAdmin}
+ {removeFromChannel}
+ </ul>
+ {serverError}
+ </div>
+ );
+ } else if (this.canRemoveMember()) {
+ return (
+ <button
+ type='button'
+ className='btn btn-danger btn-message'
+ onClick={this.handleRemoveFromChannel}
+ >
+ <FormattedMessage
+ id='channel_members_dropdown.remove_member'
+ defaultMessage='Remove Member'
+ />
+ </button>
+ );
+ } else if (this.isChannelAdmin()) {
+ return (
+ <div>
+ <FormattedMessage
+ id='channel_members_dropdown.channel_admin'
+ defaultMessage='Channel Admin'
+ />
+ </div>
+ );
+ }
+
+ return (
+ <div>
+ <FormattedMessage
+ id='channel_members_dropdown.channel_member'
+ defaultMessage='Channel Member'
+ />
+ </div>
+ );
+ }
+}
+
+ChannelMembersDropdown.propTypes = {
+ channel: React.PropTypes.object.isRequired,
+ user: React.PropTypes.object.isRequired,
+ teamMember: React.PropTypes.object.isRequired,
+ channelMember: React.PropTypes.object.isRequired
+};
diff --git a/webapp/components/channel_members_modal.jsx b/webapp/components/channel_members_modal.jsx
index 351efed96..96d90e5cc 100644
--- a/webapp/components/channel_members_modal.jsx
+++ b/webapp/components/channel_members_modal.jsx
@@ -1,170 +1,29 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-import SearchableUserList from './searchable_user_list.jsx';
-import LoadingScreen from './loading_screen.jsx';
-
-import UserStore from 'stores/user_store.jsx';
-import ChannelStore from 'stores/channel_store.jsx';
-import TeamStore from 'stores/team_store.jsx';
-
-import {searchUsers} from 'actions/user_actions.jsx';
-import {removeUserFromChannel} from 'actions/channel_actions.jsx';
-
-import * as AsyncClient from 'utils/async_client.jsx';
-import * as UserAgent from 'utils/user_agent.jsx';
-import Constants from 'utils/constants.jsx';
+import MemberListChannel from './member_list_channel.jsx';
import React from 'react';
import {Modal} from 'react-bootstrap';
import {FormattedMessage} from 'react-intl';
-const USERS_PER_PAGE = 50;
-
export default class ChannelMembersModal extends React.Component {
constructor(props) {
super(props);
- this.onChange = this.onChange.bind(this);
- this.onStatusChange = this.onStatusChange.bind(this);
this.onHide = this.onHide.bind(this);
- this.handleRemove = this.handleRemove.bind(this);
- this.createRemoveMemberButton = this.createRemoveMemberButton.bind(this);
- this.search = this.search.bind(this);
- this.nextPage = this.nextPage.bind(this);
-
- this.term = '';
- this.searchTimeoutId = 0;
-
- const stats = ChannelStore.getStats(props.channel.id);
this.state = {
- users: [],
- total: stats.member_count,
- show: true,
- search: false,
- statusChange: false
+ channel: this.props.channel,
+ show: true
};
}
- componentDidMount() {
- ChannelStore.addStatsChangeListener(this.onChange);
- UserStore.addInChannelChangeListener(this.onChange);
- UserStore.addStatusesChangeListener(this.onStatusChange);
-
- AsyncClient.getProfilesInChannel(this.props.channel.id, 0);
- }
-
- componentWillUnmount() {
- ChannelStore.removeStatsChangeListener(this.onChange);
- UserStore.removeInChannelChangeListener(this.onChange);
- UserStore.removeStatusesChangeListener(this.onStatusChange);
- }
-
- onChange(force) {
- if (this.state.search && !force) {
- this.search(this.term);
- return;
- }
-
- const stats = ChannelStore.getStats(this.props.channel.id);
- this.setState({
- users: UserStore.getProfileListInChannel(this.props.channel.id),
- total: stats.member_count
- });
- }
-
- onStatusChange() {
- // Initiate a render to pick up on new statuses
- this.setState({
- statusChange: !this.state.statusChange
- });
- }
-
onHide() {
this.setState({show: false});
}
- handleRemove(user) {
- const userId = user.id;
-
- removeUserFromChannel(
- this.props.channel.id,
- userId,
- null,
- (err) => {
- this.setState({inviteError: err.message});
- }
- );
- }
-
- createRemoveMemberButton({user}) {
- if (user.id === UserStore.getCurrentId()) {
- return null;
- }
-
- return (
- <button
- type='button'
- className='btn btn-primary btn-message'
- onClick={this.handleRemove.bind(this, user)}
- >
- <FormattedMessage
- id='channel_members_modal.remove'
- defaultMessage='Remove'
- />
- </button>
- );
- }
-
- nextPage(page) {
- AsyncClient.getProfilesInChannel(this.props.channel.id, (page + 1) * USERS_PER_PAGE, USERS_PER_PAGE);
- }
-
- search(term) {
- this.term = term;
-
- if (term === '') {
- this.onChange(true);
- this.setState({search: false});
- return;
- }
-
- clearTimeout(this.searchTimeoutId);
-
- this.searchTimeoutId = setTimeout(
- () => {
- searchUsers(
- term,
- TeamStore.getCurrentId(),
- {in_channel_id: this.props.channel.id},
- (users) => {
- this.setState({search: true, users});
- }
- );
- },
- Constants.SEARCH_TIMEOUT_MILLISECONDS
- );
- }
-
render() {
- let content;
- if (this.state.loading) {
- content = (<LoadingScreen/>);
- } else {
- content = (
- <SearchableUserList
- users={this.state.users}
- usersPerPage={USERS_PER_PAGE}
- total={this.state.total}
- nextPage={this.nextPage}
- search={this.search}
- actions={[this.createRemoveMemberButton]}
- focusOnMount={!UserAgent.isMobile()}
- />
- );
- }
-
return (
<div>
<Modal
@@ -177,7 +36,7 @@ export default class ChannelMembersModal extends React.Component {
<Modal.Title>
<span className='name'>{this.props.channel.display_name}</span>
<FormattedMessage
- id='channel_memebers_modal.members'
+ id='channel_members_modal.members'
defaultMessage=' Members'
/>
</Modal.Title>
@@ -198,7 +57,9 @@ export default class ChannelMembersModal extends React.Component {
<Modal.Body
ref='modalBody'
>
- {content}
+ <MemberListChannel
+ channel={this.props.channel}
+ />
</Modal.Body>
</Modal>
</div>
diff --git a/webapp/components/channel_select.jsx b/webapp/components/channel_select.jsx
index 59bf2f15a..194de3874 100644
--- a/webapp/components/channel_select.jsx
+++ b/webapp/components/channel_select.jsx
@@ -31,12 +31,13 @@ export default class ChannelSelect extends React.Component {
super(props);
this.handleChannelChange = this.handleChannelChange.bind(this);
+ this.filterChannels = this.filterChannels.bind(this);
this.compareByDisplayName = this.compareByDisplayName.bind(this);
AsyncClient.getMoreChannels(true);
this.state = {
- channels: ChannelStore.getAll().sort(this.compareByDisplayName)
+ channels: ChannelStore.getAll().filter(this.filterChannels).sort(this.compareByDisplayName)
};
}
@@ -50,10 +51,19 @@ export default class ChannelSelect extends React.Component {
handleChannelChange() {
this.setState({
- channels: ChannelStore.getAll().concat(ChannelStore.getMoreAll()).sort(this.compareByDisplayName)
+ channels: ChannelStore.getAll().concat(ChannelStore.getMoreAll()).
+ filter(this.filterChannels).sort(this.compareByDisplayName)
});
}
+ filterChannels(channel) {
+ if (channel.display_name) {
+ return true;
+ }
+
+ return false;
+ }
+
compareByDisplayName(channelA, channelB) {
return channelA.display_name.localeCompare(channelB.display_name);
}
diff --git a/webapp/components/channel_switch_modal.jsx b/webapp/components/channel_switch_modal.jsx
index 2f8595c78..fc66e06b1 100644
--- a/webapp/components/channel_switch_modal.jsx
+++ b/webapp/components/channel_switch_modal.jsx
@@ -64,6 +64,7 @@ export default class SwitchChannelModal extends React.Component {
}
onExited() {
+ this.selected = null;
setTimeout(() => {
$('#post_textbox').get(0).focus();
});
@@ -71,6 +72,7 @@ export default class SwitchChannelModal extends React.Component {
onChange(e) {
this.setState({text: e.target.value});
+ this.selected = null;
}
onItemSelected(item) {
@@ -89,6 +91,15 @@ export default class SwitchChannelModal extends React.Component {
handleSubmit() {
let channel = null;
+ if (!this.selected) {
+ if (this.state.text !== '') {
+ this.setState({
+ error: Utils.localizeMessage('channel_switch_modal.not_found', 'No matches found.')
+ });
+ }
+ return;
+ }
+
if (this.selected.type === Constants.DM_CHANNEL) {
const user = UserStore.getProfileByUsername(this.selected.name);
@@ -117,7 +128,7 @@ export default class SwitchChannelModal extends React.Component {
this.onHide();
} else if (this.state.text !== '') {
this.setState({
- error: Utils.localizeMessage('channel_switch_modal.not_found', 'No matches found.')
+ error: Utils.localizeMessage('channel_switch_modal.failed_to_open', 'Failed to open channel.')
});
}
}
diff --git a/webapp/components/claim/components/email_to_ldap.jsx b/webapp/components/claim/components/email_to_ldap.jsx
index 890512803..7d062a957 100644
--- a/webapp/components/claim/components/email_to_ldap.jsx
+++ b/webapp/components/claim/components/email_to_ldap.jsx
@@ -4,9 +4,9 @@
import LoginMfa from 'components/login/components/login_mfa.jsx';
import * as Utils from 'utils/utils.jsx';
-import Client from 'client/web_client.jsx';
import {checkMfa} from 'actions/user_actions.jsx';
+import {emailToLdap} from 'actions/admin_actions.jsx';
import React from 'react';
import {FormattedMessage} from 'react-intl';
@@ -79,7 +79,7 @@ export default class EmailToLDAP extends React.Component {
}
submit(loginId, password, token, ldapId, ldapPassword) {
- Client.emailToLdap(
+ emailToLdap(
loginId,
password,
token,
diff --git a/webapp/components/claim/components/email_to_oauth.jsx b/webapp/components/claim/components/email_to_oauth.jsx
index 3cede15a3..bc5a7bdaa 100644
--- a/webapp/components/claim/components/email_to_oauth.jsx
+++ b/webapp/components/claim/components/email_to_oauth.jsx
@@ -4,10 +4,10 @@
import LoginMfa from 'components/login/components/login_mfa.jsx';
import * as Utils from 'utils/utils.jsx';
-import Client from 'client/web_client.jsx';
import Constants from 'utils/constants.jsx';
import {checkMfa} from 'actions/user_actions.jsx';
+import {emailToOAuth} from 'actions/admin_actions.jsx';
import React from 'react';
import ReactDOM from 'react-dom';
@@ -55,7 +55,7 @@ export default class EmailToOAuth extends React.Component {
}
submit(loginId, password, token) {
- Client.emailToOAuth(
+ emailToOAuth(
loginId,
password,
token,
diff --git a/webapp/components/claim/components/oauth_to_email.jsx b/webapp/components/claim/components/oauth_to_email.jsx
index ed604583d..ffba1c331 100644
--- a/webapp/components/claim/components/oauth_to_email.jsx
+++ b/webapp/components/claim/components/oauth_to_email.jsx
@@ -2,13 +2,13 @@
// See License.txt for license information.
import * as Utils from 'utils/utils.jsx';
-import Client from 'client/web_client.jsx';
import Constants from 'utils/constants.jsx';
import React from 'react';
import ReactDOM from 'react-dom';
import {FormattedMessage} from 'react-intl';
-import {browserHistory} from 'react-router/es6';
+
+import {oauthToEmail} from 'actions/admin_actions.jsx';
export default class OAuthToEmail extends React.Component {
constructor(props) {
@@ -48,14 +48,10 @@ export default class OAuthToEmail extends React.Component {
state.error = null;
this.setState(state);
- Client.oauthToEmail(
+ oauthToEmail(
this.props.email,
password,
- (data) => {
- if (data.follow_link) {
- browserHistory.push(data.follow_link);
- }
- },
+ null,
(err) => {
this.setState({error: err.message});
}
diff --git a/webapp/components/create_comment.jsx b/webapp/components/create_comment.jsx
index 0e9d2a41a..d9d66c8fa 100644
--- a/webapp/components/create_comment.jsx
+++ b/webapp/components/create_comment.jsx
@@ -331,6 +331,9 @@ export default class CreateComment extends React.Component {
draft.fileInfos = draft.fileInfos.concat(fileInfos);
PostStore.storeCommentDraft(this.props.rootId, draft);
+ // Focus on preview if needed
+ this.refs.preview.refs.container.scrollIntoViewIfNeeded();
+
this.setState({uploadsInProgress: draft.uploadsInProgress, fileInfos: draft.fileInfos});
}
@@ -355,6 +358,9 @@ export default class CreateComment extends React.Component {
const fileInfos = this.state.fileInfos;
const uploadsInProgress = this.state.uploadsInProgress;
+ // Clear previous errors
+ this.handleUploadError(null);
+
// id can either be the id of an uploaded file or the client id of an in progress upload
let index = fileInfos.findIndex((info) => info.id === id);
if (index === -1) {
@@ -432,6 +438,7 @@ export default class CreateComment extends React.Component {
fileInfos={this.state.fileInfos}
onRemove={this.removePreview}
uploadsInProgress={this.state.uploadsInProgress}
+ ref='preview'
/>
);
}
diff --git a/webapp/components/create_post.jsx b/webapp/components/create_post.jsx
index e1b2ca059..9269633ff 100644
--- a/webapp/components/create_post.jsx
+++ b/webapp/components/create_post.jsx
@@ -297,6 +297,9 @@ export default class CreatePost extends React.Component {
const fileInfos = Object.assign([], this.state.fileInfos);
const uploadsInProgress = this.state.uploadsInProgress;
+ // Clear previous errors
+ this.handleUploadError(null);
+
// id can either be the id of an uploaded file or the client id of an in progress upload
let index = fileInfos.findIndex((info) => info.id === id);
if (index === -1) {
diff --git a/webapp/components/delete_channel_modal.jsx b/webapp/components/delete_channel_modal.jsx
index 1b642861a..a6577a4a9 100644
--- a/webapp/components/delete_channel_modal.jsx
+++ b/webapp/components/delete_channel_modal.jsx
@@ -1,8 +1,6 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-import * as AsyncClient from 'utils/async_client.jsx';
-import Client from 'client/web_client.jsx';
import {Modal} from 'react-bootstrap';
import TeamStore from 'stores/team_store.jsx';
import Constants from 'utils/constants.jsx';
@@ -13,7 +11,7 @@ import {browserHistory} from 'react-router/es6';
import React from 'react';
-import {loadChannelsForCurrentUser} from 'actions/channel_actions.jsx';
+import {deleteChannel} from 'actions/channel_actions.jsx';
export default class DeleteChannelModal extends React.Component {
constructor(props) {
@@ -31,15 +29,7 @@ export default class DeleteChannelModal extends React.Component {
}
browserHistory.push(TeamStore.getCurrentTeamRelativeUrl() + '/channels/town-square');
- Client.deleteChannel(
- this.props.channel.id,
- () => {
- loadChannelsForCurrentUser();
- },
- (err) => {
- AsyncClient.dispatchError(err, 'handleDelete');
- }
- );
+ deleteChannel(this.props.channel.id);
}
onHide() {
diff --git a/webapp/components/delete_post_modal.jsx b/webapp/components/delete_post_modal.jsx
index 84eef4671..39d4f41f9 100644
--- a/webapp/components/delete_post_modal.jsx
+++ b/webapp/components/delete_post_modal.jsx
@@ -3,13 +3,9 @@
import $ from 'jquery';
import ReactDOM from 'react-dom';
-import Client from 'client/web_client.jsx';
-import PostStore from 'stores/post_store.jsx';
-import ModalStore from 'stores/modal_store.jsx';
import {Modal} from 'react-bootstrap';
-import * as AsyncClient from 'utils/async_client.jsx';
-import AppDispatcher from '../dispatcher/app_dispatcher.jsx';
-import {removePostFromStore} from 'actions/post_actions.jsx';
+import ModalStore from 'stores/modal_store.jsx';
+import {deletePost} from 'actions/post_actions.jsx';
import Constants from 'utils/constants.jsx';
import {FormattedMessage} from 'react-intl';
@@ -51,24 +47,16 @@ export default class DeletePostModal extends React.Component {
}
handleDelete() {
- Client.deletePost(
+ deletePost(
this.state.post.channel_id,
- this.state.post.id,
+ this.state.post,
() => {
- removePostFromStore(this.state.post);
- if (this.state.post.id === PostStore.getSelectedPostId()) {
- AppDispatcher.handleServerAction({
- type: ActionTypes.RECEIVED_POST_SELECTED,
- postId: null
- });
- }
+ this.handleHide();
},
(err) => {
- AsyncClient.dispatchError(err, 'deletePost');
+ this.setState({error: err.message});
}
);
-
- this.handleHide();
}
handleToggle(value, args) {
diff --git a/webapp/components/do_verify_email.jsx b/webapp/components/do_verify_email.jsx
index e0ac3218e..9b6a9ccad 100644
--- a/webapp/components/do_verify_email.jsx
+++ b/webapp/components/do_verify_email.jsx
@@ -2,11 +2,12 @@
// See License.txt for license information.
import {FormattedMessage} from 'react-intl';
-import Client from 'client/web_client.jsx';
import LoadingScreen from './loading_screen.jsx';
import {browserHistory, Link} from 'react-router/es6';
+import {verifyEmail} from 'actions/user_actions.jsx';
+
import React from 'react';
export default class DoVerifyEmail extends React.Component {
@@ -19,15 +20,11 @@ export default class DoVerifyEmail extends React.Component {
};
}
componentWillMount() {
- const uid = this.props.location.query.uid;
- const hid = this.props.location.query.hid;
- const email = this.props.location.query.email;
-
- Client.verifyEmail(
- uid,
- hid,
+ verifyEmail(
+ this.props.location.query.uid,
+ this.props.location.query.hid,
() => {
- browserHistory.push('/login?extra=verified&email=' + email);
+ browserHistory.push('/login?extra=verified&email=' + this.props.location.query.email);
},
(err) => {
this.setState({verifyStatus: 'failure', serverError: err.message});
diff --git a/webapp/components/edit_channel_header_modal.jsx b/webapp/components/edit_channel_header_modal.jsx
index 490b9fb31..0d8eb8acb 100644
--- a/webapp/components/edit_channel_header_modal.jsx
+++ b/webapp/components/edit_channel_header_modal.jsx
@@ -2,13 +2,12 @@
// See License.txt for license information.
import ReactDOM from 'react-dom';
-import AppDispatcher from '../dispatcher/app_dispatcher.jsx';
-import Client from 'client/web_client.jsx';
import Constants from 'utils/constants.jsx';
import * as Utils from 'utils/utils.jsx';
import PreferenceStore from 'stores/preference_store.jsx';
import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'react-intl';
+import {updateChannelHeader} from 'actions/channel_actions.jsx';
import {Modal} from 'react-bootstrap';
@@ -64,17 +63,12 @@ class EditChannelHeaderModal extends React.Component {
handleSubmit() {
this.setState({submitted: true});
- Client.updateChannelHeader(
+ updateChannelHeader(
this.props.channel.id,
this.state.header,
- (channel) => {
+ () => {
this.setState({serverError: ''});
this.onHide();
-
- AppDispatcher.handleServerAction({
- type: Constants.ActionTypes.RECEIVED_CHANNEL,
- channel
- });
},
(err) => {
if (err.id === 'api.context.invalid_param.app_error') {
diff --git a/webapp/components/edit_channel_purpose_modal.jsx b/webapp/components/edit_channel_purpose_modal.jsx
index 7ba2eff2c..4bb876460 100644
--- a/webapp/components/edit_channel_purpose_modal.jsx
+++ b/webapp/components/edit_channel_purpose_modal.jsx
@@ -3,14 +3,13 @@
import PreferenceStore from 'stores/preference_store.jsx';
-import * as AsyncClient from 'utils/async_client.jsx';
-import Client from 'client/web_client.jsx';
import Constants from 'utils/constants.jsx';
import * as Utils from 'utils/utils.jsx';
import React from 'react';
import {Modal} from 'react-bootstrap';
import {FormattedMessage} from 'react-intl';
+import {updateChannelPurpose} from 'actions/channel_actions.jsx';
export default class EditChannelPurposeModal extends React.Component {
constructor(props) {
@@ -64,12 +63,10 @@ export default class EditChannelPurposeModal extends React.Component {
this.setState({submitted: true});
- Client.updateChannelPurpose(
+ updateChannelPurpose(
this.props.channel.id,
this.refs.purpose.value.trim(),
() => {
- AsyncClient.getChannel(this.props.channel.id);
-
this.handleHide();
},
(err) => {
diff --git a/webapp/components/edit_post_modal.jsx b/webapp/components/edit_post_modal.jsx
index 2108ec3d1..b2b607428 100644
--- a/webapp/components/edit_post_modal.jsx
+++ b/webapp/components/edit_post_modal.jsx
@@ -9,11 +9,9 @@ import MessageHistoryStore from 'stores/message_history_store.jsx';
import PreferenceStore from 'stores/preference_store.jsx';
import * as GlobalActions from 'actions/global_actions.jsx';
-import {loadPosts} from 'actions/post_actions.jsx';
+import {updatePost} from 'actions/post_actions.jsx';
-import Client from 'client/web_client.jsx';
import * as UserAgent from 'utils/user_agent.jsx';
-import * as AsyncClient from 'utils/async_client.jsx';
import * as Utils from 'utils/utils.jsx';
import Constants from 'utils/constants.jsx';
const KeyCodes = Constants.KeyCodes;
@@ -91,15 +89,12 @@ export default class EditPostModal extends React.Component {
return;
}
- Client.updatePost(
+ updatePost(
updatedPost,
() => {
- loadPosts(updatedPost.channel_id);
window.scrollTo(0, 0);
},
- (err) => {
- AsyncClient.dispatchError(err, 'updatePost');
- }
+ Boolean(PostStore.getFocusedPostId()) // If there is focused post we need to update that post's store too.
);
$('#edit_post').modal('hide');
@@ -125,6 +120,17 @@ export default class EditPostModal extends React.Component {
}
handleEditPostEvent(options) {
+ var post = PostStore.getPost(options.channelId, options.postId);
+ if (global.window.mm_license.IsLicensed === 'true') {
+ if (global.window.mm_config.AllowEditPost === Constants.ALLOW_EDIT_POST_NEVER) {
+ return;
+ }
+ if (global.window.mm_config.AllowEditPost === Constants.ALLOW_EDIT_POST_TIME_LIMIT) {
+ if ((post.create_at + (global.window.mm_config.PostEditTimeLimit * 1000)) < Utils.getTimestamp()) {
+ return;
+ }
+ }
+ }
this.setState({
editText: options.message || '',
originalText: options.message || '',
@@ -180,7 +186,10 @@ export default class EditPostModal extends React.Component {
onModalHide() {
if (this.state.refocusId !== '') {
setTimeout(() => {
- $(this.state.refocusId).get(0).focus();
+ const element = $(this.state.refocusId).get(0);
+ if (element) {
+ element.focus();
+ }
});
}
}
diff --git a/webapp/components/file_attachment_list.jsx b/webapp/components/file_attachment_list.jsx
index 3d39d8709..472cd2686 100644
--- a/webapp/components/file_attachment_list.jsx
+++ b/webapp/components/file_attachment_list.jsx
@@ -50,7 +50,7 @@ export default class FileAttachmentList extends React.Component {
return (
<div>
- <div className='post-image__columns'>
+ <div className='post-image__columns clearfix'>
{postFiles}
</div>
<ViewImageModal
diff --git a/webapp/components/file_preview.jsx b/webapp/components/file_preview.jsx
index 53cec7f7b..624bfaf44 100644
--- a/webapp/components/file_preview.jsx
+++ b/webapp/components/file_preview.jsx
@@ -84,7 +84,10 @@ export default class FilePreview extends React.Component {
});
return (
- <div className='file-preview__container'>
+ <div
+ className='file-preview__container'
+ ref='container'
+ >
{previews}
</div>
);
diff --git a/webapp/components/file_upload.jsx b/webapp/components/file_upload.jsx
index 9eff25ab5..a821fedab 100644
--- a/webapp/components/file_upload.jsx
+++ b/webapp/components/file_upload.jsx
@@ -4,7 +4,6 @@
import $ from 'jquery';
import 'jquery-dragster/jquery.dragster.js';
import ReactDOM from 'react-dom';
-import Client from 'client/web_client.jsx';
import Constants from 'utils/constants.jsx';
import ChannelStore from 'stores/channel_store.jsx';
import DelayedAction from 'utils/delayed_action.jsx';
@@ -13,6 +12,8 @@ import * as Utils from 'utils/utils.jsx';
import {intlShape, injectIntl, defineMessages} from 'react-intl';
+import {uploadFile} from 'actions/file_actions.jsx';
+
const holders = defineMessages({
limited: {
id: 'file_upload.limited',
@@ -47,6 +48,7 @@ class FileUpload extends React.Component {
this.cancelUpload = this.cancelUpload.bind(this);
this.pasteUpload = this.pasteUpload.bind(this);
this.keyUpload = this.keyUpload.bind(this);
+ this.handleMaxUploadReached = this.handleMaxUploadReached.bind(this);
this.state = {
requests: {}
@@ -88,13 +90,14 @@ class FileUpload extends React.Component {
// generate a unique id that can be used by other components to refer back to this upload
const clientId = Utils.generateId();
- const request = Client.uploadFile(files[i],
- files[i].name,
- channelId,
- clientId,
- this.fileUploadSuccess.bind(this, channelId),
- this.fileUploadFail.bind(this, clientId, channelId)
- );
+ const request = uploadFile(
+ files[i],
+ files[i].name,
+ channelId,
+ clientId,
+ this.fileUploadSuccess.bind(this, channelId),
+ this.fileUploadFail.bind(this, clientId)
+ );
const requests = this.state.requests;
requests[clientId] = request;
@@ -270,7 +273,8 @@ class FileUpload extends React.Component {
const name = formatMessage(holders.pasted) + d.getFullYear() + '-' + (d.getMonth() + 1) + '-' + d.getDate() + ' ' + hour + '-' + min + '.' + ext;
- const request = Client.uploadFile(file,
+ const request = uploadFile(
+ file,
name,
channelId,
clientId,
@@ -309,6 +313,16 @@ class FileUpload extends React.Component {
}
}
+ handleMaxUploadReached(e) {
+ e.preventDefault();
+
+ const {formatMessage} = this.props.intl;
+
+ this.props.onUploadError(formatMessage(holders.limited, {count: Constants.MAX_UPLOAD_FILES}));
+
+ return false;
+ }
+
render() {
let multiple = true;
if (UserAgent.isMobileApp()) {
@@ -322,10 +336,14 @@ class FileUpload extends React.Component {
accept = 'image/*';
}
+ const channelId = this.props.channelId || ChannelStore.getCurrentId();
+
+ const uploadsRemaining = Constants.MAX_UPLOAD_FILES - this.props.getFileCount(channelId);
+
return (
<span
ref='input'
- className='btn btn-file'
+ className={'btn btn-file' + (uploadsRemaining <= 0 ? ' btn-file__disabled' : '')}
>
<span
className='icon'
@@ -335,7 +353,7 @@ class FileUpload extends React.Component {
ref='fileInput'
type='file'
onChange={this.handleChange}
- onClick={this.props.onClick}
+ onClick={uploadsRemaining > 0 ? this.props.onClick : this.handleMaxUploadReached}
multiple={multiple}
accept={accept}
/>
diff --git a/webapp/components/integrations/components/installed_oauth_app.jsx b/webapp/components/integrations/components/installed_oauth_app.jsx
index 15a79ed4c..a6dea65bf 100644
--- a/webapp/components/integrations/components/installed_oauth_app.jsx
+++ b/webapp/components/integrations/components/installed_oauth_app.jsx
@@ -5,10 +5,10 @@ import React from 'react';
import FormError from 'components/form_error.jsx';
-import Client from 'client/web_client.jsx';
import * as Utils from 'utils/utils.jsx';
import {FormattedMessage, FormattedHTMLMessage} from 'react-intl';
+import {regenerateOAuthAppSecret} from 'actions/admin_actions.jsx';
const FAKE_SECRET = '***************';
@@ -49,7 +49,7 @@ export default class InstalledOAuthApp extends React.Component {
handleRegenerate(e) {
e.preventDefault();
- Client.regenerateOAuthAppSecret(
+ regenerateOAuthAppSecret(
this.props.oauthApp.id,
(data) => {
this.props.oauthApp.client_secret = data.client_secret;
diff --git a/webapp/components/invite_member_modal.jsx b/webapp/components/invite_member_modal.jsx
index f4fd1d712..563c1aba9 100644
--- a/webapp/components/invite_member_modal.jsx
+++ b/webapp/components/invite_member_modal.jsx
@@ -5,13 +5,13 @@ import ReactDOM from 'react-dom';
import * as utils from 'utils/utils.jsx';
import Constants from 'utils/constants.jsx';
const ActionTypes = Constants.ActionTypes;
-import Client from 'client/web_client.jsx';
import * as GlobalActions from 'actions/global_actions.jsx';
import ModalStore from 'stores/modal_store.jsx';
import UserStore from 'stores/user_store.jsx';
import ChannelStore from 'stores/channel_store.jsx';
import TeamStore from 'stores/team_store.jsx';
import ConfirmModal from './confirm_modal.jsx';
+import {inviteMembers} from 'actions/team_actions.jsx';
import {intlShape, injectIntl, defineMessages, FormattedMessage, FormattedHTMLMessage} from 'react-intl';
@@ -142,7 +142,7 @@ class InviteMemberModal extends React.Component {
this.setState({isSendingEmails: true});
- Client.inviteMembers(
+ inviteMembers(
data,
() => {
this.handleHide(false);
diff --git a/webapp/components/logged_in.jsx b/webapp/components/logged_in.jsx
index 841061d48..9282e74ca 100644
--- a/webapp/components/logged_in.jsx
+++ b/webapp/components/logged_in.jsx
@@ -4,7 +4,6 @@
import LoadingScreen from 'components/loading_screen.jsx';
import UserStore from 'stores/user_store.jsx';
-import BrowserStore from 'stores/browser_store.jsx';
import PreferenceStore from 'stores/preference_store.jsx';
import * as GlobalActions from 'actions/global_actions.jsx';
@@ -29,30 +28,6 @@ export default class LoggedIn extends React.Component {
this.onUserChanged = this.onUserChanged.bind(this);
this.setupUser = this.setupUser.bind(this);
- // Force logout of all tabs if one tab is logged out
- $(window).bind('storage', (e) => {
- // when one tab on a browser logs out, it sets __logout__ in localStorage to trigger other tabs to log out
- if (e.originalEvent.key === '__logout__' && e.originalEvent.storageArea === localStorage && e.originalEvent.newValue) {
- // make sure it isn't this tab that is sending the logout signal (only necessary for IE11)
- if (BrowserStore.isSignallingLogout(e.originalEvent.newValue)) {
- return;
- }
-
- console.log('detected logout from a different tab'); //eslint-disable-line no-console
- GlobalActions.emitUserLoggedOutEvent('/', false);
- }
-
- if (e.originalEvent.key === '__login__' && e.originalEvent.storageArea === localStorage && e.originalEvent.newValue) {
- // make sure it isn't this tab that is sending the logout signal (only necessary for IE11)
- if (BrowserStore.isSignallingLogin(e.originalEvent.newValue)) {
- return;
- }
-
- console.log('detected login from a different tab'); //eslint-disable-line no-console
- location.reload();
- }
- });
-
// Because current CSS requires the root tag to have specific stuff
$('#root').attr('class', 'channel-view');
diff --git a/webapp/components/login/login_controller.jsx b/webapp/components/login/login_controller.jsx
index 6dc7af883..535cdfd12 100644
--- a/webapp/components/login/login_controller.jsx
+++ b/webapp/components/login/login_controller.jsx
@@ -6,6 +6,8 @@ import ErrorBar from 'components/error_bar.jsx';
import FormError from 'components/form_error.jsx';
import * as GlobalActions from 'actions/global_actions.jsx';
+import {addUserToTeamFromInvite} from 'actions/team_actions.jsx';
+import {checkMfa, webLogin} from 'actions/user_actions.jsx';
import BrowserStore from 'stores/browser_store.jsx';
import UserStore from 'stores/user_store.jsx';
@@ -47,7 +49,8 @@ export default class LoginController extends React.Component {
samlEnabled: global.window.mm_license.IsLicensed === 'true' && global.window.mm_config.EnableSaml === 'true',
loginId: '', // the browser will set a default for this
password: '',
- showMfa: false
+ showMfa: false,
+ loading: false
};
}
@@ -117,40 +120,38 @@ export default class LoginController extends React.Component {
return;
}
- if (global.window.mm_config.EnableMultifactorAuthentication === 'true') {
- Client.checkMfa(
- loginId,
- (data) => {
- if (data.mfa_required === 'true') {
- this.setState({showMfa: true});
- } else {
- this.submit(loginId, password, '');
- }
- },
- (err) => {
- this.setState({serverError: err.message});
+ checkMfa(
+ loginId,
+ (data) => {
+ if (data && data.mfa_required === 'true') {
+ this.setState({showMfa: true});
+ } else {
+ this.submit(loginId, password, '');
}
- );
- } else {
- this.submit(loginId, password, '');
- }
+ },
+ (err) => {
+ this.setState({serverError: err.message});
+ }
+ );
}
submit(loginId, password, token) {
- this.setState({serverError: null});
+ this.setState({serverError: null, loading: true});
- Client.webLogin(
+ webLogin(
loginId,
password,
token,
() => {
// check for query params brought over from signup_user_complete
- const query = this.props.location.query;
- if (query.id || query.h) {
- Client.addUserToTeamFromInvite(
- query.d,
- query.h,
- query.id,
+ const hash = this.props.location.query.h;
+ const data = this.props.location.query.d;
+ const inviteId = this.props.location.query.id;
+ if (inviteId || hash) {
+ addUserToTeamFromInvite(
+ data,
+ hash,
+ inviteId,
(team) => {
this.finishSignin(team);
},
@@ -172,6 +173,7 @@ export default class LoginController extends React.Component {
err.id === 'ent.ldap.do_login.user_not_registered.app_error') {
this.setState({
showMfa: false,
+ loading: false,
serverError: (
<FormattedMessage
id='login.userNotFound'
@@ -182,6 +184,7 @@ export default class LoginController extends React.Component {
} else if (err.id === 'api.user.check_user_password.invalid.app_error' || err.id === 'ent.ldap.do_login.invalid_password.app_error') {
this.setState({
showMfa: false,
+ loading: false,
serverError: (
<FormattedMessage
id='login.invalidPassword'
@@ -190,7 +193,7 @@ export default class LoginController extends React.Component {
)
});
} else {
- this.setState({showMfa: false, serverError: err.message});
+ this.setState({showMfa: false, serverError: err.message, loading: false});
}
}
);
@@ -348,6 +351,23 @@ export default class LoginController extends React.Component {
errorClass = ' has-error';
}
+ let loginButton =
+ (<FormattedMessage
+ id='login.signIn'
+ defaultMessage='Sign in'
+ />);
+
+ if (this.state.loading) {
+ loginButton =
+ (<span>
+ <span className='fa fa-refresh icon--rotate'/>
+ <FormattedMessage
+ id='login.signInLoading'
+ defaultMessage='Signing in...'
+ />
+ </span>);
+ }
+
loginControls.push(
<form
key='loginBoxes'
@@ -387,10 +407,7 @@ export default class LoginController extends React.Component {
type='submit'
className='btn btn-primary'
>
- <FormattedMessage
- id='login.signIn'
- defaultMessage='Sign in'
- />
+ { loginButton }
</button>
</div>
</div>
diff --git a/webapp/components/member_list_channel.jsx b/webapp/components/member_list_channel.jsx
new file mode 100644
index 000000000..6f8a266ad
--- /dev/null
+++ b/webapp/components/member_list_channel.jsx
@@ -0,0 +1,179 @@
+// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import ChannelMembersDropdown from 'components/channel_members_dropdown.jsx';
+import SearchableUserList from 'components/searchable_user_list.jsx';
+
+import ChannelStore from 'stores/channel_store.jsx';
+import UserStore from 'stores/user_store.jsx';
+import TeamStore from 'stores/team_store.jsx';
+
+import {searchUsers, loadProfilesAndTeamMembersAndChannelMembers, loadTeamMembersAndChannelMembersForProfilesList} from 'actions/user_actions.jsx';
+import {getChannelStats} from 'utils/async_client.jsx';
+
+import Constants from 'utils/constants.jsx';
+
+import * as UserAgent from 'utils/user_agent.jsx';
+
+import React from 'react';
+
+const USERS_PER_PAGE = 50;
+
+export default class MemberListChannel extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.onChange = this.onChange.bind(this);
+ this.onStatsChange = this.onStatsChange.bind(this);
+ this.search = this.search.bind(this);
+ this.loadComplete = this.loadComplete.bind(this);
+
+ this.searchTimeoutId = 0;
+
+ const stats = ChannelStore.getCurrentStats();
+
+ this.state = {
+ users: UserStore.getProfileListInChannel(),
+ teamMembers: Object.assign({}, TeamStore.getMembersInTeam()),
+ channelMembers: Object.assign({}, ChannelStore.getMembersInChannel()),
+ total: stats.member_count,
+ search: false,
+ term: '',
+ loading: true
+ };
+ }
+
+ componentDidMount() {
+ UserStore.addInTeamChangeListener(this.onChange);
+ UserStore.addStatusesChangeListener(this.onChange);
+ TeamStore.addChangeListener(this.onChange);
+ ChannelStore.addChangeListener(this.onChange);
+ ChannelStore.addStatsChangeListener(this.onStatsChange);
+
+ loadProfilesAndTeamMembersAndChannelMembers(0, Constants.PROFILE_CHUNK_SIZE, TeamStore.getCurrentId(), ChannelStore.getCurrentId(), this.loadComplete);
+ getChannelStats(ChannelStore.getCurrentId());
+ }
+
+ componentWillUnmount() {
+ UserStore.removeInTeamChangeListener(this.onChange);
+ UserStore.removeStatusesChangeListener(this.onChange);
+ TeamStore.removeChangeListener(this.onChange);
+ ChannelStore.removeChangeListener(this.onChange);
+ ChannelStore.removeStatsChangeListener(this.onStatsChange);
+ }
+
+ loadComplete() {
+ this.setState({loading: false});
+ }
+
+ onChange(force) {
+ if (this.state.search && !force) {
+ return;
+ } else if (this.state.search) {
+ this.search(this.state.term);
+ return;
+ }
+
+ this.setState({
+ users: UserStore.getProfileListInChannel(),
+ teamMembers: Object.assign({}, TeamStore.getMembersInTeam()),
+ channelMembers: Object.assign({}, ChannelStore.getMembersInChannel())
+ });
+ }
+
+ onStatsChange() {
+ const stats = ChannelStore.getCurrentStats();
+ this.setState({total: stats.member_count});
+ }
+
+ nextPage(page) {
+ loadProfilesAndTeamMembersAndChannelMembers((page + 1) * USERS_PER_PAGE, USERS_PER_PAGE);
+ }
+
+ search(term) {
+ if (term === '') {
+ this.setState({
+ search: false,
+ term,
+ users: UserStore.getProfileListInChannel(),
+ teamMembers: Object.assign([], TeamStore.getMembersInTeam()),
+ channelMembers: Object.assign([], ChannelStore.getMembersInChannel())
+ });
+ return;
+ }
+
+ clearTimeout(this.searchTimeoutId);
+
+ const searchTimeoutId = setTimeout(
+ () => {
+ searchUsers(
+ term,
+ TeamStore.getCurrentId(),
+ {},
+ (users) => {
+ if (searchTimeoutId !== this.searchTimeoutId) {
+ return;
+ }
+
+ this.setState({
+ loading: true,
+ search: true,
+ users,
+ term,
+ teamMembers: Object.assign([], TeamStore.getMembersInTeam()),
+ channelMembers: Object.assign([], ChannelStore.getMembersInChannel())
+ });
+ loadTeamMembersAndChannelMembersForProfilesList(users, TeamStore.getCurrentId(), ChannelStore.getCurrentId(), this.loadComplete);
+ }
+ );
+ },
+ Constants.SEARCH_TIMEOUT_MILLISECONDS
+ );
+
+ this.searchTimeoutId = searchTimeoutId;
+ }
+
+ render() {
+ const teamMembers = this.state.teamMembers;
+ const channelMembers = this.state.channelMembers;
+ const users = this.state.users;
+ const actionUserProps = {};
+
+ let usersToDisplay;
+ if (this.state.loading) {
+ usersToDisplay = null;
+ } else {
+ usersToDisplay = [];
+
+ for (let i = 0; i < users.length; i++) {
+ const user = users[i];
+
+ if (teamMembers[user.id] && channelMembers[user.id]) {
+ usersToDisplay.push(user);
+ actionUserProps[user.id] = {
+ channel: this.props.channel,
+ teamMember: teamMembers[user.id],
+ channelMember: channelMembers[user.id]
+ };
+ }
+ }
+ }
+
+ return (
+ <SearchableUserList
+ users={usersToDisplay}
+ usersPerPage={USERS_PER_PAGE}
+ total={this.state.total}
+ nextPage={this.nextPage}
+ search={this.search}
+ actions={[ChannelMembersDropdown]}
+ actionUserProps={actionUserProps}
+ focusOnMount={!UserAgent.isMobile()}
+ />
+ );
+ }
+}
+
+MemberListChannel.propTypes = {
+ channel: React.PropTypes.object.isRequired
+};
diff --git a/webapp/components/member_list_team.jsx b/webapp/components/member_list_team.jsx
index a9db0e734..df17d7df2 100644
--- a/webapp/components/member_list_team.jsx
+++ b/webapp/components/member_list_team.jsx
@@ -23,6 +23,7 @@ export default class MemberListTeam extends React.Component {
super(props);
this.onChange = this.onChange.bind(this);
+ this.onTeamChange = this.onTeamChange.bind(this);
this.onStatsChange = this.onStatsChange.bind(this);
this.search = this.search.bind(this);
this.loadComplete = this.loadComplete.bind(this);
@@ -44,7 +45,7 @@ export default class MemberListTeam extends React.Component {
componentDidMount() {
UserStore.addInTeamChangeListener(this.onChange);
UserStore.addStatusesChangeListener(this.onChange);
- TeamStore.addChangeListener(this.onChange.bind(null, true));
+ TeamStore.addChangeListener(this.onTeamChange);
TeamStore.addStatsChangeListener(this.onStatsChange);
loadProfilesAndTeamMembers(0, Constants.PROFILE_CHUNK_SIZE, TeamStore.getCurrentId(), this.loadComplete);
@@ -54,7 +55,7 @@ export default class MemberListTeam extends React.Component {
componentWillUnmount() {
UserStore.removeInTeamChangeListener(this.onChange);
UserStore.removeStatusesChangeListener(this.onChange);
- TeamStore.removeChangeListener(this.onChange);
+ TeamStore.removeChangeListener(this.onTeamChange);
TeamStore.removeStatsChangeListener(this.onStatsChange);
}
@@ -62,6 +63,10 @@ export default class MemberListTeam extends React.Component {
this.setState({loading: false});
}
+ onTeamChange() {
+ this.onChange(true);
+ }
+
onChange(force) {
if (this.state.search && !force) {
return;
@@ -90,13 +95,16 @@ export default class MemberListTeam extends React.Component {
clearTimeout(this.searchTimeoutId);
- this.searchTimeoutId = setTimeout(
+ const searchTimeoutId = setTimeout(
() => {
searchUsers(
term,
TeamStore.getCurrentId(),
{},
(users) => {
+ if (searchTimeoutId !== this.searchTimeoutId) {
+ return;
+ }
this.setState({loading: true, search: true, users, term, teamMembers: Object.assign([], TeamStore.getMembersInTeam())});
loadTeamMembersForProfilesList(users, TeamStore.getCurrentId(), this.loadComplete);
}
@@ -104,6 +112,8 @@ export default class MemberListTeam extends React.Component {
},
Constants.SEARCH_TIMEOUT_MILLISECONDS
);
+
+ this.searchTimeoutId = searchTimeoutId;
}
render() {
diff --git a/webapp/components/mfa/mfa_controller.jsx b/webapp/components/mfa/mfa_controller.jsx
index 21b9737f8..cd9497985 100644
--- a/webapp/components/mfa/mfa_controller.jsx
+++ b/webapp/components/mfa/mfa_controller.jsx
@@ -1,6 +1,8 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
+import {emitUserLoggedOutEvent} from 'actions/global_actions.jsx';
+
import React from 'react';
import {FormattedMessage} from 'react-intl';
import {browserHistory, Link} from 'react-router/es6';
@@ -16,13 +18,32 @@ export default class MFAController extends React.Component {
render() {
let backButton;
- if (window.mm_config.EnforceMultifactorAuthentication !== 'true') {
+ if (window.mm_config.EnforceMultifactorAuthentication === 'true') {
+ backButton = (
+ <div className='signup-header'>
+ <a
+ href='#'
+ onClick={(e) => {
+ e.preventDefault();
+ emitUserLoggedOutEvent('/login');
+ }}
+ >
+ <span className='fa fa-chevron-left'/>
+ <FormattedMessage
+ id='web.header.logout'
+ defaultMessage='Logout'
+ />
+ </a>
+ </div>
+ );
+ } else {
backButton = (
<div className='signup-header'>
<Link to='/'>
<span className='fa fa-chevron-left'/>
<FormattedMessage
id='web.header.back'
+ defaultMessage='Back'
/>
</Link>
</div>
diff --git a/webapp/components/more_channels.jsx b/webapp/components/more_channels.jsx
index e4cff451d..d0b5f5399 100644
--- a/webapp/components/more_channels.jsx
+++ b/webapp/components/more_channels.jsx
@@ -107,17 +107,22 @@ export default class MoreChannels extends React.Component {
clearTimeout(this.searchTimeoutId);
- this.searchTimeoutId = setTimeout(
+ const searchTimeoutId = setTimeout(
() => {
searchMoreChannels(
term,
(channels) => {
+ if (searchTimeoutId !== this.searchTimeoutId) {
+ return;
+ }
this.setState({search: true, channels});
}
);
},
SEARCH_TIMEOUT_MILLISECONDS
);
+
+ this.searchTimeoutId = searchTimeoutId;
}
render() {
@@ -196,4 +201,4 @@ export default class MoreChannels extends React.Component {
MoreChannels.propTypes = {
onModalDismissed: React.PropTypes.func,
handleNewChannel: React.PropTypes.func
-}; \ No newline at end of file
+};
diff --git a/webapp/components/navbar.jsx b/webapp/components/navbar.jsx
index 338d4edd1..b54b8701e 100644
--- a/webapp/components/navbar.jsx
+++ b/webapp/components/navbar.jsx
@@ -22,8 +22,6 @@ import PreferenceStore from 'stores/preference_store.jsx';
import ChannelSwitchModal from './channel_switch_modal.jsx';
-import Client from 'client/web_client.jsx';
-import * as AsyncClient from 'utils/async_client.jsx';
import * as Utils from 'utils/utils.jsx';
import * as ChannelUtils from 'utils/channel_utils.jsx';
import * as ChannelActions from 'actions/channel_actions.jsx';
@@ -37,7 +35,7 @@ import {FormattedMessage} from 'react-intl';
import {Popover, OverlayTrigger} from 'react-bootstrap';
-import {Link, browserHistory} from 'react-router/es6';
+import {Link} from 'react-router/es6';
import React from 'react';
@@ -111,23 +109,7 @@ export default class Navbar extends React.Component {
}
handleLeave() {
- var channelId = this.state.channel.id;
-
- Client.leaveChannel(channelId,
- () => {
- ChannelActions.loadChannelsForCurrentUser();
-
- if (this.state.isFavorite) {
- ChannelActions.unmarkFavorite(channelId);
- }
-
- const townsquare = ChannelStore.getByName('town-square');
- browserHistory.push(TeamStore.getCurrentTeamRelativeUrl() + '/channels/' + townsquare.name);
- },
- (err) => {
- AsyncClient.dispatchError(err, 'handleLeave');
- }
- );
+ ChannelActions.leaveChannel(this.state.channel.id);
}
hideSidebars(e) {
diff --git a/webapp/components/new_channel_modal.jsx b/webapp/components/new_channel_modal.jsx
index fc9fd0295..be97532dc 100644
--- a/webapp/components/new_channel_modal.jsx
+++ b/webapp/components/new_channel_modal.jsx
@@ -272,7 +272,7 @@ export default class NewChannelModal extends React.Component {
className='form-control no-resize'
ref='channel_purpose'
rows='4'
- placeholder={Utils.localizeMessage('channel_modal.purpose', 'Purpose')}
+ placeholder={Utils.localizeMessage('channel_modal.purposeEx', 'E.g.: "A channel to file bugs and improvements"')}
maxLength='250'
value={this.props.channelData.purpose}
onChange={this.handleChange}
@@ -309,7 +309,7 @@ export default class NewChannelModal extends React.Component {
className='form-control no-resize'
ref='channel_header'
rows='4'
- placeholder={Utils.localizeMessage('channel_modal.header', 'Header')}
+ placeholder={Utils.localizeMessage('channel_modal.headerEx', 'E.g.: "[Link Title](http://example.com)"')}
maxLength='128'
value={this.props.channelData.header}
onChange={this.handleChange}
diff --git a/webapp/components/password_reset_form.jsx b/webapp/components/password_reset_form.jsx
index b37e07f2d..c6fe2525f 100644
--- a/webapp/components/password_reset_form.jsx
+++ b/webapp/components/password_reset_form.jsx
@@ -2,12 +2,12 @@
// See License.txt for license information.
import ReactDOM from 'react-dom';
-import Client from 'client/web_client.jsx';
import * as Utils from 'utils/utils.jsx';
import Constants from 'utils/constants.jsx';
import {FormattedMessage} from 'react-intl';
-import {browserHistory} from 'react-router/es6';
+
+import {resetPassword} from 'actions/user_actions.jsx';
import React from 'react';
@@ -42,12 +42,11 @@ class PasswordResetForm extends React.Component {
error: null
});
- Client.resetPassword(
+ resetPassword(
this.props.location.query.code,
password,
() => {
this.setState({error: null});
- browserHistory.push('/login?extra=' + Constants.PASSWORD_CHANGE);
},
(err) => {
this.setState({error: err.message});
diff --git a/webapp/components/popover_list_members.jsx b/webapp/components/popover_list_members.jsx
index 9cea3922a..5ffcb687a 100644
--- a/webapp/components/popover_list_members.jsx
+++ b/webapp/components/popover_list_members.jsx
@@ -5,6 +5,11 @@ import ProfilePicture from 'components/profile_picture.jsx';
import TeamStore from 'stores/team_store.jsx';
import UserStore from 'stores/user_store.jsx';
+import ChannelStore from 'stores/channel_store.jsx';
+
+import TeamMembersModal from './team_members_modal.jsx';
+import ChannelMembersModal from './channel_members_modal.jsx';
+import ChannelInviteModal from './channel_invite_modal.jsx';
import {openDirectChannelToUser} from 'actions/channel_actions.jsx';
@@ -22,10 +27,17 @@ export default class PopoverListMembers extends React.Component {
constructor(props) {
super(props);
+ this.showMembersModal = this.showMembersModal.bind(this);
+
this.handleShowDirectChannel = this.handleShowDirectChannel.bind(this);
this.closePopover = this.closePopover.bind(this);
- this.state = {showPopover: false};
+ this.state = {
+ showPopover: false,
+ showTeamMembersModal: false,
+ showChannelMembersModal: false,
+ showChannelInviteModal: false
+ };
}
componentDidUpdate() {
@@ -53,12 +65,31 @@ export default class PopoverListMembers extends React.Component {
this.setState({showPopover: false});
}
+ showMembersModal(e) {
+ e.preventDefault();
+
+ if (ChannelStore.isDefault(this.props.channel)) {
+ this.setState({
+ showPopover: false,
+ showTeamMembersModal: true
+ });
+ } else {
+ this.setState({
+ showPopover: false,
+ showChannelMembersModal: true
+ });
+ }
+ }
+
render() {
const popoverHtml = [];
const members = this.props.members;
const teamMembers = UserStore.getProfilesUsernameMap();
+ let isAdmin = false;
const currentUserId = UserStore.getCurrentId();
+ isAdmin = TeamStore.isTeamAdminForCurrentTeam() || UserStore.isSystemAdminForCurrentUser();
+
if (members && teamMembers) {
members.sort((a, b) => {
const aName = Utils.displayUsername(a.id);
@@ -96,7 +127,7 @@ export default class PopoverListMembers extends React.Component {
key={'popover-member-' + i}
>
<ProfilePicture
- src={`${Client.getUsersRoute()}/${m.id}/image?time=${m.update_at}`}
+ src={`${Client.getUsersRoute()}/${m.id}/image?time=${m.last_picture_update}`}
width='26'
height='26'
/>
@@ -117,17 +148,37 @@ export default class PopoverListMembers extends React.Component {
}
});
+ let membersName = (
+ <FormattedMessage
+ id='members_popover.manageMembers'
+ defaultMessage='Manage Members'
+ />
+ );
+ if (!isAdmin && ChannelStore.isDefault(this.props.channel)) {
+ membersName = (
+ <FormattedMessage
+ id='members_popover.viewMembers'
+ defaultMessage='View Members'
+ />
+ );
+ }
+
popoverHtml.push(
<div
className='more-modal__row'
key={'popover-member-more'}
>
- <div className='col-sm-5'/>
+ <div className='col-sm-3'/>
<div className='more-modal__details'>
<div
className='more-modal__name'
>
- {'...'}
+ <a
+ href='#'
+ onClick={this.showMembersModal}
+ >
+ {membersName}
+ </a>
</div>
</div>
</div>
@@ -146,6 +197,38 @@ export default class PopoverListMembers extends React.Component {
defaultMessage='Members'
/>
);
+
+ let channelMembersModal;
+ if (this.state.showChannelMembersModal) {
+ channelMembersModal = (
+ <ChannelMembersModal
+ onModalDismissed={() => this.setState({showChannelMembersModal: false})}
+ showInviteModal={() => this.setState({showChannelInviteModal: true})}
+ channel={this.props.channel}
+ />
+ );
+ }
+
+ let teamMembersModal;
+ if (this.state.showTeamMembersModal) {
+ teamMembersModal = (
+ <TeamMembersModal
+ onHide={() => this.setState({showTeamMembersModal: false})}
+ isAdmin={isAdmin}
+ />
+ );
+ }
+
+ let channelInviteModal;
+ if (this.state.showChannelInviteModal) {
+ channelInviteModal = (
+ <ChannelInviteModal
+ onHide={() => this.setState({showChannelInviteModal: false})}
+ channel={this.props.channel}
+ />
+ );
+ }
+
return (
<div>
<div
@@ -181,6 +264,9 @@ export default class PopoverListMembers extends React.Component {
<div className='more-modal__list'>{popoverHtml}</div>
</Popover>
</Overlay>
+ {channelMembersModal}
+ {teamMembersModal}
+ {channelInviteModal}
</div>
);
}
diff --git a/webapp/components/post_view/components/post.jsx b/webapp/components/post_view/components/post.jsx
index f052ac4ae..896002a6c 100644
--- a/webapp/components/post_view/components/post.jsx
+++ b/webapp/components/post_view/components/post.jsx
@@ -150,10 +150,10 @@ export default class Post extends React.Component {
}
let timestamp = 0;
- if (!this.props.user || this.props.user.update_at == null) {
- timestamp = this.props.currentUser.update_at;
+ if (!this.props.user || this.props.user.last_picture_update == null) {
+ timestamp = this.props.currentUser.last_picture_update;
} else {
- timestamp = this.props.user.update_at;
+ timestamp = this.props.user.last_picture_update;
}
let sameUserClass = '';
@@ -250,7 +250,11 @@ export default class Post extends React.Component {
}
return (
- <div>
+ <div
+ ref={(div) => {
+ this.domNode = div;
+ }}
+ >
<div
id={'post_' + post.id}
className={'post ' + sameUserClass + ' ' + compactClass + ' ' + rootUser + ' ' + postType + ' ' + currentUserCss + ' ' + shouldHighlightClass + ' ' + systemMessageClass + ' ' + hideControls + ' ' + dropdownOpenedClass}
@@ -285,6 +289,7 @@ export default class Post extends React.Component {
compactDisplay={this.props.compactDisplay}
previewCollapsed={this.props.previewCollapsed}
isCommentMention={this.props.isCommentMention}
+ childComponentDidUpdateFunction={this.props.childComponentDidUpdateFunction}
/>
</div>
</div>
@@ -313,5 +318,6 @@ Post.propTypes = {
useMilitaryTime: React.PropTypes.bool.isRequired,
isFlagged: React.PropTypes.bool,
status: React.PropTypes.string,
- isBusy: React.PropTypes.bool
+ isBusy: React.PropTypes.bool,
+ childComponentDidUpdateFunction: React.PropTypes.func
};
diff --git a/webapp/components/post_view/components/post_attachment_oembed.jsx b/webapp/components/post_view/components/post_attachment_oembed.jsx
deleted file mode 100644
index 359c7cc35..000000000
--- a/webapp/components/post_view/components/post_attachment_oembed.jsx
+++ /dev/null
@@ -1,108 +0,0 @@
-// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import $ from 'jquery';
-import React from 'react';
-
-export default class PostAttachmentOEmbed extends React.Component {
- constructor(props) {
- super(props);
- this.fetchData = this.fetchData.bind(this);
-
- this.isLoading = false;
- }
-
- componentWillMount() {
- this.setState({data: {}});
- }
-
- componentWillReceiveProps(nextProps) {
- if (nextProps.link !== this.props.link) {
- this.isLoading = false;
- this.fetchData(nextProps.link);
- }
- }
-
- componentDidMount() {
- this.fetchData(this.props.link);
- }
-
- fetchData(link) {
- if (!this.isLoading) {
- this.isLoading = true;
- let url = 'https://noembed.com/embed?nowrap=on';
- url += '&url=' + encodeURIComponent(link);
- url += '&maxheight=' + this.props.provider.height;
- return $.ajax({
- url,
- dataType: 'jsonp',
- success: (result) => {
- this.isLoading = false;
- if (result.error) {
- this.setState({data: {}});
- } else {
- this.setState({data: result});
- }
- },
- error: () => {
- this.setState({data: {}});
- }
- });
- }
- return null;
- }
-
- render() {
- let data = {};
- let content;
- if ($.isEmptyObject(this.state.data)) {
- content = <div style={{height: this.props.provider.height}}/>;
- } else {
- data = this.state.data;
- content = (
- <div
- style={{height: this.props.provider.height}}
- dangerouslySetInnerHTML={{__html: data.html}}
- />
- );
- }
-
- return (
- <div
- className='attachment attachment--oembed'
- ref='attachment'
- >
- <div className='attachment__content'>
- <div
- className={'clearfix attachment__container'}
- >
- <h1
- className='attachment__title'
- >
- <a
- className='attachment__title-link'
- href={data.url}
- target='_blank'
- rel='noopener noreferrer'
- >
- {data.title}
- </a>
- </h1>
- <div >
- <div
- className={'attachment__body attachment__body--no_thumb'}
- >
- {content}
- </div>
- </div>
- </div>
- </div>
- </div>
- );
- }
-}
-
-PostAttachmentOEmbed.propTypes = {
- link: React.PropTypes.string.isRequired,
- provider: React.PropTypes.object.isRequired
-};
diff --git a/webapp/components/post_view/components/post_attachment_opengraph.jsx b/webapp/components/post_view/components/post_attachment_opengraph.jsx
new file mode 100644
index 000000000..20beaed51
--- /dev/null
+++ b/webapp/components/post_view/components/post_attachment_opengraph.jsx
@@ -0,0 +1,212 @@
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import React from 'react';
+
+import OpenGraphStore from 'stores/opengraph_store.jsx';
+import * as Utils from 'utils/utils.jsx';
+import * as CommonUtils from 'utils/commons.jsx';
+import {requestOpenGraphMetadata} from 'actions/global_actions.jsx';
+
+export default class PostAttachmentOpenGraph extends React.Component {
+ constructor(props) {
+ super(props);
+ this.imageDimentions = { // Image dimentions in pixels.
+ height: 150,
+ width: 150
+ };
+ this.maxDescriptionLength = 300;
+ this.descriptionEllipsis = '...';
+ this.fetchData = this.fetchData.bind(this);
+ this.onOpenGraphMetadataChange = this.onOpenGraphMetadataChange.bind(this);
+ this.toggleImageVisibility = this.toggleImageVisibility.bind(this);
+ this.onImageLoad = this.onImageLoad.bind(this);
+ }
+
+ componentWillMount() {
+ this.setState({
+ data: {},
+ imageLoaded: false,
+ imageVisible: this.props.previewCollapsed.startsWith('false')
+ });
+ this.fetchData(this.props.link);
+ }
+
+ componentWillReceiveProps(nextProps) {
+ this.setState({imageVisible: nextProps.previewCollapsed.startsWith('false')});
+ if (!Utils.areObjectsEqual(nextProps.link, this.props.link)) {
+ this.fetchData(nextProps.link);
+ }
+ }
+
+ shouldComponentUpdate(nextProps, nextState) {
+ if (nextState.imageVisible !== this.state.imageVisible) {
+ return true;
+ }
+ if (nextState.imageLoaded !== this.state.imageLoaded) {
+ return true;
+ }
+ if (!Utils.areObjectsEqual(nextState.data, this.state.data)) {
+ return true;
+ }
+ return false;
+ }
+
+ componentDidMount() {
+ OpenGraphStore.addUrlDataChangeListener(this.onOpenGraphMetadataChange);
+ }
+
+ componentDidUpdate() {
+ if (this.props.childComponentDidUpdateFunction) {
+ this.props.childComponentDidUpdateFunction();
+ }
+ }
+
+ componentWillUnmount() {
+ OpenGraphStore.removeUrlDataChangeListener(this.onOpenGraphMetadataChange);
+ }
+
+ onOpenGraphMetadataChange(url) {
+ if (url === this.props.link) {
+ this.fetchData(url);
+ }
+ }
+
+ fetchData(url) {
+ const data = OpenGraphStore.getOgInfo(url);
+ this.setState({data, imageLoaded: false});
+ if (Utils.isEmptyObject(data)) {
+ requestOpenGraphMetadata(url);
+ }
+ }
+
+ getBestImageUrl() {
+ const nearestPointData = CommonUtils.getNearestPoint(this.imageDimentions, this.state.data.images, 'width', 'height');
+
+ const bestImage = nearestPointData.nearestPoint;
+ const bestImageLte = nearestPointData.nearestPointLte; // Best image <= 150px height and width
+
+ let finalBestImage;
+
+ if (
+ !Utils.isEmptyObject(bestImageLte) &&
+ bestImageLte.height <= this.imageDimentions.height &&
+ bestImageLte.width <= this.imageDimentions.width
+ ) {
+ finalBestImage = bestImageLte;
+ } else {
+ finalBestImage = bestImage;
+ }
+
+ return finalBestImage.secure_url || finalBestImage.url;
+ }
+
+ toggleImageVisibility() {
+ this.setState({imageVisible: !this.state.imageVisible});
+ }
+
+ onImageLoad() {
+ this.setState({imageLoaded: true});
+ }
+
+ loadImage(src) {
+ const img = new Image();
+ img.onload = this.onImageLoad;
+ img.src = src;
+ }
+
+ imageToggleAnchoreTag(imageUrl) {
+ if (imageUrl) {
+ return (
+ <a
+ className={'post__embed-visibility'}
+ data-expanded={this.state.imageVisible}
+ aria-label='Toggle Embed Visibility'
+ onClick={this.toggleImageVisibility}
+ />
+ );
+ }
+ return null;
+ }
+
+ imageTag(imageUrl) {
+ if (imageUrl && this.state.imageVisible) {
+ return (
+ <img
+ className={this.state.imageLoaded ? 'attachment__image' : 'attachment__image loading'}
+ src={this.state.imageLoaded ? imageUrl : null}
+ />
+ );
+ }
+ return null;
+ }
+
+ render() {
+ if (Utils.isEmptyObject(this.state.data) || Utils.isEmptyObject(this.state.data.description)) {
+ return null;
+ }
+
+ const data = this.state.data;
+ const imageUrl = this.getBestImageUrl();
+ var description = data.description;
+
+ if (description.length > this.maxDescriptionLength) {
+ description = description.substring(0, this.maxDescriptionLength - this.descriptionEllipsis.length) + this.descriptionEllipsis;
+ }
+
+ if (imageUrl && this.state.imageVisible) {
+ this.loadImage(imageUrl);
+ }
+
+ return (
+ <div
+ className='attachment attachment--oembed'
+ ref='attachment'
+ >
+ <div className='attachment__content'>
+ <div
+ className={'clearfix attachment__container'}
+ >
+ <span className='sitename'>{data.site_name}</span>
+ <h1
+ className='attachment__title has-link'
+ >
+ <a
+ className='attachment__title-link'
+ href={data.url || this.props.link}
+ target='_blank'
+ rel='noopener noreferrer'
+ title={data.title || data.url || this.props.link}
+ >
+ {data.title || data.url || this.props.link}
+ </a>
+ </h1>
+ <div >
+ <div
+ className={'attachment__body attachment__body--no_thumb'}
+ >
+ <div>
+ <div>
+ {description} &nbsp;
+ {this.imageToggleAnchoreTag(imageUrl)}
+ </div>
+ {this.imageTag(imageUrl)}
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ );
+ }
+}
+
+PostAttachmentOpenGraph.defaultProps = {
+ previewCollapsed: 'false'
+};
+
+PostAttachmentOpenGraph.propTypes = {
+ link: React.PropTypes.string.isRequired,
+ childComponentDidUpdateFunction: React.PropTypes.func,
+ previewCollapsed: React.PropTypes.string
+};
diff --git a/webapp/components/post_view/components/post_body.jsx b/webapp/components/post_view/components/post_body.jsx
index 60e682e8d..10c24aab2 100644
--- a/webapp/components/post_view/components/post_body.jsx
+++ b/webapp/components/post_view/components/post_body.jsx
@@ -188,6 +188,7 @@ export default class PostBody extends React.Component {
message={messageWrapper}
compactDisplay={this.props.compactDisplay}
previewCollapsed={this.props.previewCollapsed}
+ childComponentDidUpdateFunction={this.props.childComponentDidUpdateFunction}
/>
);
}
@@ -221,5 +222,6 @@ PostBody.propTypes = {
handleCommentClick: React.PropTypes.func.isRequired,
compactDisplay: React.PropTypes.bool,
previewCollapsed: React.PropTypes.string,
- isCommentMention: React.PropTypes.bool
+ isCommentMention: React.PropTypes.bool,
+ childComponentDidUpdateFunction: React.PropTypes.func
};
diff --git a/webapp/components/post_view/components/post_body_additional_content.jsx b/webapp/components/post_view/components/post_body_additional_content.jsx
index a65b608d7..cad618de0 100644
--- a/webapp/components/post_view/components/post_body_additional_content.jsx
+++ b/webapp/components/post_view/components/post_body_additional_content.jsx
@@ -2,12 +2,11 @@
// See License.txt for license information.
import PostAttachmentList from './post_attachment_list.jsx';
-import PostAttachmentOEmbed from './post_attachment_oembed.jsx';
+import PostAttachmentOpenGraph from './post_attachment_opengraph.jsx';
import PostImage from './post_image.jsx';
import YoutubeVideo from 'components/youtube_video.jsx';
import Constants from 'utils/constants.jsx';
-import OEmbedProviders from './providers.json';
import * as Utils from 'utils/utils.jsx';
import React from 'react';
@@ -17,22 +16,24 @@ export default class PostBodyAdditionalContent extends React.Component {
super(props);
this.getSlackAttachment = this.getSlackAttachment.bind(this);
- this.getOEmbedProvider = this.getOEmbedProvider.bind(this);
this.generateToggleableEmbed = this.generateToggleableEmbed.bind(this);
this.generateStaticEmbed = this.generateStaticEmbed.bind(this);
this.toggleEmbedVisibility = this.toggleEmbedVisibility.bind(this);
this.isLinkToggleable = this.isLinkToggleable.bind(this);
+ this.handleLinkLoadError = this.handleLinkLoadError.bind(this);
this.state = {
embedVisible: props.previewCollapsed.startsWith('false'),
- link: Utils.extractFirstLink(props.post.message)
+ link: Utils.extractFirstLink(props.post.message),
+ linkLoadError: false
};
}
componentWillReceiveProps(nextProps) {
this.setState({
embedVisible: nextProps.previewCollapsed.startsWith('false'),
- link: Utils.extractFirstLink(nextProps.post.message)
+ link: Utils.extractFirstLink(nextProps.post.message),
+ linkLoadError: false
});
}
@@ -46,6 +47,9 @@ export default class PostBodyAdditionalContent extends React.Component {
if (nextState.embedVisible !== this.state.embedVisible) {
return true;
}
+ if (nextState.linkLoadError !== this.state.linkLoadError) {
+ return true;
+ }
return false;
}
@@ -66,25 +70,11 @@ export default class PostBodyAdditionalContent extends React.Component {
);
}
- getOEmbedProvider(link) {
- for (let i = 0; i < OEmbedProviders.length; i++) {
- for (let j = 0; j < OEmbedProviders[i].patterns.length; j++) {
- if (link.match(OEmbedProviders[i].patterns[j])) {
- return OEmbedProviders[i];
- }
- }
- }
-
- return null;
- }
-
isLinkImage(link) {
- for (let i = 0; i < Constants.IMAGE_TYPES.length; i++) {
- const imageType = Constants.IMAGE_TYPES[i];
- const suffix = link.substring(link.length - (imageType.length + 1));
- if (suffix === '.' + imageType || suffix === '=' + imageType) {
- return true;
- }
+ const regex = /.+\/(.+\.(?:jpg|gif|bmp|png|jpeg))(?:\?.*)?$/i;
+ const match = link.match(regex);
+ if (match && match[1]) {
+ return true;
}
return false;
@@ -107,6 +97,12 @@ export default class PostBodyAdditionalContent extends React.Component {
return false;
}
+ handleLinkLoadError() {
+ this.setState({
+ linkLoadError: true
+ });
+ }
+
generateToggleableEmbed() {
const link = this.state.link;
if (!link) {
@@ -128,6 +124,7 @@ export default class PostBodyAdditionalContent extends React.Component {
<PostImage
channelId={this.props.post.channel_id}
link={link}
+ onLinkLoadError={this.handleLinkLoadError}
/>
);
}
@@ -141,39 +138,21 @@ export default class PostBodyAdditionalContent extends React.Component {
}
const link = Utils.extractFirstLink(this.props.post.message);
- if (!link) {
- return null;
- }
-
- if (Utils.isFeatureEnabled(Constants.PRE_RELEASE_FEATURES.EMBED_PREVIEW)) {
- const provider = this.getOEmbedProvider(link);
-
- if (provider) {
- return (
- <PostAttachmentOEmbed
- provider={provider}
- link={link}
- />
- );
- }
+ if (link && Utils.isFeatureEnabled(Constants.PRE_RELEASE_FEATURES.EMBED_PREVIEW)) {
+ return (
+ <PostAttachmentOpenGraph
+ link={link}
+ childComponentDidUpdateFunction={this.props.childComponentDidUpdateFunction}
+ previewCollapsed={this.props.previewCollapsed}
+ />
+ );
}
return null;
}
render() {
- const staticEmbed = this.generateStaticEmbed();
-
- if (staticEmbed) {
- return (
- <div>
- {this.props.message}
- {staticEmbed}
- </div>
- );
- }
-
- if (this.isLinkToggleable()) {
+ if (this.isLinkToggleable() && !this.state.linkLoadError) {
const messageWithToggle = [];
// if message has only one line and starts with a link place toggle in this only line
@@ -213,6 +192,17 @@ export default class PostBodyAdditionalContent extends React.Component {
);
}
+ const staticEmbed = this.generateStaticEmbed();
+
+ if (staticEmbed) {
+ return (
+ <div>
+ {this.props.message}
+ {staticEmbed}
+ </div>
+ );
+ }
+
return this.props.message;
}
}
@@ -224,5 +214,6 @@ PostBodyAdditionalContent.propTypes = {
post: React.PropTypes.object.isRequired,
message: React.PropTypes.element.isRequired,
compactDisplay: React.PropTypes.bool,
- previewCollapsed: React.PropTypes.string
+ previewCollapsed: React.PropTypes.string,
+ childComponentDidUpdateFunction: React.PropTypes.func
};
diff --git a/webapp/components/post_view/components/post_image.jsx b/webapp/components/post_view/components/post_image.jsx
index d1d1a6c7a..9a761bfca 100644
--- a/webapp/components/post_view/components/post_image.jsx
+++ b/webapp/components/post_view/components/post_image.jsx
@@ -53,6 +53,9 @@ export default class PostImageEmbed extends React.Component {
errored: true,
loaded: true
});
+ if (this.props.onLinkLoadError) {
+ this.props.onLinkLoadError();
+ }
}
render() {
@@ -79,5 +82,6 @@ export default class PostImageEmbed extends React.Component {
}
PostImageEmbed.propTypes = {
- link: React.PropTypes.string.isRequired
+ link: React.PropTypes.string.isRequired,
+ onLinkLoadError: React.PropTypes.func
};
diff --git a/webapp/components/post_view/components/post_info.jsx b/webapp/components/post_view/components/post_info.jsx
index aa204add1..3f38bdffe 100644
--- a/webapp/components/post_view/components/post_info.jsx
+++ b/webapp/components/post_view/components/post_info.jsx
@@ -8,12 +8,10 @@ import PostTime from './post_time.jsx';
import * as GlobalActions from 'actions/global_actions.jsx';
import * as PostActions from 'actions/post_actions.jsx';
-import TeamStore from 'stores/team_store.jsx';
-import UserStore from 'stores/user_store.jsx';
-
import * as Utils from 'utils/utils.jsx';
import * as PostUtils from 'utils/post_utils.jsx';
import Constants from 'utils/constants.jsx';
+import DelayedAction from 'utils/delayed_action.jsx';
import {Tooltip, OverlayTrigger} from 'react-bootstrap';
import React from 'react';
@@ -23,31 +21,42 @@ export default class PostInfo extends React.Component {
constructor(props) {
super(props);
- this.handleDropdownClick = this.handleDropdownClick.bind(this);
+ this.handleDropdownOpened = this.handleDropdownOpened.bind(this);
this.handlePermalink = this.handlePermalink.bind(this);
this.removePost = this.removePost.bind(this);
this.flagPost = this.flagPost.bind(this);
this.unflagPost = this.unflagPost.bind(this);
+
+ this.canEdit = false;
+ this.canDelete = false;
+ this.editDisableAction = new DelayedAction(this.handleEditDisable);
}
- handleDropdownClick(e) {
- var position = $('#post-list').height() - $(e.target).offset().top;
- var dropdown = $(e.target).closest('.col__reply').find('.dropdown-menu');
+ handleDropdownOpened() {
+ this.props.handleDropdownOpened(true);
+
+ const position = $('#post-list').height() - $(this.refs.dropdownToggle).offset().top;
+ const dropdown = $(this.refs.dropdown);
+
if (position < dropdown.height()) {
dropdown.addClass('bottom');
}
}
+ handleEditDisable() {
+ this.canEdit = false;
+ }
+
componentDidMount() {
- $('#post_dropdown' + this.props.post.id).on('shown.bs.dropdown', () => this.props.handleDropdownOpened(true));
+ $('#post_dropdown' + this.props.post.id).on('shown.bs.dropdown', this.handleDropdownOpened);
$('#post_dropdown' + this.props.post.id).on('hidden.bs.dropdown', () => this.props.handleDropdownOpened(false));
}
createDropdown() {
var post = this.props.post;
- var isOwner = this.props.currentUser.id === post.user_id;
- var isAdmin = TeamStore.isTeamAdminForCurrentTeam() || UserStore.isSystemAdminForCurrentUser();
- const isSystemMessage = post.type && post.type.startsWith(Constants.SYSTEM_MESSAGE_PREFIX);
+
+ this.canDelete = PostUtils.canDeletePost(post);
+ this.canEdit = PostUtils.canEditPost(post, this.editDisableAction);
if (post.state === Constants.POST_FAILED || post.state === Constants.POST_LOADING) {
return '';
@@ -139,7 +148,7 @@ export default class PostInfo extends React.Component {
</li>
);
- if (isOwner || isAdmin) {
+ if (this.canDelete) {
dropdownContents.push(
<li
key='deletePost'
@@ -162,12 +171,12 @@ export default class PostInfo extends React.Component {
);
}
- if (isOwner && !isSystemMessage) {
+ if (this.canEdit) {
dropdownContents.push(
<li
key='editPost'
role='presentation'
- className='dropdown-submenu'
+ className={this.canEdit ? 'dropdown-submenu' : 'dropdown-submenu hide'}
>
<a
href='#'
@@ -199,15 +208,16 @@ export default class PostInfo extends React.Component {
id={'post_dropdown' + this.props.post.id}
>
<a
+ ref='dropdownToggle'
href='#'
className='dropdown-toggle post__dropdown theme'
type='button'
data-toggle='dropdown'
aria-expanded='false'
- onClick={this.handleDropdownClick}
/>
<div className='dropdown-menu__content'>
<ul
+ ref='dropdown'
className='dropdown-menu'
role='menu'
>
diff --git a/webapp/components/post_view/components/post_list.jsx b/webapp/components/post_view/components/post_list.jsx
index 29358122b..7550db348 100644
--- a/webapp/components/post_view/components/post_list.jsx
+++ b/webapp/components/post_view/components/post_list.jsx
@@ -45,6 +45,7 @@ export default class PostList extends React.Component {
this.scrollToBottom = this.scrollToBottom.bind(this);
this.scrollToBottomAnimated = this.scrollToBottomAnimated.bind(this);
this.handleKeyDown = this.handleKeyDown.bind(this);
+ this.childComponentDidUpdate = this.childComponentDidUpdate.bind(this);
this.jumpToPostNode = null;
this.wasAtBottom = true;
@@ -159,7 +160,7 @@ export default class PostList extends React.Component {
const id = this.props.postList.order[i];
const element = this.refs[id];
- if (!element || element.offsetTop + element.clientHeight <= this.refs.postlist.scrollTop) {
+ if (!element || !element.domNode || element.domNode.offsetTop + element.domNode.clientHeight <= this.refs.postlist.scrollTop) {
// this post is off the top of the screen so the last one is at the top of the screen
let topPostId;
@@ -347,6 +348,7 @@ export default class PostList extends React.Component {
isFlagged={isFlagged}
status={status}
isBusy={this.props.isBusy}
+ childComponentDidUpdateFunction={this.childComponentDidUpdate}
/>
);
@@ -421,6 +423,11 @@ export default class PostList extends React.Component {
this.scrollToBottom();
}
});
+
+ // This avoids the scroll jumping from top to bottom after the page has rendered (PLT-5025).
+ if (!this.refs.newMessageSeparator) {
+ this.scrollToBottom();
+ }
} else if (this.props.scrollType === ScrollTypes.POST && this.props.scrollPostId) {
window.requestAnimationFrame(() => {
const postNode = ReactDOM.findDOMNode(this.refs[this.props.scrollPostId]);
@@ -487,6 +494,12 @@ export default class PostList extends React.Component {
);
}
+ checkAndUpdateScrolling() {
+ if (this.props.postList != null && this.refs.postlist) {
+ this.updateScrolling();
+ }
+ }
+
componentDidMount() {
if (this.props.postList != null) {
this.updateScrolling();
@@ -504,9 +517,11 @@ export default class PostList extends React.Component {
}
componentDidUpdate() {
- if (this.props.postList != null && this.refs.postlist) {
- this.updateScrolling();
- }
+ this.checkAndUpdateScrolling();
+ }
+
+ childComponentDidUpdate() {
+ this.checkAndUpdateScrolling();
}
render() {
diff --git a/webapp/components/post_view/components/post_message_container.jsx b/webapp/components/post_view/components/post_message_container.jsx
index 2d17e74c4..4e27cd29a 100644
--- a/webapp/components/post_view/components/post_message_container.jsx
+++ b/webapp/components/post_view/components/post_message_container.jsx
@@ -89,7 +89,7 @@ export default class PostMessageContainer extends React.Component {
return (
<PostMessageView
options={this.props.options}
- message={this.props.post.message}
+ post={this.props.post}
emojis={this.state.emojis}
enableFormatting={this.state.enableFormatting}
mentionKeys={this.state.mentionKeys}
diff --git a/webapp/components/post_view/components/post_message_view.jsx b/webapp/components/post_view/components/post_message_view.jsx
index 24f96a8d9..eff791aec 100644
--- a/webapp/components/post_view/components/post_message_view.jsx
+++ b/webapp/components/post_view/components/post_message_view.jsx
@@ -2,14 +2,16 @@
// See License.txt for license information.
import React from 'react';
+import {FormattedMessage} from 'react-intl';
import * as TextFormatting from 'utils/text_formatting.jsx';
import * as Utils from 'utils/utils.jsx';
+import * as PostUtils from 'utils/post_utils.jsx';
export default class PostMessageView extends React.Component {
static propTypes = {
options: React.PropTypes.object.isRequired,
- message: React.PropTypes.string.isRequired,
+ post: React.PropTypes.object.isRequired,
emojis: React.PropTypes.object.isRequired,
enableFormatting: React.PropTypes.bool.isRequired,
mentionKeys: React.PropTypes.arrayOf(React.PropTypes.string).isRequired,
@@ -23,7 +25,7 @@ export default class PostMessageView extends React.Component {
return true;
}
- if (nextProps.message !== this.props.message) {
+ if (nextProps.post.message !== this.props.post.message) {
return true;
}
@@ -47,9 +49,28 @@ export default class PostMessageView extends React.Component {
return false;
}
+ editedIndicator() {
+ return (
+ PostUtils.isEdited(this.props.post) ?
+ <span className='edited'>
+ <FormattedMessage
+ id='post_message_view.edited'
+ defaultMessage='(edited)'
+ />
+ </span> :
+ ''
+ );
+ }
+
render() {
if (!this.props.enableFormatting) {
- return <span>{this.props.message}</span>;
+ return (
+ <span>
+ {this.props.post.message}
+ &nbsp;
+ {this.editedIndicator()}
+ </span>
+ );
}
const options = Object.assign({}, this.props.options, {
@@ -62,10 +83,13 @@ export default class PostMessageView extends React.Component {
});
return (
- <span
- onClick={Utils.handleFormattedTextClick}
- dangerouslySetInnerHTML={{__html: TextFormatting.formatText(this.props.message, options)}}
- />
+ <div>
+ <span
+ onClick={Utils.handleFormattedTextClick}
+ dangerouslySetInnerHTML={{__html: TextFormatting.formatText(this.props.post.message, options)}}
+ />
+ {this.editedIndicator()}
+ </div>
);
}
}
diff --git a/webapp/components/post_view/components/post_time.jsx b/webapp/components/post_view/components/post_time.jsx
index c8e57f6a9..caad12d4a 100644
--- a/webapp/components/post_view/components/post_time.jsx
+++ b/webapp/components/post_view/components/post_time.jsx
@@ -27,7 +27,10 @@ export default class PostTime extends React.Component {
render() {
return (
- <time className='post__time'>
+ <time
+ className='post__time'
+ dateTime={getDateForUnixTicks(this.props.eventTime).toISOString()}
+ >
{getDateForUnixTicks(this.props.eventTime).toLocaleString('en', {hour: '2-digit', minute: '2-digit', hour12: !this.props.useMilitaryTime})}
</time>
);
diff --git a/webapp/components/post_view/components/providers.json b/webapp/components/post_view/components/providers.json
deleted file mode 100644
index b5899c225..000000000
--- a/webapp/components/post_view/components/providers.json
+++ /dev/null
@@ -1,376 +0,0 @@
-[
- {
- "patterns": [
- "http://(?:www\\.)?xkcd\\.com/\\d+/?"
- ],
- "name": "XKCD",
- "height": 110
- },
- {
- "patterns": [
- "https?://soundcloud.com/.*/.*"
- ],
- "name": "SoundCloud",
- "height": 140
- },
- {
- "patterns": [
- "https?://(?:www\\.)?flickr\\.com/.*",
- "https?://flic\\.kr/p/[a-zA-Z0-9]+"
- ],
- "name": "Flickr",
- "height": 110
- },
- {
- "patterns": [
- "http://www\\.ted\\.com/talks/.+\\.html"
- ],
- "name": "TED",
- "height": 110
- },
- {
- "patterns": [
- "http://(?:www\\.)?theverge\\.com/\\d{4}/\\d{1,2}/\\d{1,2}/\\d+/[^/]+/?$"
- ],
- "name": "The Verge",
- "height": 110
- },
- {
- "patterns": [
- "http://.*\\.viddler\\.com/.*"
- ],
- "name": "Viddler",
- "height": 110
- },
- {
- "patterns": [
- "https?://(?:www\\.)?avclub\\.com/article/[^/]+/?$"
- ],
- "name": "The AV Club",
- "height": 110
- },
- {
- "patterns": [
- "https?://(?:www\\.)?wired\\.com/([^/]+/)?\\d+/\\d+/[^/]+/?$"
- ],
- "name": "Wired",
- "height": 110
- },
- {
- "patterns": [
- "http://www\\.theonion\\.com/articles/[^/]+/?"
- ],
- "name": "The Onion",
- "height": 110
- },
- {
- "patterns": [
- "http://yfrog\\.com/[0-9a-zA-Z]+/?$"
- ],
- "name": "YFrog",
- "height": 110
- },
- {
- "patterns": [
- "http://www\\.duffelblog\\.com/\\d{4}/\\d{1,2}/[^/]+/?$"
- ],
- "name": "The Duffel Blog",
- "height": 110
- },
- {
- "patterns": [
- "http://www\\.clickhole\\.com/article/[^/]+/?"
- ],
- "name": "Clickhole",
- "height": 110
- },
- {
- "patterns": [
- "https?://(?:www.)?skitch.com/([^/]+)/[^/]+/.+",
- "http://skit.ch/[^/]+"
- ],
- "name": "Skitch",
- "height": 110
- },
- {
- "patterns": [
- "https?://(alpha|posts|photos)\\.app\\.net/.*"
- ],
- "name": "ADN",
- "height": 110
- },
- {
- "patterns": [
- "https?://gist\\.github\\.com/(?:[-0-9a-zA-Z]+/)?([0-9a-fA-f]+)"
- ],
- "name": "Gist",
- "height": 110
- },
- {
- "patterns": [
- "https?://www\\.(dropbox\\.com/s/.+\\.(?:jpg|png|gif))",
- "https?://db\\.tt/[a-zA-Z0-9]+"
- ],
- "name": "Dropbox",
- "height": 110
- },
- {
- "patterns": [
- "https?://[^\\.]+\\.wikipedia\\.org/wiki/(?!Talk:)[^#]+(?:#(.+))?"
- ],
- "name": "Wikipedia",
- "height": 110
- },
- {
- "patterns": [
- "http://www.traileraddict.com/trailer/[^/]+/trailer"
- ],
- "name": "TrailerAddict",
- "height": 110
- },
- {
- "patterns": [
- "http://lockerz\\.com/[sd]/\\d+"
- ],
- "name": "Lockerz",
- "height": 110
- },
- {
- "patterns": [
- "http://gifuk\\.com/s/[0-9a-f]{16}"
- ],
- "name": "GIFUK",
- "height": 110
- },
- {
- "patterns": [
- "http://trailers\\.apple\\.com/trailers/[^/]+/[^/]+"
- ],
- "name": "iTunes Movie Trailers",
- "height": 110
- },
- {
- "patterns": [
- "http://gfycat\\.com/([a-zA-Z]+)"
- ],
- "name": "Gfycat",
- "height": 110
- },
- {
- "patterns": [
- "http://bash\\.org/\\?(\\d+)"
- ],
- "name": "Bash.org",
- "height": 110
- },
- {
- "patterns": [
- "http://arstechnica\\.com/[^/]+/\\d+/\\d+/[^/]+/?$"
- ],
- "name": "Ars Technica",
- "height": 110
- },
- {
- "patterns": [
- "http://imgur\\.com/gallery/[0-9a-zA-Z]+"
- ],
- "name": "Imgur",
- "height": 110
- },
- {
- "patterns": [
- "http://www\\.asciiartfarts\\.com/[0-9]+\\.html"
- ],
- "name": "ASCII Art Farts",
- "height": 110
- },
- {
- "patterns": [
- "http://www\\.monoprice\\.com/products/product\\.asp\\?.*p_id=\\d+"
- ],
- "name": "Monoprice",
- "height": 110
- },
- {
- "patterns": [
- "http://boingboing\\.net/\\d{4}/\\d{2}/\\d{2}/[^/]+\\.html"
- ],
- "name": "Boing Boing",
- "height": 110
- },
- {
- "patterns": [
- "https?://github\\.com/([^/]+)/([^/]+)/commit/(.+)",
- "http://git\\.io/[_0-9a-zA-Z]+"
- ],
- "name": "Github Commit",
- "height": 110
- },
- {
- "patterns": [
- "https?://open\\.spotify\\.com/(track|album)/([0-9a-zA-Z]{22})"
- ],
- "name": "Spotify",
- "height": 110
- },
- {
- "patterns": [
- "https?://path\\.com/p/([0-9a-zA-Z]+)$"
- ],
- "name": "Path",
- "height": 110
- },
- {
- "patterns": [
- "http://www.funnyordie.com/videos/[^/]+/.+"
- ],
- "name": "Funny or Die",
- "height": 110
- },
- {
- "patterns": [
- "http://(?:www\\.)?twitpic\\.com/([^/]+)"
- ],
- "name": "Twitpic",
- "height": 110
- },
- {
- "patterns": [
- "https?://www\\.giantbomb\\.com/videos/[^/]+/\\d+-\\d+/?"
- ],
- "name": "GiantBomb",
- "height": 110
- },
- {
- "patterns": [
- "http://(?:www\\.)?beeradvocate\\.com/beer/profile/\\d+/\\d+"
- ],
- "name": "Beer Advocate",
- "height": 110
- },
- {
- "patterns": [
- "http://(?:www\\.)?imdb.com/title/(tt\\d+)"
- ],
- "name": "IMDB",
- "height": 110
- },
- {
- "patterns": [
- "http://cl\\.ly/(?:image/)?[0-9a-zA-Z]+/?$"
- ],
- "name": "CloudApp",
- "height": 110
- },
- {
- "patterns": [
- "http://clyp\\.it/.*"
- ],
- "name": "Clyp",
- "height": 110
- },
- {
- "patterns": [
- "http://www\\.hulu\\.com/watch/.*"
- ],
- "name": "Hulu",
- "height": 110
- },
- {
- "patterns": [
- "https?://(?:www|mobile\\.)?twitter\\.com/(?:#!/)?[^/]+/status(?:es)?/(\\d+)/?$",
- "https?://t\\.co/[a-zA-Z0-9]+"
- ],
- "name": "Twitter",
- "height": 110
- },
- {
- "patterns": [
- "https?://(?:www\\.)?vimeo\\.com/.+"
- ],
- "name": "Vimeo",
- "height": 110
- },
- {
- "patterns": [
- "http://www\\.amazon\\.com/(?:.+/)?[gd]p/(?:product/)?(?:tags-on-product/)?([a-zA-Z0-9]+)",
- "http://amzn\\.com/([^/]+)"
- ],
- "name": "Amazon",
- "height": 110
- },
- {
- "patterns": [
- "http://qik\\.com/video/.*"
- ],
- "name": "Qik",
- "height": 110
- },
- {
- "patterns": [
- "http://www\\.rdio\\.com/artist/[^/]+/album/[^/]+/?",
- "http://www\\.rdio\\.com/artist/[^/]+/album/[^/]+/track/[^/]+/?",
- "http://www\\.rdio\\.com/people/[^/]+/playlists/\\d+/[^/]+"
- ],
- "name": "Rdio",
- "height": 110
- },
- {
- "patterns": [
- "http://www\\.slideshare\\.net/.*/.*"
- ],
- "name": "SlideShare",
- "height": 110
- },
- {
- "patterns": [
- "http://imgur\\.com/([0-9a-zA-Z]+)$"
- ],
- "name": "Imgur",
- "height": 110
- },
- {
- "patterns": [
- "https?://instagr(?:\\.am|am\\.com)/p/.+"
- ],
- "name": "Instagram",
- "height": 110
- },
- {
- "patterns": [
- "http://www\\.twitlonger\\.com/show/[a-zA-Z0-9]+",
- "http://tl\\.gd/[^/]+"
- ],
- "name": "Twitlonger",
- "height": 110
- },
- {
- "patterns": [
- "https?://vine.co/v/[a-zA-Z0-9]+"
- ],
- "name": "Vine",
- "height": 490
- },
- {
- "patterns": [
- "http://www\\.urbandictionary\\.com/define\\.php\\?term=.+"
- ],
- "name": "Urban Dictionary",
- "height": 110
- },
- {
- "patterns": [
- "http://picplz\\.com/user/[^/]+/pic/[^/]+"
- ],
- "name": "Picplz",
- "height": 110
- },
- {
- "patterns": [
- "https?://(?:www\\.)?twitter\\.com/(?:#!/)?[^/]+/status(?:es)?/(\\d+)/photo/\\d+(?:/large|/)?$",
- "https?://pic\\.twitter\\.com/.+"
- ],
- "name": "Twitter",
- "height": 110
- }
-]
diff --git a/webapp/components/post_view/post_view_controller.jsx b/webapp/components/post_view/post_view_controller.jsx
index a18a73b86..a18d0ac38 100644
--- a/webapp/components/post_view/post_view_controller.jsx
+++ b/webapp/components/post_view/post_view_controller.jsx
@@ -229,8 +229,16 @@ export default class PostViewController extends React.Component {
onPostListScroll(atBottom) {
if (atBottom) {
+ let lastViewedBottom;
const lastPost = PostStore.getLatestPost(this.state.channel.id);
- this.setState({scrollType: ScrollTypes.BOTTOM, lastViewedBottom: lastPost.create_at || new Date().getTime()});
+
+ if (lastPost && lastPost.create_at) {
+ lastViewedBottom = lastPost.create_at;
+ } else {
+ lastViewedBottom = new Date().getTime();
+ }
+
+ this.setState({scrollType: ScrollTypes.BOTTOM, lastViewedBottom});
} else {
this.setState({scrollType: ScrollTypes.FREE});
}
diff --git a/webapp/components/profile_popover.jsx b/webapp/components/profile_popover.jsx
index 7cb2f7261..22cf60004 100644
--- a/webapp/components/profile_popover.jsx
+++ b/webapp/components/profile_popover.jsx
@@ -83,6 +83,9 @@ export default class ProfilePopover extends React.Component {
openDirectChannelToUser(
user,
(channel) => {
+ if (Utils.isMobile()) {
+ GlobalActions.emitCloseRightHandSide();
+ }
this.setState({loadingDMChannel: -1});
if (this.props.hide) {
this.props.hide();
@@ -185,34 +188,34 @@ export default class ProfilePopover extends React.Component {
const fullname = Utils.getFullName(this.props.user);
if (fullname) {
dataContent.push(
- <div
- data-toggle='tooltip'
- title={fullname}
- key='user-popover-fullname'
+ <OverlayTrigger
+ delayShow={Constants.WEBRTC_TIME_DELAY}
+ placement='top'
+ overlay={<Tooltip id='fullNameTooltip'>{fullname}</Tooltip>}
>
- <p
- className='text-nowrap'
+ <div
+ className='overflow--ellipsis text-nowrap padding-bottom'
>
{fullname}
- </p>
- </div>
+ </div>
+ </OverlayTrigger>
);
}
if (this.props.user.position) {
const position = this.props.user.position.substring(0, Constants.MAX_POSITION_LENGTH);
dataContent.push(
- <div
- data-toggle='tooltip'
- title={position}
- key='user-popover-position'
+ <OverlayTrigger
+ delayShow={Constants.WEBRTC_TIME_DELAY}
+ placement='top'
+ overlay={<Tooltip id='positionTooltip'>{position}</Tooltip>}
>
- <p
- className='text-nowrap'
+ <div
+ className='overflow--ellipsis text-nowrap padding-bottom'
>
{position}
- </p>
- </div>
+ </div>
+ </OverlayTrigger>
);
}
diff --git a/webapp/components/rhs_comment.jsx b/webapp/components/rhs_comment.jsx
index 8b7642fd8..26659c7a1 100644
--- a/webapp/components/rhs_comment.jsx
+++ b/webapp/components/rhs_comment.jsx
@@ -9,9 +9,6 @@ import ProfilePicture from 'components/profile_picture.jsx';
import ReactionListContainer from 'components/post_view/components/reaction_list_container.jsx';
import RhsDropdown from 'components/rhs_dropdown.jsx';
-import TeamStore from 'stores/team_store.jsx';
-import UserStore from 'stores/user_store.jsx';
-
import * as GlobalActions from 'actions/global_actions.jsx';
import {flagPost, unflagPost} from 'actions/post_actions.jsx';
@@ -19,6 +16,7 @@ import * as Utils from 'utils/utils.jsx';
import * as PostUtils from 'utils/post_utils.jsx';
import Constants from 'utils/constants.jsx';
+import DelayedAction from 'utils/delayed_action.jsx';
import {Tooltip, OverlayTrigger} from 'react-bootstrap';
import {FormattedMessage} from 'react-intl';
@@ -36,6 +34,10 @@ export default class RhsComment extends React.Component {
this.flagPost = this.flagPost.bind(this);
this.unflagPost = this.unflagPost.bind(this);
+ this.canEdit = false;
+ this.canDelete = false;
+ this.editDisableAction = new DelayedAction(this.handleEditDisable);
+
this.state = {};
}
@@ -44,6 +46,10 @@ export default class RhsComment extends React.Component {
GlobalActions.showGetPostLinkModal(this.props.post);
}
+ handleEditDisable() {
+ this.canEdit = false;
+ }
+
removePost() {
GlobalActions.emitRemovePost(this.props.post);
}
@@ -110,8 +116,8 @@ export default class RhsComment extends React.Component {
return '';
}
- const isOwner = this.props.currentUser.id === post.user_id;
- var isAdmin = TeamStore.isTeamAdminForCurrentTeam() || UserStore.isSystemAdminForCurrentUser();
+ this.canDelete = PostUtils.canDeletePost(post);
+ this.canEdit = PostUtils.canEditPost(post, this.editDisableAction);
var dropdownContents = [];
@@ -170,7 +176,7 @@ export default class RhsComment extends React.Component {
</li>
);
- if (isOwner || isAdmin) {
+ if (this.canDelete) {
dropdownContents.push(
<li
role='presentation'
@@ -193,11 +199,12 @@ export default class RhsComment extends React.Component {
);
}
- if (isOwner) {
+ if (this.canEdit) {
dropdownContents.push(
<li
role='presentation'
key='edit-button'
+ className={this.canEdit ? '' : 'hide'}
>
<a
href='#'
@@ -239,7 +246,7 @@ export default class RhsComment extends React.Component {
currentUserCss = 'current--user';
}
- var timestamp = this.props.currentUser.update_at;
+ var timestamp = this.props.currentUser.last_picture_update;
let status = this.props.status;
if (post.props && post.props.from_webhook === 'true') {
@@ -471,7 +478,10 @@ export default class RhsComment extends React.Component {
</li>
{botIndicator}
<li className='col'>
- <time className='post__time'>
+ <time
+ className='post__time'
+ dateTime={Utils.getDateForUnixTicks(post.create_at).toISOString()}
+ >
{Utils.getDateForUnixTicks(post.create_at).toLocaleString('en', timeOptions)}
</time>
{flagTrigger}
diff --git a/webapp/components/rhs_root_post.jsx b/webapp/components/rhs_root_post.jsx
index 95f5fc1ac..7d00e2322 100644
--- a/webapp/components/rhs_root_post.jsx
+++ b/webapp/components/rhs_root_post.jsx
@@ -11,7 +11,6 @@ import RhsDropdown from 'components/rhs_dropdown.jsx';
import ChannelStore from 'stores/channel_store.jsx';
import UserStore from 'stores/user_store.jsx';
-import TeamStore from 'stores/team_store.jsx';
import * as GlobalActions from 'actions/global_actions.jsx';
import {flagPost, unflagPost} from 'actions/post_actions.jsx';
@@ -20,6 +19,7 @@ import * as Utils from 'utils/utils.jsx';
import * as PostUtils from 'utils/post_utils.jsx';
import Constants from 'utils/constants.jsx';
+import DelayedAction from 'utils/delayed_action.jsx';
import {Tooltip, OverlayTrigger} from 'react-bootstrap';
import {FormattedMessage} from 'react-intl';
@@ -34,6 +34,10 @@ export default class RhsRootPost extends React.Component {
this.flagPost = this.flagPost.bind(this);
this.unflagPost = this.unflagPost.bind(this);
+ this.canEdit = false;
+ this.canDelete = false;
+ this.editDisableAction = new DelayedAction(this.handleEditDisable);
+
this.state = {};
}
@@ -42,6 +46,10 @@ export default class RhsRootPost extends React.Component {
GlobalActions.showGetPostLinkModal(this.props.post);
}
+ handleEditDisable() {
+ this.canEdit = false;
+ }
+
shouldComponentUpdate(nextProps) {
if (nextProps.status !== this.props.status) {
return true;
@@ -96,13 +104,13 @@ export default class RhsRootPost extends React.Component {
const post = this.props.post;
const user = this.props.user;
const mattermostLogo = Constants.MATTERMOST_ICON_SVG;
- var isOwner = this.props.currentUser.id === post.user_id;
- var isAdmin = TeamStore.isTeamAdminForCurrentTeam() || UserStore.isSystemAdminForCurrentUser();
- const isSystemMessage = post.type && post.type.startsWith(Constants.SYSTEM_MESSAGE_PREFIX);
- var timestamp = user ? user.update_at : 0;
+ var timestamp = user ? user.last_picture_update : 0;
var channel = ChannelStore.get(post.channel_id);
const flagIcon = Constants.FLAG_ICON_SVG;
+ this.canDelete = PostUtils.canDeletePost(post);
+ this.canEdit = PostUtils.canEditPost(post, this.editDisableAction);
+
var type = 'Post';
if (post.root_id.length > 0) {
type = 'Comment';
@@ -189,7 +197,7 @@ export default class RhsRootPost extends React.Component {
</li>
);
- if (isOwner || isAdmin) {
+ if (this.canDelete) {
dropdownContents.push(
<li
key='rhs-root-delete'
@@ -209,11 +217,12 @@ export default class RhsRootPost extends React.Component {
);
}
- if (isOwner && !isSystemMessage) {
+ if (this.canEdit) {
dropdownContents.push(
<li
key='rhs-root-edit'
role='presentation'
+ className={this.canEdit ? '' : 'hide'}
>
<a
href='#'
@@ -408,7 +417,10 @@ export default class RhsRootPost extends React.Component {
<li className='col__name'>{userProfile}</li>
{botIndicator}
<li className='col'>
- <time className='post__time'>
+ <time
+ className='post__time'
+ dateTime={Utils.getDateForUnixTicks(post.create_at).toISOString()}
+ >
{Utils.getDateForUnixTicks(post.create_at).toLocaleString('en', timeOptions)}
</time>
<OverlayTrigger
diff --git a/webapp/components/root.jsx b/webapp/components/root.jsx
index be50c7d48..465df5d79 100644
--- a/webapp/components/root.jsx
+++ b/webapp/components/root.jsx
@@ -8,11 +8,12 @@ import Client from 'client/web_client.jsx';
import {IntlProvider} from 'react-intl';
import React from 'react';
-
import FastClick from 'fastclick';
+import $ from 'jquery';
import {browserHistory} from 'react-router/es6';
import UserStore from 'stores/user_store.jsx';
+import BrowserStore from 'stores/browser_store.jsx';
export default class Root extends React.Component {
constructor(props) {
@@ -35,6 +36,30 @@ export default class Root extends React.Component {
}
/*eslint-enable */
+ // Force logout of all tabs if one tab is logged out
+ $(window).bind('storage', (e) => {
+ // when one tab on a browser logs out, it sets __logout__ in localStorage to trigger other tabs to log out
+ if (e.originalEvent.key === '__logout__' && e.originalEvent.storageArea === localStorage && e.originalEvent.newValue) {
+ // make sure it isn't this tab that is sending the logout signal (only necessary for IE11)
+ if (BrowserStore.isSignallingLogout(e.originalEvent.newValue)) {
+ return;
+ }
+
+ console.log('detected logout from a different tab'); //eslint-disable-line no-console
+ GlobalActions.emitUserLoggedOutEvent('/', false);
+ }
+
+ if (e.originalEvent.key === '__login__' && e.originalEvent.storageArea === localStorage && e.originalEvent.newValue) {
+ // make sure it isn't this tab that is sending the logout signal (only necessary for IE11)
+ if (BrowserStore.isSignallingLogin(e.originalEvent.newValue)) {
+ return;
+ }
+
+ console.log('detected login from a different tab'); //eslint-disable-line no-console
+ location.reload();
+ }
+ });
+
// Fastclick
FastClick.attach(document.body);
}
diff --git a/webapp/components/search_bar.jsx b/webapp/components/search_bar.jsx
index a7e9bfcac..c5fcd4697 100644
--- a/webapp/components/search_bar.jsx
+++ b/webapp/components/search_bar.jsx
@@ -2,9 +2,6 @@
// See License.txt for license information.
import $ from 'jquery';
-import ReactDOM from 'react-dom';
-import Client from 'client/web_client.jsx';
-import * as AsyncClient from 'utils/async_client.jsx';
import * as GlobalActions from 'actions/global_actions.jsx';
import SearchStore from 'stores/search_store.jsx';
import UserStore from 'stores/user_store.jsx';
@@ -15,7 +12,7 @@ import SearchSuggestionList from './suggestion/search_suggestion_list.jsx';
import SearchUserProvider from './suggestion/search_user_provider.jsx';
import * as Utils from 'utils/utils.jsx';
import Constants from 'utils/constants.jsx';
-import {loadProfilesForPosts, getFlaggedPosts} from 'actions/post_actions.jsx';
+import {getFlaggedPosts, performSearch} from 'actions/post_actions.jsx';
import {FormattedMessage, FormattedHTMLMessage} from 'react-intl';
@@ -119,26 +116,18 @@ export default class SearchBar extends React.Component {
if (terms.length) {
this.setState({isSearching: true});
- Client.search(
+ performSearch(
terms,
isMentionSearch,
- (data) => {
+ () => {
this.setState({isSearching: false});
- if (Utils.isMobile()) {
- ReactDOM.findDOMNode(this.refs.search).value = '';
- }
-
- AppDispatcher.handleServerAction({
- type: ActionTypes.RECEIVED_SEARCH,
- results: data,
- is_mention_search: isMentionSearch
- });
- loadProfilesForPosts(data.posts);
+ if (Utils.isMobile() && this.search) {
+ this.search.value = '';
+ }
},
- (err) => {
+ () => {
this.setState({isSearching: false});
- AsyncClient.dispatchError(err, 'search');
}
);
}
@@ -147,7 +136,7 @@ export default class SearchBar extends React.Component {
handleSubmit(e) {
e.preventDefault();
this.performSearch(this.state.searchTerm.trim());
- $(ReactDOM.findDOMNode(this.refs.search)).find('input').blur();
+ $(this.search).find('input').blur();
this.clearFocus();
}
@@ -276,7 +265,9 @@ export default class SearchBar extends React.Component {
>
<span className='fa fa-search sidebar__search-icon'/>
<SuggestionBox
- ref='search'
+ ref={(search) => {
+ this.search = search;
+ }}
className='form-control search-bar'
placeholder={Utils.localizeMessage('search_bar.search', 'Search')}
value={this.state.searchTerm}
diff --git a/webapp/components/search_results.jsx b/webapp/components/search_results.jsx
index a0245b7e4..86d1bac1d 100644
--- a/webapp/components/search_results.jsx
+++ b/webapp/components/search_results.jsx
@@ -124,6 +124,12 @@ export default class SearchResults extends React.Component {
window.removeEventListener('resize', this.handleResize);
}
+ componentDidUpdate(prevProps, prevState) {
+ if (this.state.searchTerm !== prevState.searchTerm) {
+ this.resize();
+ }
+ }
+
handleResize() {
this.setState({
windowWidth: Utils.windowWidth(),
diff --git a/webapp/components/search_results_item.jsx b/webapp/components/search_results_item.jsx
index 76681959e..be62653c0 100644
--- a/webapp/components/search_results_item.jsx
+++ b/webapp/components/search_results_item.jsx
@@ -62,7 +62,7 @@ export default class SearchResultsItem extends React.Component {
render() {
let channelName = null;
const channel = this.props.channel;
- const timestamp = UserStore.getCurrentUser().update_at;
+ const timestamp = UserStore.getCurrentUser().last_picture_update;
const user = this.props.user || {};
const post = this.props.post;
const flagIcon = Constants.FLAG_ICON_SVG;
@@ -285,7 +285,7 @@ export default class SearchResultsItem extends React.Component {
</li>
{rhsControls}
</ul>
- <div className='search-item-snippet'>
+ <div className='search-item-snippet post__body'>
{message}
</div>
</div>
diff --git a/webapp/components/setting_item_max.jsx b/webapp/components/setting_item_max.jsx
index 904e6c8d1..5971ce584 100644
--- a/webapp/components/setting_item_max.jsx
+++ b/webapp/components/setting_item_max.jsx
@@ -49,7 +49,7 @@ export default class SettingItemMax extends React.Component {
submit = (
<input
type='submit'
- className='btn btn-sm btn-primary'
+ className='btn btn-sm btn-primary pull-right'
href='#'
onClick={this.props.submit}
value={Utils.localizeMessage('setting_item_max.save', 'Save')}
@@ -88,7 +88,7 @@ export default class SettingItemMax extends React.Component {
{clientError}
{submit}
<a
- className='btn btn-sm theme'
+ className='btn btn-sm pull-right'
href='#'
onClick={this.props.updateSection}
>
diff --git a/webapp/components/setting_picture.jsx b/webapp/components/setting_picture.jsx
index b74ee8eb7..d1ff60c6a 100644
--- a/webapp/components/setting_picture.jsx
+++ b/webapp/components/setting_picture.jsx
@@ -73,7 +73,7 @@ export default class SettingPicture extends React.Component {
/>
);
} else {
- var confirmButtonClass = 'btn btn-sm';
+ var confirmButtonClass = 'btn btn-sm pull-right';
if (this.props.submitActive) {
confirmButtonClass += ' btn-primary';
} else {
@@ -132,7 +132,7 @@ export default class SettingPicture extends React.Component {
</span>
{confirmButton}
<a
- className='btn btn-sm theme'
+ className='btn btn-sm theme pull-right'
href='#'
onClick={self.props.updateSection}
>
diff --git a/webapp/components/should_verify_email.jsx b/webapp/components/should_verify_email.jsx
index 5ac67e383..61edf9422 100644
--- a/webapp/components/should_verify_email.jsx
+++ b/webapp/components/should_verify_email.jsx
@@ -2,11 +2,12 @@
// See License.txt for license information.
import {FormattedMessage} from 'react-intl';
-import Client from 'client/web_client.jsx';
import React from 'react';
import {Link} from 'react-router/es6';
+import {resendVerification} from 'actions/user_actions.jsx';
+
export default class ShouldVerifyEmail extends React.Component {
constructor(props) {
super(props);
@@ -22,7 +23,7 @@ export default class ShouldVerifyEmail extends React.Component {
this.setState({resendStatus: 'sending'});
- Client.resendVerification(
+ resendVerification(
email,
() => {
this.setState({resendStatus: 'success'});
diff --git a/webapp/components/sidebar_header.jsx b/webapp/components/sidebar_header.jsx
index a5fbd2659..9bc4a5639 100644
--- a/webapp/components/sidebar_header.jsx
+++ b/webapp/components/sidebar_header.jsx
@@ -10,7 +10,7 @@ import * as Utils from 'utils/utils.jsx';
import SidebarHeaderDropdown from './sidebar_header_dropdown.jsx';
import {Tooltip, OverlayTrigger} from 'react-bootstrap';
-import {Preferences, TutorialSteps} from 'utils/constants.jsx';
+import {Preferences, TutorialSteps, Constants} from 'utils/constants.jsx';
import {createMenuTip} from 'components/tutorial/tutorial_tip.jsx';
export default class SidebarHeader extends React.Component {
@@ -59,7 +59,7 @@ export default class SidebarHeader extends React.Component {
profilePicture = (
<img
className='user__picture'
- src={Client.getUsersRoute() + '/' + me.id + '/image?time=' + me.update_at}
+ src={Client.getUsersRoute() + '/' + me.id + '/image?time=' + me.last_picture_update}
/>
);
}
@@ -78,7 +78,7 @@ export default class SidebarHeader extends React.Component {
teamNameWithToolTip = (
<OverlayTrigger
trigger={['hover', 'focus']}
- delayShow={1000}
+ delayShow={Constants.OVERLAY_TIME_DELAY}
placement='bottom'
overlay={<Tooltip id='team-name__tooltip'>{this.props.teamDescription}</Tooltip>}
ref='descriptionOverlay'
@@ -91,16 +91,13 @@ export default class SidebarHeader extends React.Component {
return (
<div className='team__header theme'>
{tutorialTip}
- <a
- href='#'
- onClick={this.toggleDropdown}
- >
+ <div>
{profilePicture}
<div className='header__info'>
<div className='user__name'>{'@' + me.username}</div>
{teamNameWithToolTip}
</div>
- </a>
+ </div>
<SidebarHeaderDropdown
ref='dropdown'
teamType={this.props.teamType}
diff --git a/webapp/components/sidebar_header_dropdown.jsx b/webapp/components/sidebar_header_dropdown.jsx
index 826d9a342..86432e3ab 100644
--- a/webapp/components/sidebar_header_dropdown.jsx
+++ b/webapp/components/sidebar_header_dropdown.jsx
@@ -351,7 +351,10 @@ export default class SidebarHeaderDropdown extends React.Component {
if (moreTeams) {
teams.push(
<li key='joinTeam_li'>
- <Link to='/select_team'>
+ <Link
+ onClick={this.handleClick}
+ to='/select_team'
+ >
<FormattedMessage
id='navbar_dropdown.join'
defaultMessage='Join Another Team'
diff --git a/webapp/components/signup/components/signup_email.jsx b/webapp/components/signup/components/signup_email.jsx
index aa3493c96..9ed10b94c 100644
--- a/webapp/components/signup/components/signup_email.jsx
+++ b/webapp/components/signup/components/signup_email.jsx
@@ -5,11 +5,10 @@ import LoadingScreen from 'components/loading_screen.jsx';
import * as GlobalActions from 'actions/global_actions.jsx';
import {track} from 'actions/analytics_actions.jsx';
-
-import BrowserStore from 'stores/browser_store.jsx';
+import {getInviteInfo} from 'actions/team_actions.jsx';
+import {loginById, createUserWithInvite} from 'actions/user_actions.jsx';
import * as Utils from 'utils/utils.jsx';
-import Client from 'client/web_client.jsx';
import Constants from 'utils/constants.jsx';
import React from 'react';
@@ -58,7 +57,7 @@ export default class SignupEmail extends React.Component {
loading = false;
} else if (inviteId && inviteId.length > 0) {
loading = true;
- Client.getInviteInfo(
+ getInviteInfo(
inviteId,
(inviteData) => {
if (!inviteData) {
@@ -118,26 +117,12 @@ export default class SignupEmail extends React.Component {
handleSignupSuccess(user, data) {
track('signup', 'signup_user_02_complete');
- Client.loginById(
+ loginById(
data.id,
user.password,
'',
- () => {
- if (this.state.hash > 0) {
- BrowserStore.setGlobalItem(this.state.hash, JSON.stringify({usedBefore: true}));
- }
-
- GlobalActions.emitInitialLoad(
- () => {
- const query = this.props.location.query;
- if (query.redirect_to) {
- browserHistory.push(query.redirect_to);
- } else {
- GlobalActions.redirectUserToDefaultTeam();
- }
- }
- );
- },
+ this.state.hash,
+ null,
(err) => {
if (err.id === 'api.user.login.not_verified.app_error') {
browserHistory.push('/should_verify_email?email=' + encodeURIComponent(user.email) + '&teamname=' + encodeURIComponent(this.state.teamName));
@@ -241,7 +226,7 @@ export default class SignupEmail extends React.Component {
allow_marketing: true
};
- Client.createUserWithInvite(user,
+ createUserWithInvite(user,
this.state.data,
this.state.hash,
this.state.inviteId,
diff --git a/webapp/components/signup/components/signup_ldap.jsx b/webapp/components/signup/components/signup_ldap.jsx
index d80b27159..4c9afc8d6 100644
--- a/webapp/components/signup/components/signup_ldap.jsx
+++ b/webapp/components/signup/components/signup_ldap.jsx
@@ -5,9 +5,10 @@ import FormError from 'components/form_error.jsx';
import * as GlobalActions from 'actions/global_actions.jsx';
import {track} from 'actions/analytics_actions.jsx';
+import {addUserToTeamFromInvite} from 'actions/team_actions.jsx';
+import {webLoginByLdap} from 'actions/user_actions.jsx';
import * as Utils from 'utils/utils.jsx';
-import Client from 'client/web_client.jsx';
import React from 'react';
import {FormattedMessage, FormattedHTMLMessage} from 'react-intl';
@@ -55,7 +56,7 @@ export default class SignupLdap extends React.Component {
this.setState({ldapError: ''});
- Client.webLoginByLdap(
+ webLoginByLdap(
this.state.ldapId,
this.state.ldapPassword,
null,
@@ -69,11 +70,15 @@ export default class SignupLdap extends React.Component {
}
handleLdapSignupSuccess() {
- if (this.props.location.query.id || this.props.location.query.h) {
- Client.addUserToTeamFromInvite(
- this.props.location.query.d,
- this.props.location.query.h,
- this.props.location.query.id,
+ const hash = this.props.location.query.h;
+ const data = this.props.location.query.d;
+ const inviteId = this.props.location.query.id;
+
+ if (inviteId || hash) {
+ addUserToTeamFromInvite(
+ data,
+ hash,
+ inviteId,
() => {
this.finishSignup();
},
diff --git a/webapp/components/signup/signup_controller.jsx b/webapp/components/signup/signup_controller.jsx
index 9bf5936be..737431926 100644
--- a/webapp/components/signup/signup_controller.jsx
+++ b/webapp/components/signup/signup_controller.jsx
@@ -12,6 +12,7 @@ import BrowserStore from 'stores/browser_store.jsx';
import * as AsyncClient from 'utils/async_client.jsx';
import Client from 'client/web_client.jsx';
import * as GlobalActions from 'actions/global_actions.jsx';
+import {addUserToTeamFromInvite, getInviteInfo} from 'actions/team_actions.jsx';
import logoImage from 'images/logo.png';
import ErrorBar from 'components/error_bar.jsx';
@@ -68,7 +69,7 @@ export default class SignupController extends React.Component {
const userLoggedIn = UserStore.getCurrentUser() != null;
if ((inviteId || hash) && userLoggedIn) {
- Client.addUserToTeamFromInvite(
+ addUserToTeamFromInvite(
data,
hash,
inviteId,
@@ -79,11 +80,16 @@ export default class SignupController extends React.Component {
}
);
},
- (e) => {
+ () => {
this.setState({ // eslint-disable-line react/no-did-mount-set-state
noOpenServerError: true,
loading: false,
- serverError: e.message
+ serverError: (
+ <FormattedMessage
+ id='signup_user_completed.invalid_invite'
+ defaultMessage='The invite link was invalid. Please speak with your Administrator to receive an invitation.'
+ />
+ )
});
}
);
@@ -92,7 +98,7 @@ export default class SignupController extends React.Component {
}
if (inviteId) {
- Client.getInviteInfo(
+ getInviteInfo(
inviteId,
(inviteData) => {
if (!inviteData) {
diff --git a/webapp/components/suggestion/at_mention_provider.jsx b/webapp/components/suggestion/at_mention_provider.jsx
index 9263c6e50..5f79e08ae 100644
--- a/webapp/components/suggestion/at_mention_provider.jsx
+++ b/webapp/components/suggestion/at_mention_provider.jsx
@@ -6,6 +6,7 @@ import Provider from './provider.jsx';
import ChannelStore from 'stores/channel_store.jsx';
import UserStore from 'stores/user_store.jsx';
+import SuggestionStore from 'stores/suggestion_store.jsx';
import {autocompleteUsersInChannel} from 'actions/user_actions.jsx';
@@ -70,7 +71,7 @@ class AtMentionSuggestion extends Suggestion {
icon = (
<img
className='mention__image'
- src={Client.getUsersRoute() + '/' + user.id + '/image?time=' + user.update_at}
+ src={Client.getUsersRoute() + '/' + user.id + '/image?time=' + user.last_picture_update}
/>
);
}
@@ -161,6 +162,8 @@ export default class AtMentionProvider extends Provider {
});
}
);
+ } else {
+ SuggestionStore.clearSuggestions(suggestionId);
}
}
}
diff --git a/webapp/components/suggestion/channel_mention_provider.jsx b/webapp/components/suggestion/channel_mention_provider.jsx
index 63e6944ac..f1d6d9e76 100644
--- a/webapp/components/suggestion/channel_mention_provider.jsx
+++ b/webapp/components/suggestion/channel_mention_provider.jsx
@@ -51,61 +51,83 @@ class ChannelMentionSuggestion extends Suggestion {
}
export default class ChannelMentionProvider extends Provider {
+ constructor() {
+ super();
+
+ this.lastCompletedWord = '';
+ }
+
handlePretextChanged(suggestionId, pretext) {
- const captured = (/(^|\s)(~([^~]*))$/i).exec(pretext.toLowerCase());
- if (captured) {
- const prefix = captured[3];
+ const captured = (/(^|\s)(~([^~\r\n]*))$/i).exec(pretext.toLowerCase());
- this.startNewRequest(prefix);
+ if (!captured) {
+ // Not a channel mention
+ return;
+ }
- autocompleteChannels(
- prefix,
- (data) => {
- if (this.shouldCancelDispatch(prefix)) {
- return;
- }
+ if (this.lastCompletedWord && captured[0].startsWith(this.lastCompletedWord)) {
+ // It appears we're still matching a channel handle that we already completed
+ return;
+ }
+
+ // Clear the last completed word since we've started to match new text
+ this.lastCompletedWord = '';
+
+ const prefix = captured[3];
+
+ this.startNewRequest(prefix);
+
+ autocompleteChannels(
+ prefix,
+ (data) => {
+ if (this.shouldCancelDispatch(prefix)) {
+ return;
+ }
+
+ const channels = data;
- const channels = data;
-
- // Wrap channels in an outer object to avoid overwriting the 'type' property.
- const wrappedChannels = [];
- const wrappedMoreChannels = [];
- const moreChannels = [];
- channels.forEach((item) => {
- if (ChannelStore.get(item.id)) {
- wrappedChannels.push({
- type: Constants.MENTION_CHANNELS,
- channel: item
- });
- return;
- }
-
- wrappedMoreChannels.push({
- type: Constants.MENTION_MORE_CHANNELS,
+ // Wrap channels in an outer object to avoid overwriting the 'type' property.
+ const wrappedChannels = [];
+ const wrappedMoreChannels = [];
+ const moreChannels = [];
+ channels.forEach((item) => {
+ if (ChannelStore.get(item.id)) {
+ wrappedChannels.push({
+ type: Constants.MENTION_CHANNELS,
channel: item
});
+ return;
+ }
- moreChannels.push(item);
+ wrappedMoreChannels.push({
+ type: Constants.MENTION_MORE_CHANNELS,
+ channel: item
});
- const wrapped = wrappedChannels.concat(wrappedMoreChannels);
- const mentions = wrapped.map((item) => '~' + item.channel.name);
-
- AppDispatcher.handleServerAction({
- type: ActionTypes.RECEIVED_MORE_CHANNELS,
- channels: moreChannels
- });
+ moreChannels.push(item);
+ });
+
+ const wrapped = wrappedChannels.concat(wrappedMoreChannels);
+ const mentions = wrapped.map((item) => '~' + item.channel.name);
+
+ AppDispatcher.handleServerAction({
+ type: ActionTypes.RECEIVED_MORE_CHANNELS,
+ channels: moreChannels
+ });
+
+ AppDispatcher.handleServerAction({
+ type: ActionTypes.SUGGESTION_RECEIVED_SUGGESTIONS,
+ id: suggestionId,
+ matchedPretext: captured[2],
+ terms: mentions,
+ items: wrapped,
+ component: ChannelMentionSuggestion
+ });
+ }
+ );
+ }
- AppDispatcher.handleServerAction({
- type: ActionTypes.SUGGESTION_RECEIVED_SUGGESTIONS,
- id: suggestionId,
- matchedPretext: captured[2],
- terms: mentions,
- items: wrapped,
- component: ChannelMentionSuggestion
- });
- }
- );
- }
+ handleCompleteWord(term) {
+ this.lastCompletedWord = term;
}
}
diff --git a/webapp/components/suggestion/emoticon_provider.jsx b/webapp/components/suggestion/emoticon_provider.jsx
index 6bb0aee3b..6a4332e2f 100644
--- a/webapp/components/suggestion/emoticon_provider.jsx
+++ b/webapp/components/suggestion/emoticon_provider.jsx
@@ -14,7 +14,7 @@ const MIN_EMOTICON_LENGTH = 2;
class EmoticonSuggestion extends Suggestion {
render() {
const text = this.props.term;
- const emoticon = this.props.item;
+ const emoji = this.props.item.emoji;
let className = 'emoticon-suggestion';
if (this.props.isSelection) {
@@ -30,7 +30,7 @@ class EmoticonSuggestion extends Suggestion {
<img
alt={text}
className='emoticon-suggestion__image'
- src={EmojiStore.getEmojiImageUrl(emoticon)}
+ src={EmojiStore.getEmojiImageUrl(emoji)}
title={text}
/>
</div>
@@ -73,15 +73,24 @@ export default class EmoticonProvider {
// check for named emoji
for (const [name, emoji] of EmojiStore.getEmojis()) {
- if (name.indexOf(partialName) !== -1) {
- matched.push(emoji);
+ if (emoji.aliases) {
+ // This is a system emoji so it may have multiple names
+ for (const alias of emoji.aliases) {
+ if (alias.indexOf(partialName) !== -1) {
+ matched.push({name: alias, emoji});
+ break;
+ }
+ }
+ } else if (name.indexOf(partialName) !== -1) {
+ // This is a custom emoji so it only has one name
+ matched.push({name, emoji});
}
}
// sort the emoticons so that emoticons starting with the entered text come first
matched.sort((a, b) => {
- const aName = a.name || a.aliases[0];
- const bName = b.name || b.aliases[0];
+ const aName = a.name;
+ const bName = b.name;
const aPrefix = aName.startsWith(partialName);
const bPrefix = bName.startsWith(partialName);
@@ -95,7 +104,7 @@ export default class EmoticonProvider {
return 1;
});
- const terms = matched.map((emoticon) => ':' + (emoticon.name || emoticon.aliases[0]) + ':');
+ const terms = matched.map((item) => ':' + item.name + ':');
SuggestionStore.clearSuggestions(suggestionId);
if (terms.length > 0) {
diff --git a/webapp/components/suggestion/search_user_provider.jsx b/webapp/components/suggestion/search_user_provider.jsx
index bff59ace8..70808ca26 100644
--- a/webapp/components/suggestion/search_user_provider.jsx
+++ b/webapp/components/suggestion/search_user_provider.jsx
@@ -41,7 +41,7 @@ class SearchUserSuggestion extends Suggestion {
<i className='fa fa fa-plus-square'/>
<img
className='profile-img rounded'
- src={Client.getUsersRoute() + '/' + item.id + '/image?time=' + item.update_at}
+ src={Client.getUsersRoute() + '/' + item.id + '/image?time=' + item.last_picture_update}
/>
<div className='mention--align'>
<span>
diff --git a/webapp/components/suggestion/suggestion_box.jsx b/webapp/components/suggestion/suggestion_box.jsx
index e9f7c3699..29b9b2d8b 100644
--- a/webapp/components/suggestion/suggestion_box.jsx
+++ b/webapp/components/suggestion/suggestion_box.jsx
@@ -153,6 +153,12 @@ export default class SuggestionBox extends React.Component {
window.requestAnimationFrame(() => {
Utils.setCaretPosition(textbox, prefix.length + term.length + 1);
});
+
+ for (const provider of this.props.providers) {
+ if (provider.handleCompleteWord) {
+ provider.handleCompleteWord(term, matchedPretext);
+ }
+ }
}
handleKeyDown(e) {
diff --git a/webapp/components/suggestion/switch_channel_provider.jsx b/webapp/components/suggestion/switch_channel_provider.jsx
index 301974b9a..0bc30a79f 100644
--- a/webapp/components/suggestion/switch_channel_provider.jsx
+++ b/webapp/components/suggestion/switch_channel_provider.jsx
@@ -35,7 +35,7 @@ class SwitchChannelSuggestion extends Suggestion {
<div className='pull-left'>
<img
className='mention__image'
- src={Client.getUsersRoute() + '/' + item.id + '/image?time=' + item.update_at}
+ src={Client.getUsersRoute() + '/' + item.id + '/image?time=' + item.last_picture_update}
/>
</div>
);
diff --git a/webapp/components/team_general_tab.jsx b/webapp/components/team_general_tab.jsx
index 955a71ac5..0100cad64 100644
--- a/webapp/components/team_general_tab.jsx
+++ b/webapp/components/team_general_tab.jsx
@@ -8,60 +8,9 @@ import SettingItemMax from './setting_item_max.jsx';
import * as Utils from 'utils/utils.jsx';
import Constants from 'utils/constants.jsx';
-import {intlShape, injectIntl, defineMessages, FormattedMessage, FormattedHTMLMessage} from 'react-intl';
+import {FormattedMessage} from 'react-intl';
import {updateTeam} from 'actions/team_actions.jsx';
-const holders = defineMessages({
- dirDisabled: {
- id: 'general_tab.dirDisabled',
- defaultMessage: 'Team Directory has been disabled. Please ask a System Admin to enable the Team Directory in the System Console team settings.'
- },
- required: {
- id: 'general_tab.required',
- defaultMessage: 'This field is required'
- },
- chooseName: {
- id: 'general_tab.chooseName',
- defaultMessage: 'Please choose a new name for your team'
- },
- includeDirTitle: {
- id: 'general_tab.includeDirTitle',
- defaultMessage: 'Include this team in the Team Directory'
- },
- yes: {
- id: 'general_tab.yes',
- defaultMessage: 'Yes'
- },
- no: {
- id: 'general_tab.no',
- defaultMessage: 'No'
- },
- dirOff: {
- id: 'general_tab.dirOff',
- defaultMessage: 'Team directory is turned off for this system.'
- },
- openInviteTitle: {
- id: 'general_tab.openInviteTitle',
- defaultMessage: 'Allow any user with an account on this server to join this team'
- },
- codeTitle: {
- id: 'general_tab.codeTitle',
- defaultMessage: 'Invite Code'
- },
- codeDesc: {
- id: 'general_tab.codeDesc',
- defaultMessage: "Click 'Edit' to regenerate Invite Code."
- },
- teamNameInfo: {
- id: 'general_tab.teamNameInfo',
- defaultMessage: 'Set the name of the team as it appears on your sign-in screen and at the top of the left-hand sidebar.'
- },
- teamDescriptionInfo: {
- id: 'general_tab.teamDescriptionInfo',
- defaultMessage: 'Team description provides additional information to help users select the right team. Maximum of 50 characters.'
- }
-});
-
import React from 'react';
class GeneralTab extends React.Component {
@@ -156,13 +105,12 @@ class GeneralTab extends React.Component {
var state = {serverError: '', clientError: ''};
let valid = true;
- const {formatMessage} = this.props.intl;
const name = this.state.name.trim();
if (!name) {
- state.clientError = formatMessage(holders.required);
+ state.clientError = Utils.localizeMessage('general_tab.required', 'This field is required');
valid = false;
} else if (name === this.props.team.display_name) {
- state.clientError = formatMessage(holders.chooseName);
+ state.clientError = Utils.localizeMessage('general_tab.chooseName', 'Please choose a new name for your team');
valid = false;
} else {
state.clientError = '';
@@ -197,7 +145,7 @@ class GeneralTab extends React.Component {
if (inviteId) {
state.clientError = '';
} else {
- state.clientError = this.props.intl.fromatMessage(holders.required);
+ state.clientError = Utils.localizeMessage('general_tab.required', 'This field is required');
valid = false;
}
@@ -230,10 +178,9 @@ class GeneralTab extends React.Component {
var state = {serverError: '', clientError: ''};
let valid = true;
- const {formatMessage} = this.props.intl;
const description = this.state.description.trim();
if (description === this.props.team.description) {
- state.clientError = formatMessage(holders.chooseName);
+ state.clientError = Utils.localizeMessage('general_tab.chooseDescription', 'Please choose a new description for your team');
valid = false;
} else {
state.clientError = '';
@@ -324,8 +271,6 @@ class GeneralTab extends React.Component {
serverError = this.state.serverError;
}
- const {formatMessage} = this.props.intl;
-
let openInviteSection;
if (this.props.activeSection === 'open_invite') {
const inputs = [
@@ -372,7 +317,7 @@ class GeneralTab extends React.Component {
openInviteSection = (
<SettingItemMax
- title={formatMessage(holders.openInviteTitle)}
+ title={Utils.localizeMessage('general_tab.openInviteTitle', 'Allow any user with an account on this server to join this team')}
inputs={inputs}
submit={this.handleOpenInviteSubmit}
server_error={serverError}
@@ -382,14 +327,14 @@ class GeneralTab extends React.Component {
} else {
let describe = '';
if (this.state.allow_open_invite === true) {
- describe = formatMessage(holders.yes);
+ describe = Utils.localizeMessage('general_tab.yes', 'Yes');
} else {
- describe = formatMessage(holders.no);
+ describe = Utils.localizeMessage('general_tab.no', 'No');
}
openInviteSection = (
<SettingItemMin
- title={formatMessage(holders.openInviteTitle)}
+ title={Utils.localizeMessage('general_tab.openInviteTitle', 'Allow any user with an account on this server to join this team')}
describe={describe}
updateSection={this.onUpdateOpenInviteSection}
/>
@@ -427,9 +372,19 @@ class GeneralTab extends React.Component {
</div>
</div>
<div className='setting-list__hint'>
- <FormattedHTMLMessage
+ <FormattedMessage
id='general_tab.codeLongDesc'
- defaultMessage='The Invite Code is used as part of the URL in the team invitation link created by <strong>Get Team Invite Link</strong> in the main menu. Regenerating creates a new team invitation link and invalidates the previous link.'
+ defaultMessage='The Invite Code is used as part of the URL in the team invitation link created by {getTeamInviteLink} in the main menu. Regenerating creates a new team invitation link and invalidates the previous link.'
+ values={{
+ getTeamInviteLink: (
+ <strong>
+ <FormattedMessage
+ id='general_tab.getTeamInviteLink'
+ defaultMessage='Get Team Invite Link'
+ />
+ </strong>
+ )
+ }}
/>
</div>
</div>
@@ -437,7 +392,7 @@ class GeneralTab extends React.Component {
inviteSection = (
<SettingItemMax
- title={formatMessage(holders.codeTitle)}
+ title={Utils.localizeMessage('general_tab.codeTitle', 'Invite Code')}
inputs={inputs}
submit={this.handleInviteIdSubmit}
server_error={serverError}
@@ -448,8 +403,8 @@ class GeneralTab extends React.Component {
} else {
inviteSection = (
<SettingItemMin
- title={formatMessage(holders.codeTitle)}
- describe={formatMessage(holders.codeDesc)}
+ title={Utils.localizeMessage('general_tab.codeTitle', 'Invite Code')}
+ describe={Utils.localizeMessage('general_tab.codeDesc', "Click 'Edit' to regenerate Invite Code.")}
updateSection={this.onUpdateInviteIdSection}
/>
);
@@ -488,11 +443,11 @@ class GeneralTab extends React.Component {
</div>
);
- const nameExtraInfo = <span>{formatMessage(holders.teamNameInfo)}</span>;
+ const nameExtraInfo = <span>{Utils.localizeMessage('general_tab.teamNameInfo', 'Set the name of the team as it appears on your sign-in screen and at the top of the left-hand sidebar.')}</span>;
nameSection = (
<SettingItemMax
- title={formatMessage({id: 'general_tab.teamName'})}
+ title={Utils.localizeMessage('general_tab.teamName', 'Team Name')}
inputs={inputs}
submit={this.handleNameSubmit}
server_error={serverError}
@@ -506,7 +461,7 @@ class GeneralTab extends React.Component {
nameSection = (
<SettingItemMin
- title={formatMessage({id: 'general_tab.teamName'})}
+ title={Utils.localizeMessage('general_tab.teamName', 'Team Name')}
describe={describe}
updateSection={this.onUpdateNameSection}
/>
@@ -546,11 +501,11 @@ class GeneralTab extends React.Component {
</div>
);
- const descriptionExtraInfo = <span>{formatMessage(holders.teamDescriptionInfo)}</span>;
+ const descriptionExtraInfo = <span>{Utils.localizeMessage('general_tab.teamDescriptionInfo', 'Team description provides additional information to help users select the right team. Maximum of 50 characters.')}</span>;
descriptionSection = (
<SettingItemMax
- title={formatMessage({id: 'general_tab.teamDescription'})}
+ title={Utils.localizeMessage('general_tab.teamDescription', 'Team Description')}
inputs={inputs}
submit={this.handleDescriptionSubmit}
server_error={serverError}
@@ -574,7 +529,7 @@ class GeneralTab extends React.Component {
descriptionSection = (
<SettingItemMin
- title={formatMessage({id: 'general_tab.teamDescription'})}
+ title={Utils.localizeMessage('general_tab.teamDescription', 'Team Description')}
describe={describemsg}
updateSection={this.onUpdateDescriptionSection}
/>
@@ -633,10 +588,9 @@ class GeneralTab extends React.Component {
}
GeneralTab.propTypes = {
- intl: intlShape.isRequired,
updateSection: React.PropTypes.func.isRequired,
team: React.PropTypes.object.isRequired,
activeSection: React.PropTypes.string.isRequired
};
-export default injectIntl(GeneralTab);
+export default GeneralTab;
diff --git a/webapp/components/user_list_row.jsx b/webapp/components/user_list_row.jsx
index ff381a30b..3a13ccb66 100644
--- a/webapp/components/user_list_row.jsx
+++ b/webapp/components/user_list_row.jsx
@@ -64,7 +64,7 @@ export default function UserListRow({user, extraInfo, actions, actionProps, acti
className='more-modal__row'
>
<ProfilePicture
- src={`${Client.getUsersRoute()}/${user.id}/image?time=${user.update_at}`}
+ src={`${Client.getUsersRoute()}/${user.id}/image?time=${user.last_picture_update}`}
status={status}
width='32'
height='32'
diff --git a/webapp/components/user_profile.jsx b/webapp/components/user_profile.jsx
index d0267c0d8..d9bd5c378 100644
--- a/webapp/components/user_profile.jsx
+++ b/webapp/components/user_profile.jsx
@@ -56,7 +56,7 @@ export default class UserProfile extends React.Component {
let profileImg = '';
if (this.props.user) {
name = Utils.displayUsername(this.props.user.id);
- profileImg = Client.getUsersRoute() + '/' + this.props.user.id + '/image?time=' + this.props.user.update_at;
+ profileImg = Client.getUsersRoute() + '/' + this.props.user.id + '/image?time=' + this.props.user.last_picture_update;
}
if (this.props.overwriteName) {
diff --git a/webapp/components/user_settings/user_settings_advanced.jsx b/webapp/components/user_settings/user_settings_advanced.jsx
index 70306d871..dc6f4ac0c 100644
--- a/webapp/components/user_settings/user_settings_advanced.jsx
+++ b/webapp/components/user_settings/user_settings_advanced.jsx
@@ -332,7 +332,7 @@ export default class AdvancedSettingsDisplay extends React.Component {
return (
<FormattedMessage
id='user.settings.advance.embed_preview'
- defaultMessage='Show experimental previews of link content, when available'
+ defaultMessage='For the first web link in a message, display a preview of website content below the message, if available'
/>
);
case 'WEBRTC_PREVIEW':
diff --git a/webapp/components/user_settings/user_settings_display.jsx b/webapp/components/user_settings/user_settings_display.jsx
index 9ffc4f721..f51128b6f 100644
--- a/webapp/components/user_settings/user_settings_display.jsx
+++ b/webapp/components/user_settings/user_settings_display.jsx
@@ -191,7 +191,7 @@ export default class UserSettingsDisplay extends React.Component {
<br/>
<FormattedMessage
id='user.settings.display.collapseDesc'
- defaultMessage='Expand links to show a preview of content, when available.'
+ defaultMessage='Set whether previews of image links show as expanded or collapsed by default. This setting can also be controlled using the slash commands /expand and /collapse.'
/>
</div>
</div>
@@ -202,7 +202,7 @@ export default class UserSettingsDisplay extends React.Component {
title={
<FormattedMessage
id='user.settings.display.collapseDisplay'
- defaultMessage='Link previews'
+ defaultMessage='Default appearance of image link previews'
/>
}
inputs={inputs}
@@ -218,14 +218,14 @@ export default class UserSettingsDisplay extends React.Component {
describe = (
<FormattedMessage
id='user.settings.display.collapseOn'
- defaultMessage='On'
+ defaultMessage='Expanded'
/>
);
} else {
describe = (
<FormattedMessage
id='user.settings.display.collapseOff'
- defaultMessage='Off'
+ defaultMessage='Collapsed'
/>
);
}
@@ -239,7 +239,7 @@ export default class UserSettingsDisplay extends React.Component {
title={
<FormattedMessage
id='user.settings.display.collapseDisplay'
- defaultMessage='Link previews'
+ defaultMessage='Default appearance of image link previews'
/>
}
describe={describe}
diff --git a/webapp/components/user_settings/user_settings_general.jsx b/webapp/components/user_settings/user_settings_general.jsx
index 06fe31a9e..d9551dccc 100644
--- a/webapp/components/user_settings/user_settings_general.jsx
+++ b/webapp/components/user_settings/user_settings_general.jsx
@@ -15,7 +15,7 @@ import * as AsyncClient from 'utils/async_client.jsx';
import * as Utils from 'utils/utils.jsx';
import {intlShape, injectIntl, defineMessages, FormattedMessage, FormattedHTMLMessage, FormattedDate} from 'react-intl';
-import {updateUser} from 'actions/user_actions.jsx';
+import {updateUser, uploadProfileImage} from 'actions/user_actions.jsx';
const holders = defineMessages({
usernameReserved: {
@@ -241,11 +241,11 @@ class UserSettingsGeneralTab extends React.Component {
this.setState({loadingPicture: true});
- Client.uploadProfileImage(picture,
+ uploadProfileImage(
+ picture,
() => {
this.updateSection('');
this.submitActive = false;
- AsyncClient.getMe();
},
(err) => {
var state = this.setupInitialState(this.props);
diff --git a/webapp/components/user_settings/user_settings_notifications.jsx b/webapp/components/user_settings/user_settings_notifications.jsx
index 2ee33c092..672f8d6b7 100644
--- a/webapp/components/user_settings/user_settings_notifications.jsx
+++ b/webapp/components/user_settings/user_settings_notifications.jsx
@@ -8,10 +8,9 @@ import DesktopNotificationSettings from './desktop_notification_settings.jsx';
import UserStore from 'stores/user_store.jsx';
-import Client from 'client/web_client.jsx';
-import * as AsyncClient from 'utils/async_client.jsx';
import * as Utils from 'utils/utils.jsx';
import Constants from 'utils/constants.jsx';
+import {updateUserNotifyProps} from 'actions/user_actions.jsx';
import EmailNotificationSetting from './email_notification_setting.jsx';
import {FormattedMessage} from 'react-intl';
@@ -143,10 +142,10 @@ export default class NotificationsTab extends React.Component {
data.first_name = this.state.firstNameKey.toString();
data.channel = this.state.channelKey.toString();
- Client.updateUserNotifyProps(data,
+ updateUserNotifyProps(
+ data,
() => {
this.props.updateSection('');
- AsyncClient.getMe();
$('.settings-modal .modal-body').scrollTop(0).perfectScrollbar('update');
},
(err) => {
diff --git a/webapp/components/user_settings/user_settings_security.jsx b/webapp/components/user_settings/user_settings_security.jsx
index 3484b8183..210e455b7 100644
--- a/webapp/components/user_settings/user_settings_security.jsx
+++ b/webapp/components/user_settings/user_settings_security.jsx
@@ -9,11 +9,12 @@ import ToggleModalButton from '../toggle_modal_button.jsx';
import PreferenceStore from 'stores/preference_store.jsx';
-import Client from 'client/web_client.jsx';
import * as AsyncClient from 'utils/async_client.jsx';
import * as Utils from 'utils/utils.jsx';
import Constants from 'utils/constants.jsx';
+import {updatePassword, getAuthorizedApps, deactivateMfa, deauthorizeOAuthApp} from 'actions/user_actions.jsx';
+
import $ from 'jquery';
import React from 'react';
import {FormattedMessage, FormattedTime, FormattedDate} from 'react-intl';
@@ -27,7 +28,7 @@ export default class SecurityTab extends React.Component {
this.submitPassword = this.submitPassword.bind(this);
this.setupMfa = this.setupMfa.bind(this);
- this.deactivateMfa = this.deactivateMfa.bind(this);
+ this.removeMfa = this.removeMfa.bind(this);
this.updateCurrentPassword = this.updateCurrentPassword.bind(this);
this.updateNewPassword = this.updateNewPassword.bind(this);
this.updateConfirmPassword = this.updateConfirmPassword.bind(this);
@@ -53,7 +54,7 @@ export default class SecurityTab extends React.Component {
componentDidMount() {
if (global.mm_config.EnableOAuthServiceProvider === 'true') {
- Client.getAuthorizedApps(
+ getAuthorizedApps(
(authorizedApps) => {
this.setState({authorizedApps, serverError: null}); //eslint-disable-line react/no-did-mount-set-state
},
@@ -91,7 +92,7 @@ export default class SecurityTab extends React.Component {
return;
}
- Client.updatePassword(
+ updatePassword(
user.id,
currentPassword,
newPassword,
@@ -118,10 +119,8 @@ export default class SecurityTab extends React.Component {
browserHistory.push('/mfa/setup');
}
- deactivateMfa() {
- Client.updateMfa(
- '',
- false,
+ removeMfa() {
+ deactivateMfa(
() => {
if (global.window.mm_license.MFA === 'true' &&
global.window.mm_config.EnableMultifactorAuthentication === 'true' &&
@@ -131,7 +130,6 @@ export default class SecurityTab extends React.Component {
}
this.props.updateSection('');
- AsyncClient.getMe();
this.setState(this.getDefaultState());
},
(err) => {
@@ -161,7 +159,7 @@ export default class SecurityTab extends React.Component {
deauthorizeApp(e) {
e.preventDefault();
const appId = e.currentTarget.getAttribute('data-app');
- Client.deauthorizeOAuthApp(
+ deauthorizeOAuthApp(
appId,
() => {
const authorizedApps = this.state.authorizedApps.filter((app) => {
@@ -221,7 +219,7 @@ export default class SecurityTab extends React.Component {
<a
className='btn btn-primary'
href='#'
- onClick={this.deactivateMfa}
+ onClick={this.removeMfa}
>
{mfaButtonText}
</a>
@@ -425,6 +423,34 @@ export default class SecurityTab extends React.Component {
</div>
</div>
);
+ } else if (this.props.user.auth_service === Constants.GOOGLE_SERVICE) {
+ inputs.push(
+ <div
+ key='oauthEmailInfo'
+ className='form-group'
+ >
+ <div className='setting-list__hint'>
+ <FormattedMessage
+ id='user.settings.security.passwordGoogleCantUpdate'
+ defaultMessage='Login occurs through Google Apps. Password cannot be updated.'
+ />
+ </div>
+ </div>
+ );
+ } else if (this.props.user.auth_service === Constants.OFFICE365_SERVICE) {
+ inputs.push(
+ <div
+ key='oauthEmailInfo'
+ className='form-group'
+ >
+ <div className='setting-list__hint'>
+ <FormattedMessage
+ id='user.settings.security.passwordOffice365CantUpdate'
+ defaultMessage='Login occurs through Office 365. Password cannot be updated.'
+ />
+ </div>
+ </div>
+ );
}
updateSectionStatus = function resetSection(e) {
@@ -502,6 +528,20 @@ export default class SecurityTab extends React.Component {
defaultMessage='Login done through SAML'
/>
);
+ } else if (this.props.user.auth_service === Constants.GOOGLE_SERVICE) {
+ describe = (
+ <FormattedMessage
+ id='user.settings.security.loginGoogle'
+ defaultMessage='Login done through Google Apps'
+ />
+ );
+ } else if (this.props.user.auth_service === Constants.OFFICE365_SERVICE) {
+ describe = (
+ <FormattedMessage
+ id='user.settings.security.loginOffice365'
+ defaultMessage='Login done through Office 365'
+ />
+ );
}
updateSectionStatus = function updateSection() {
diff --git a/webapp/components/webrtc/components/webrtc_notification.jsx b/webapp/components/webrtc/components/webrtc_notification.jsx
index 5456d6cb8..f69e731f8 100644
--- a/webapp/components/webrtc/components/webrtc_notification.jsx
+++ b/webapp/components/webrtc/components/webrtc_notification.jsx
@@ -197,7 +197,7 @@ export default class WebrtcNotification extends React.Component {
const user = this.state.userCalling;
if (user) {
const username = Utils.displayUsername(user.id);
- const profileImgSrc = Client.getUsersRoute() + '/' + user.id + '/image?time=' + (user.update_at || new Date().getTime());
+ const profileImgSrc = Client.getUsersRoute() + '/' + user.id + '/image?time=' + (user.last_picture_update || new Date().getTime());
const profileImg = (
<img
className='user-popover__image'
diff --git a/webapp/components/webrtc/webrtc_controller.jsx b/webapp/components/webrtc/webrtc_controller.jsx
index 94e5b3475..b8d3d4db6 100644
--- a/webapp/components/webrtc/webrtc_controller.jsx
+++ b/webapp/components/webrtc/webrtc_controller.jsx
@@ -81,14 +81,14 @@ export default class WebrtcController extends React.Component {
const currentUser = UserStore.getCurrentUser();
const remoteUser = UserStore.getProfile(props.userId);
- const remoteUserImage = Client.getUsersRoute() + '/' + remoteUser.id + '/image?time=' + remoteUser.update_at;
+ const remoteUserImage = Client.getUsersRoute() + '/' + remoteUser.id + '/image?time=' + remoteUser.last_picture_update;
this.state = {
windowWidth: Utils.windowWidth(),
windowHeight: Utils.windowHeight(),
channelId: ChannelStore.getCurrentId(),
currentUser,
- currentUserImage: Client.getUsersRoute() + '/' + currentUser.id + '/image?time=' + currentUser.update_at,
+ currentUserImage: Client.getUsersRoute() + '/' + currentUser.id + '/image?time=' + currentUser.last_picture_update,
remoteUserImage,
localMediaLoaded: false,
isPaused: false,
@@ -130,7 +130,7 @@ export default class WebrtcController extends React.Component {
(nextProps.userId !== this.props.userId) ||
(nextProps.isCaller !== this.props.isCaller)) {
const remoteUser = UserStore.getProfile(nextProps.userId);
- const remoteUserImage = Client.getUsersRoute() + '/' + remoteUser.id + '/image?time=' + remoteUser.update_at;
+ const remoteUserImage = Client.getUsersRoute() + '/' + remoteUser.id + '/image?time=' + remoteUser.last_picture_update;
this.setState({
error: null,
remoteUserImage
@@ -644,7 +644,7 @@ export default class WebrtcController extends React.Component {
}
onConnectCall() {
- Client.webrtcToken(
+ WebrtcActions.webrtcToken(
(info) => {
const connectingMsg = (
<FormattedMessage
diff --git a/webapp/i18n/de.json b/webapp/i18n/de.json
index f75631b97..2a95eee5a 100644
--- a/webapp/i18n/de.json
+++ b/webapp/i18n/de.json
@@ -28,6 +28,7 @@
"activity_log.sessionsDescription": "Sitzungen werden erstellt, sobald Sie sich in einem neuen Browser eines Gerätes anmelden. Sitzungen ermöglichen es Ihnen Mattermost ohne erneutes Anmelden nach einer vom System Administrator definierten Zeit zu verwenden. Um sich früher abzumelden, verwenden Sie den 'Abmelden' Button unten, um die Sitzung zu beenden.",
"activity_log_modal.android": "Android",
"activity_log_modal.androidNativeApp": "Android App",
+ "activity_log_modal.desktop": "Native Desktop App",
"activity_log_modal.iphoneNativeApp": "iPhone App",
"add_command.autocomplete": "Auto-Vervollständigung",
"add_command.autocomplete.help": "(Optional) Zeige Slash-Befehle in Autovervollständigungsliste.",
@@ -260,6 +261,7 @@
"admin.email.notificationEmailTitle": "Absenderadresse:",
"admin.email.notificationOrganization": "Adresse in der Fußzeile von Benachrichtigungen:",
"admin.email.notificationOrganizationDescription": "Name und Adresse der Organisation bzw. des Unternehmens, wie sie in E-Mail-Benachrichtigungen von Mattermost angezeigt werden sollen, z.B. \"© Musterfirma GmbH, Musterstraße 23, 59424 Musterhausen, Deutschland\". Wenn dieses Feld leer bleibt, werden der Name und Adresse der Organisation nicht angezeigt.",
+ "admin.email.notificationOrganizationExample": "z.B. \"© Musterfirma GmbH, Musterstraße 23, 59424 Musterhausen, Deutschland\"",
"admin.email.notificationsDescription": "Normalerweise wahr in Produktionsumgebungen. Wenn wahr versucht Mattermost E-Mail Benachrichtigungen zu versenden. Entwickler sollten dies auf falsch für eine schnellere Entwicklung setzen.<br />Durch setzen auf wahr wird der Vorschau Modus Banner entfernt (benötigt aus- und einloggen nach Änderung).",
"admin.email.notificationsTitle": "Aktiviere E-Mail Benachrichtigungen: ",
"admin.email.passwordSaltDescription": "32 Zeichen langer Salt der zum Signieren von Passwort zurücksetzen E-Mails hinzugefügt wird. Zufallsgeneriert bei Installation. \"Neu generieren\" klicken um einen neuen Salt zu erstellen.",
@@ -308,10 +310,20 @@
"admin.general.localization.serverLocaleTitle": "Standardsprache Server:",
"admin.general.log": "Protokollierung",
"admin.general.policy": "Richtlinie",
+ "admin.general.policy.allowEditPostAlways": "Any time",
+ "admin.general.policy.allowEditPostDescription": "Set policy on the length of time authors have to edit their messages after posting.",
+ "admin.general.policy.allowEditPostNever": "Nie",
+ "admin.general.policy.allowEditPostTimeLimit": "seconds after posting",
+ "admin.general.policy.allowEditPostTitle": "Allow users to edit their messages:",
"admin.general.policy.permissionsAdmin": "Team- und Systemadministratoren",
"admin.general.policy.permissionsAll": "Alle Teammitglieder",
"admin.general.policy.permissionsAllChannel": "Alle Kanalmitglieder",
+ "admin.general.policy.permissionsDeletePostAdmin": "Team- und Systemadministratoren",
+ "admin.general.policy.permissionsDeletePostAll": "Message authors can delete their own messages, and Administrators can delete any message",
+ "admin.general.policy.permissionsDeletePostSystemAdmin": "Systemadministratoren",
"admin.general.policy.permissionsSystemAdmin": "Systemadministratoren",
+ "admin.general.policy.restrictPostDeleteDescription": "Set policy on who has permission to delete messages.",
+ "admin.general.policy.restrictPostDeleteTitle": "Allow which users to delete messages:",
"admin.general.policy.restrictPrivateChannelCreationDescription": "Regel festlegen, wer private Gruppen erstellen darf.",
"admin.general.policy.restrictPrivateChannelCreationTitle": "Erlaube Erstellung privater Gruppen für:",
"admin.general.policy.restrictPrivateChannelDeletionCommandLineToolLink": "Kommandozeilenwerkzeug",
@@ -363,6 +375,7 @@
"admin.image.amazonS3BucketExample": "z.B.: \"mattermost-media\"",
"admin.image.amazonS3BucketTitle": "Amazon S3 Bucket:",
"admin.image.amazonS3EndpointDescription": "Hostname Ihres S3 kompatiblen Speicheranbieters. Standardmäßig 's3.amazonaws.com'.",
+ "admin.image.amazonS3EndpointExample": "E.g.: \"s3.amazonaws.com\"",
"admin.image.amazonS3EndpointTitle": "Amazon S3 Endpunkt:",
"admin.image.amazonS3IdDescription": "Erhalten Sie diesen Wert von Ihrem Amazon EC2 Administrator.",
"admin.image.amazonS3IdExample": "z.B.: \"AKIADTOVBGERKLCBV\"",
@@ -513,7 +526,7 @@
"admin.metrics.enableDescription": "Wenn wahr, wird Mattermost Performance Daten sammeln und profilieren. Bitte schauen Sie in die <a href=\"http://docs.mattermost.com/deployment/metrics.html\" target='_blank'>Dokumentation</a> um mehr über die Konfiguration von Performanceüberwachung für Mattermost zu erfahren.",
"admin.metrics.enableTitle": "Performance Monitoring aktivieren:",
"admin.metrics.listenAddressDesc": "Die Adresse auf die der Server hören wird um die Performancemetriken auszugeben.",
- "admin.metrics.listenAddressEx": "z.B.: \":8067\"",
+ "admin.metrics.listenAddressEx": "z.B.: \":8065\"",
"admin.metrics.listenAddressTitle": "Empfangs-Adresse:",
"admin.mfa.bannerDesc": "Multi-Faktor-Authentifizierung ist nur verfügbar für Zugänge mit LDAP und E-Mail Logins. Wenn es Benutzer in Ihrem System mit anderen Loginmethoden gibt, wird es empfohlen das Sie Multi-Faktor-Authentifizierung direkt in SSO oder SAML implementieren.",
"admin.mfa.cluster": "Hoch",
@@ -597,7 +610,7 @@
"admin.saml.assertionConsumerServiceURLDesc": "Geben Sie https://<your-mattermost-url>/login/sso/saml ein. Stellen Sie sicher Sie verwenden HTTP oder HTTPS in Ihrer URL entsprechend Ihrer Serverkonfiguration. Dieses Feld ist auch bekannt als die \"Assertion Consumer Service URL\".",
"admin.saml.assertionConsumerServiceURLEx": "z.B.: \"https://<ihre-mattermost-url>/login/sso/saml\"",
"admin.saml.assertionConsumerServiceURLTitle": "Service Provider Login URL:",
- "admin.saml.bannerDesc": "User attributes in SAML server, including user deactivation or removal, are updated in Mattermost during user login. Learn more at: <a href=\"https://docs.mattermost.com/deployment/sso-saml.html\">https://docs.mattermost.com/deployment/sso-saml.html</a>",
+ "admin.saml.bannerDesc": "Benutzerattribute des SAML-Servers, inklusive Benutzerdeaktivierung oder -entfernung, werden in Mattermost bei der Benutzeranmeldung übernommen. Erfahren Sie mehr darüber in der <a href=\"https://docs.mattermost.com/deployment/sso-saml.html\">Dokumentation</a>.",
"admin.saml.emailAttrDesc": "Das Attribut des LDAP Servers, welches benutzt wird um die E-Mailadressen der Nutzer in Mattermost auszfüllen.",
"admin.saml.emailAttrEx": "z.B.: \"Email\" oder \"PrimaryEmail\"",
"admin.saml.emailAttrTitle": "E-Mail Attribut:",
@@ -834,7 +847,7 @@
"admin.team.maxChannelsExample": "z.B.: \"100\"",
"admin.team.maxChannelsTitle": "Maximal Kanäle pro Team:",
"admin.team.maxNotificationsPerChannelDescription": "Maximale Anzahl von Benutzern in einem Kanal bis Benutzer mit @all,@here und @channel keine Benachrichtigungen aufgrund von Performance auslösen können.",
- "admin.team.maxNotificationsPerChannelExample": "z.B.: \"1000\"",
+ "admin.team.maxNotificationsPerChannelExample": "z.B.: \"10000\"",
"admin.team.maxNotificationsPerChannelTitle": "Maximale Benachrichtigungen pro Kanal:",
"admin.team.maxUsersDescription": "Maximale Anzahl an Benutzern pro Team, inklusive Aktive und Inaktive Benutzer.",
"admin.team.maxUsersExample": "z.B.: \"25\"",
@@ -923,12 +936,14 @@
"analytics.chart.meaningful": "Nicht genügend Daten für eine aussagekräftige Darstellung.",
"analytics.system.activeUsers": "Aktive Nutzer mit Beiträgen",
"analytics.system.channelTypes": "Kanal Typen",
+ "analytics.system.dailyActiveUsers": "Daily Active Users",
"analytics.system.expiredBanner": "Die Enterprise Lizenz im am {date} abgelaufen. Sie haben 15 Tage vom genannten Datum an Zeit die Lizenz zu erneuern, bitte kontaktieren Sie <a href='mailto:commercial@mattermost.com'>commercial@mattermost.com</a>.",
"analytics.system.expiringBanner": "Die Enterprise Lizenz wird am {date} ablaufen. Um sie zu erneuern kontaktieren Sie bitte <a href='mailto:commercial@mattermost.com'>commercial@mattermost.com</a>.",
+ "analytics.system.monthlyActiveUsers": "Monthly Active Users",
"analytics.system.postTypes": "Nachrichten, Dateien und Hashtags",
"analytics.system.privateGroups": "Private Gruppen",
"analytics.system.publicChannels": "Öffentliche Kanäle",
- "analytics.system.skippedIntensiveQueries": "To maximize performance, some statistics are disabled. You can re-enable them in config.json. See: <a href='https://docs.mattermost.com/administration/statistics.html' target='_blank'>https://docs.mattermost.com/administration/statistics.html</a>",
+ "analytics.system.skippedIntensiveQueries": "Um die Performance zu maximieren sind einige Statistiken deaktiviert. Sie können diese in der config.json wieder reaktivieren. Sie erfahren mehr in der <a href='https://docs.mattermost.com/administration/statistics.html' target='_blank'>Dokumentation</a>.",
"analytics.system.textPosts": "Nur-Text Beiträge",
"analytics.system.title": "System Statistiken",
"analytics.system.totalChannels": "Kanäle Gesamt",
@@ -1022,7 +1037,6 @@
"backstage_sidebar.integrations.outgoing_webhooks": "Ausgehende Webhooks",
"calling_screen": "Rufe an",
"center_panel.recent": "Klicken Sie hier um zu vorherigen Mittelungen zurückzukehren. ",
- "chanel_header.addMembers": "Mitglieder hinzufügen",
"change_url.close": "Schließen",
"change_url.endWithLetter": "Muss mit einem Buchstaben oder einer Nummer enden",
"change_url.invalidUrl": "Ungültige URL",
@@ -1040,6 +1054,7 @@
"channel_flow.handleTooShort": "Kanal-URL muss zwei oder mehr alphanumerische Zeichen enthalten",
"channel_flow.invalidName": "Ungültiger Kanal Name",
"channel_flow.set_url_title": "Setze {term} URL",
+ "channel_header.addMembers": "Mitglieder hinzufügen",
"channel_header.addToFavorites": "Zu Favoriten hinzufügen",
"channel_header.channel": "Kanal",
"channel_header.channelHeader": "Bearbeite Kanaltitel",
@@ -1079,10 +1094,14 @@
"channel_loader.uploadedFile": " hat eine Datei hochgeladen",
"channel_loader.uploadedImage": " hat ein Bild hochgeladen",
"channel_loader.wrote": " schrieb: ",
+ "channel_members_dropdown.channel_admin": "Channel Admin",
+ "channel_members_dropdown.channel_member": "Kanalmitglieder",
+ "channel_members_dropdown.make_channel_admin": "Make Channel Admin",
+ "channel_members_dropdown.make_channel_member": "Make Channel Member",
+ "channel_members_dropdown.remove_from_channel": "Remove From Channel",
+ "channel_members_dropdown.remove_member": "Remove Member",
"channel_members_modal.addNew": " Füge neue Mitglieder hinzu",
- "channel_members_modal.close": "Schließen",
- "channel_members_modal.remove": "Entfernen",
- "channel_memebers_modal.members": " Mitglieder",
+ "channel_members_modal.members": " Mitglieder",
"channel_modal.cancel": "Abbrechen",
"channel_modal.channel": "Kanal",
"channel_modal.createNew": "Erstelle neue(n) ",
@@ -1091,6 +1110,7 @@
"channel_modal.edit": "Bearbeiten",
"channel_modal.group": "Gruppe",
"channel_modal.header": "Überschrift",
+ "channel_modal.headerEx": "E.g.: \"[Link Title](http://example.com)\"",
"channel_modal.headerHelp": "Der Text der in der Kopfzeile der/des {term} neben dem Namen steht. Zum Beispiel könnten Sie häufig genutzte Links durch Hinzufügen von [Link Titel](http://example.de) anzeigen lassen.",
"channel_modal.modalTitle": "Neue(r) ",
"channel_modal.name": "Name",
@@ -1101,6 +1121,7 @@
"channel_modal.publicChannel1": "Erstelle einen öffentlichen Kanal",
"channel_modal.publicChannel2": "Erstelle einen neuen öffentlichen Kanal dem jeder beitreten kann. ",
"channel_modal.purpose": "Zweck",
+ "channel_modal.purposeEx": "E.g.: \"A channel to file bugs and improvements\"",
"channel_notifications.allActivity": "Für alle Aktivitäten",
"channel_notifications.allUnread": "Für alle ungelesenen Nachrichten",
"channel_notifications.globalDefault": "Globaler Standard ({notifyLevel})",
@@ -1113,6 +1134,7 @@
"channel_notifications.unreadInfo": "Der Kanalname wird fettgedruckt dargestellt wenn es ungelesene Nachrichten gibt. Auswählen von \"Nur für Erwähnungen\" wird die Fettschreibung der Kanäle nur durchführen wenn Sie erwähnt werden.",
"channel_select.placeholder": "--- Wählen Sie einen Kanal ---",
"channel_switch_modal.dm": "(Direktnachricht)",
+ "channel_switch_modal.failed_to_open": "Failed to open channel.",
"channel_switch_modal.help": "Geben Sie den Kanalnamen ein. Verwenden Sie ↑↓ zum Browsen, TAB zum auswählen, ↵ zum Bestätigen, ESC zum verwerfen",
"channel_switch_modal.not_found": "Keine Treffer.",
"channel_switch_modal.submit": "Wechseln",
@@ -1285,15 +1307,14 @@
"find_team.submitError": "Bitte eine gültige E-Mail-Adresse eingeben",
"flag_post.flag": "zur Nachverfolgung markieren",
"flag_post.unflag": "Demarkieren",
+ "general_tab.chooseDescription": "Bitte wählen Sie einen neuen Namen für Ihr Team",
"general_tab.chooseName": "Bitte wählen Sie einen neuen Namen für Ihr Team",
"general_tab.codeDesc": "Auf 'Bearbeiten' klicken um den Einladungscode neu zu generieren.",
"general_tab.codeLongDesc": "Der Einladungscode ist Teil der URL im Team Einladungslink, welcher über <strong>Team Einladungslink erhalten</strong> im Hauptmenü generiert wird. Eine Neugenerierung erstellt einen neuen Team Einladungslink und macht den vorherigen Link ungültig.",
"general_tab.codeTitle": "Einladungscode",
- "general_tab.dirDisabled": "Teamverzeichnis wurde deaktiviert. Bitten Sie einen Systemadministrator das Teamverzeichnis in der Systemkonsole des Teams zu aktivieren.",
- "general_tab.dirOff": "Team Verzeichnis ist für dieses System ausgeschaltet.",
"general_tab.emptyDescription": "Klicken Sie auf 'Bearbeiten' um eine Teambeschreibung hinzuzufügen.",
+ "general_tab.getTeamInviteLink": "Team Einladungslink erhalten",
"general_tab.includeDirDesc": "Dieses Team mit aufzuführen wird den Team Namen im Teamverzeichnis auf der Startseite zeigen und einen Link zur Registrierungsseite bereitstellen.",
- "general_tab.includeDirTitle": "Team im Teamverzeichnis aufführen",
"general_tab.no": "Nein",
"general_tab.openInviteDesc": "Wenn erlaubt wird ein Link zu diesem Team auf der Startseite eingefügt und erlaubt jedem diesem Team beizutreten.",
"general_tab.openInviteTitle": "Erlaube jedem Benutzer mit einem Account auf diesem Server diesem Team beizutreten",
@@ -1395,10 +1416,10 @@
"help.link.formatting": "Nachrichten mit Markdown formatieren",
"help.link.mentioning": "Teammitglieder erwähnen",
"help.link.messaging": "Generelles zur Nachrichtenerstellung",
- "help.mentioning.channel": "#### @Channel\nDie können den kompletten Kanal erwähnen indem Sie `@channel` verwenden. Alle Mitglieder des Kanals erhalten so eine Benachrichtigung die genauso funktioniert als würden sie persönlich erwähnt worden sein.",
+ "help.mentioning.channel": "#### @Channel\nSie können alle Mitglieder eines Kanals erwähnen indem Sie `@channel` verwenden. Alle Mitglieder des Kanals erhalten eine Benachrichtigung als wären sie persönlich erwähnt worden.",
"help.mentioning.channelExample": "@channel Super Arbeit bei den Vorstellungsgesprächen. Ich glaube wir haben einige exzellente potentielle Kandidaten gefunden!",
"help.mentioning.mentions": "## @Erwähnungen\nVerwenden Sie @Erwähnungen um die Aufmerksamkeit eines spezifischen Teammitgliedes zu erhalten.",
- "help.mentioning.recent": "## Letzte Erwähnungen\nKlicken Sie auf das `@`neben dem Suchfeld um die letzten @Erwähnungen und Wörter, die eine Erwähnungen auslösen, aufzurufen. Klicken Sie auf **Sprung** neben dem Suchergebnis in der rechten Seitenleiste um im mittleren Feld an die Stelle der Mittteilung mit der Erwähnungen zu springen.",
+ "help.mentioning.recent": "## Letzte Erwähnungen\nKlicken Sie auf das `@`neben dem Suchfeld um die letzten @Erwähnungen und Wörter, die eine Erwähnungen auslösen, aufzurufen. Klicken Sie auf **Anzeigen** neben dem Suchergebnis in der rechten Seitenleiste um im mittleren Feld an die Stelle der Mittteilung mit der Erwähnungen zu springen.",
"help.mentioning.title": "# Teammitglieder erwähnen\n",
"help.mentioning.triggers": "## Wörter welche Erwähnungen auslösen\nZusätzlich zur Benachrichtigung durch @Benutzername und @channel können Sie eigene Wörter unter **Kontoeinstellungen** > **Benachrichtigungen** > **Wörter, welche Erwähnungen auslösen** definieren welche eine Erwähnungsbenachrichtigung auslösen. Standardmäßig erhalten Sie Erwähnungsmeldungen für Ihren Vornamen und Sie können weitere Wörter durch Eingabe in das Feld separiert durch Kommas hinzufügen. Dies ist hilfreich wenn Sie wegen bestimmter Themen benachrichtig werden wollen, wie zum Beispiel \"Vorstellungsgespräche\" oder \"Marketing\".",
"help.mentioning.username": "#### @Benutzername\nSie können einen Teammitglied Erwähnen indem Sie das `@` Symbol plus seinen Benutzernamen verwenden um ihm eine Benachrichtigung zu senden.\n\nTippen Sie `@` um eine Liste der Mitglieder aufzurufen welche erwähnt werden können. Um die Liste zu filtern tippen Sie die ersten Buchstaben des Benutzernamens, Vornamens, Nachnamens oder Spitznamens. Die **Hoch** und **Runter** Pfeiltasten können zum Scrollen durch die Liste verwendet werden und durch drücken von **Enter** wird die gewählte Erwähnungen übernommen. Sobald dieser ausgewählt wurde wird der volle Name oder Spitzname durch den Benutzernamen ersetzt.\nDas folgende Beispiel sendet eine Erwähnungsnachricht an **alice* welche Sie über den Kanal und die Mitteilung informiert in der sie erwähnt wurde. Wenn **alice** von Mattermost abwesend ist und [E-Mail Benachrichtigung](http://docs.mattermost.com/help/getting-started/configuring-notifications.html#email-notifications) aktiviert hat, erhält sie eine E-Mail mit der Mitteilung.",
@@ -1550,6 +1571,7 @@
"login.passwordChanged": " Passwort erfolgreich aktualisiert",
"login.session_expired": " Ihre Sitzung ist abgelaufen. Bitte melden Sie sich erneut an.",
"login.signIn": "Anmelden",
+ "login.signInLoading": "Signing in...",
"login.signInWith": "Anmelden mit:",
"login.userNotFound": "Ein existierender Zugang mit Ihrem Benutzernamen wurde in diesem Team nicht gefunden.",
"login.username": "Benutzername",
@@ -1561,8 +1583,10 @@
"member_item.makeAdmin": "Zum Admin machen",
"member_item.member": "Mitglied",
"member_list.noUsersAdd": "Keine Benutzer zum Hinzufügen.",
+ "members_popover.manageMembers": "Mitglieder verwalten",
"members_popover.msg": "Nachricht",
"members_popover.title": "Mitglieder",
+ "members_popover.viewMembers": "Zeige Mitglieder",
"mfa.confirm.complete": "<strong>Einrichtung abgeschlossen!</strong>",
"mfa.confirm.okay": "OK",
"mfa.confirm.secure": "Ihr Zugang ist nun abgesichert. Wenn Sie sich das nächste mal anmelden werden Sie gefragt den Code aus der Google Authenticator App von Ihrem Smartphone einzugeben.",
@@ -1665,6 +1689,7 @@
"post_info.mobile.unflag": "Demarkieren",
"post_info.permalink": "Dauerhafter Link",
"post_info.reply": "Antworten",
+ "post_message_view.edited": "(edited)",
"posts_view.loadMore": "Weitere Nachrichten laden",
"posts_view.newMsg": "Neue Nachrichten",
"posts_view.newMsgBelow": "{count} neue {count, plural, one {Mitteilung} other {Mitteilungen}} weiter unten",
@@ -1723,7 +1748,7 @@
"search_header.title2": "Letzte Erwähnungen",
"search_header.title3": "Markierte Nachrichten",
"search_item.direct": "Direktnachricht (mit {username})",
- "search_item.jump": "Sprung",
+ "search_item.jump": "Anzeigen",
"search_results.because": "<ul><li>Wenn Sie nach einem Textteil suchen(z.b. suche nach \"rea\", wenn Sie \"Realität\" oder \"Reaktion\" finden möchten), hängen Sie ein * an Ihren Suchbegriff</li><li>Wegen zu vieler Treffer werden Suchen mit nur zwei Buchstaben oder häufigen Wörtern wie \"this\", \"a\" und \"is\" im Suchergebnis nicht angezeigt.</li></ul>",
"search_results.noResults": "Keine Ergebnisse gefunden. Nochmal versuchen?",
"search_results.usage": "<ul><li>Verwenden Sie <b>\"Anführungszeichen\"</b> zur Suche nach Phrasen</li><li>Verwenden Sie <b>from:</b> um nach Nachrichten eines bestimmten Absenders zu suchen und <b>in:</b> für Nachrichten in einem bestimmten Kanal</li></ul>",
@@ -1899,7 +1924,7 @@
"update_command.question": "Ihre Änderungen könnten einen existierenden Slash-Befehl außer Kraft setzen. Sind Sie sich sicher dass Sie ihn aktualisieren möchten?",
"update_command.update": "Aktualisieren",
"upload_overlay.info": "Datei zum Hochladen hier ablegen.",
- "user.settings.advance.embed_preview": "Zeige experimentelle Vorschauen von Linkinhalten, sofern verfügbar",
+ "user.settings.advance.embed_preview": "For the first web link in a message, display a preview of website content below the message, if available",
"user.settings.advance.embed_toggle": "Zeige Umschalter für alle eingebetteten Vorschauen",
"user.settings.advance.enabledFeatures": "{count, number} {count, plural, one {Feature} other {Features}} aktiviert",
"user.settings.advance.formattingDesc": "Wenn aktiviert werden Nachrichten formatiert, sodass Links erstellt, Emojis angezeigt, Text formatiert und Zeilenumbrüche hinzugefügt werden. Standardmäßig ist dies aktiviert. Ändern der Einstellung erfordert ein Neuladen der Seite.",
@@ -1944,10 +1969,10 @@
"user.settings.display.channelDisplayTitle": "Kanalanzeige-Modus",
"user.settings.display.channeldisplaymode": "Wählen Sie die Breite den mittleren Kanals.",
"user.settings.display.clockDisplay": "Uhrzeit Format",
- "user.settings.display.collapseDesc": "Erweitere Links um eine Vorschau des Inhaltes zu sehen, sofern verfügbar.",
- "user.settings.display.collapseDisplay": "Link Vorschau",
- "user.settings.display.collapseOff": "Aus",
- "user.settings.display.collapseOn": "Ein",
+ "user.settings.display.collapseDesc": "Set whether previews of image links show as expanded or collapsed by default. This setting can also be controlled using the slash commands /expand and /collapse.",
+ "user.settings.display.collapseDisplay": "Default appearance of image link previews",
+ "user.settings.display.collapseOff": "Collapsed",
+ "user.settings.display.collapseOn": "Expanded",
"user.settings.display.fixedWidthCentered": "Feste Breite, zentriert",
"user.settings.display.fontDesc": "Wählen Sie die Schriftart zur Anzeige im Mattermost Interface aus.",
"user.settings.display.fontTitle": "Schriftart",
@@ -2130,7 +2155,9 @@
"user.settings.security.lastUpdated": "Zuletzt aktualisiert am {date} um {time}",
"user.settings.security.ldap": "AD/LDAP",
"user.settings.security.loginGitlab": "Anmeldung durchgeführt durch GitLab",
+ "user.settings.security.loginGoogle": "Login done through Google Apps",
"user.settings.security.loginLdap": "Anmeldung via AD/LDAP",
+ "user.settings.security.loginOffice365": "Login done through Office 365",
"user.settings.security.loginSaml": "Anmeldung durchgeführt durch SAML",
"user.settings.security.logoutActiveSessions": "Zeigen und beenden von aktiven Sitzungen",
"user.settings.security.method": "Anmeldemethode",
@@ -2159,9 +2186,11 @@
"user.settings.security.passwordErrorUppercaseNumberSymbol": "Ihr Passwort muss aus mindestens {min} Zeichen bestehen und mindestens einen Großbuchstaben, eine Ziffer und ein Symbol (wie \"~!@#$%^&*()\") enthalten.",
"user.settings.security.passwordErrorUppercaseSymbol": "Ihr Passwort muss aus mindestens {min} Zeichen bestehen und mindestens einen Großbuchstaben und ein Symbol (wie \"~!@#$%^&*()\") enthalten.",
"user.settings.security.passwordGitlabCantUpdate": "Einloggen findet über GitLab statt. Die Adresse kann nicht aktualisiert werden.",
+ "user.settings.security.passwordGoogleCantUpdate": "Einloggen findet über GitLab statt. Die Adresse kann nicht aktualisiert werden.",
"user.settings.security.passwordLdapCantUpdate": "Einloggen findet über AD/LDAP statt. Die Adresse kann nicht aktualisiert werden.",
"user.settings.security.passwordMatchError": "Die eingegebenen neuen Passwörter stimmen nicht überein.",
"user.settings.security.passwordMinLength": "Ungültige minimale Länge, kann keine Vorschau anzeigen.",
+ "user.settings.security.passwordOffice365CantUpdate": "Einloggen findet über GitLab statt. Die Adresse kann nicht aktualisiert werden.",
"user.settings.security.passwordSamlCantUpdate": "Dieses Feld wird durch Ihren Login Provider festgelegt. Wenn Sie es ändern möchten, müssen Sie dies durch den Login Provider tun.",
"user.settings.security.retypePassword": "Passwort erneut eingeben",
"user.settings.security.saml": "SAML",
diff --git a/webapp/i18n/en.json b/webapp/i18n/en.json
index c2ec7a4e9..6aa994bf3 100644
--- a/webapp/i18n/en.json
+++ b/webapp/i18n/en.json
@@ -28,6 +28,7 @@
"activity_log.sessionsDescription": "Sessions are created when you log in to a new browser on a device. Sessions let you use Mattermost without having to log in again for a time period specified by the System Admin. If you want to log out sooner, use the 'Logout' button below to end a session.",
"activity_log_modal.android": "Android",
"activity_log_modal.androidNativeApp": "Android Native App",
+ "activity_log_modal.desktop": "Native Desktop App",
"activity_log_modal.iphoneNativeApp": "iPhone Native App",
"add_command.autocomplete": "Autocomplete",
"add_command.autocomplete.help": "(Optional) Show slash command in autocomplete list.",
@@ -251,15 +252,16 @@
"admin.email.mhpnsHelp": "Download <a href=\"https://itunes.apple.com/us/app/mattermost/id984966508?mt=8\" target='_blank'>Mattermost iOS app</a> from iTunes. Download <a href=\"https://play.google.com/store/apps/details?id=com.mattermost.mattermost&hl=en\" target='_blank'>Mattermost Android app</a> from Google Play. Learn more about <a href=\"http://docs.mattermost.com/deployment/push.html#hosted-push-notifications-service-hpns\" target='_blank'>HPNS</a>.",
"admin.email.mtpns": "Use iOS and Android apps on iTunes and Google Play with TPNS",
"admin.email.mtpnsHelp": "Download <a href=\"https://itunes.apple.com/us/app/mattermost/id984966508?mt=8\" target='_blank'>Mattermost iOS app</a> from iTunes. Download <a href=\"https://play.google.com/store/apps/details?id=com.mattermost.mattermost&hl=en\" target='_blank'>Mattermost Android app</a> from Google Play. Learn more about <a href=\"http://docs.mattermost.com/deployment/push.html#test-push-notifications-service-tpns\" target='_blank'>TPNS</a>.",
- "admin.email.nofificationOrganizationExample": "Ex. \"© ABC Corporation, 565 Knight Way, Palo Alto, California, 94305, USA\"",
+ "admin.email.nofificationOrganizationExample": "E.g.: \"© ABC Corporation, 565 Knight Way, Palo Alto, California, 94305, USA\"",
"admin.email.notificationDisplayDescription": "Display name on email account used when sending notification emails from Mattermost.",
- "admin.email.notificationDisplayExample": "Ex: \"Mattermost Notification\", \"System\", \"No-Reply\"",
+ "admin.email.notificationDisplayExample": "E.g.: \"Mattermost Notification\", \"System\", \"No-Reply\"",
"admin.email.notificationDisplayTitle": "Notification Display Name:",
"admin.email.notificationEmailDescription": "Email address displayed on email account used when sending notification emails from Mattermost.",
- "admin.email.notificationEmailExample": "Ex: \"mattermost@yourcompany.com\", \"admin@yourcompany.com\"",
+ "admin.email.notificationEmailExample": "E.g.: \"mattermost@yourcompany.com\", \"admin@yourcompany.com\"",
"admin.email.notificationEmailTitle": "Notification From Address:",
"admin.email.notificationOrganization": "Notification Footer Mailing Address:",
"admin.email.notificationOrganizationDescription": "Organization name and address displayed on email notifications from Mattermost, such as \"© ABC Corporation, 565 Knight Way, Palo Alto, California, 94305, USA\". If the field is left empty, the organization name and address will not be displayed.",
+ "admin.email.notificationOrganizationExample": "E.g.: \"© ABC Corporation, 565 Knight Way, Palo Alto, California, 94305, USA\"",
"admin.email.notificationsDescription": "Typically set to true in production. When true, Mattermost attempts to send email notifications. Developers may set this field to false to skip email setup for faster development.<br />Setting this to true removes the Preview Mode banner (requires logging out and logging back in after setting is changed).",
"admin.email.notificationsTitle": "Enable Email Notifications: ",
"admin.email.passwordSaltDescription": "32-character salt added to signing of password reset emails. Randomly generated on install. Click \"Regenerate\" to create new salt.",
@@ -278,16 +280,16 @@
"admin.email.requireVerificationTitle": "Require Email Verification: ",
"admin.email.selfPush": "Manually enter Push Notification Service location",
"admin.email.smtpPasswordDescription": " Obtain this credential from administrator setting up your email server.",
- "admin.email.smtpPasswordExample": "Ex: \"yourpassword\", \"jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY\"",
+ "admin.email.smtpPasswordExample": "E.g.: \"yourpassword\", \"jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY\"",
"admin.email.smtpPasswordTitle": "SMTP Server Password:",
"admin.email.smtpPortDescription": "Port of SMTP email server.",
- "admin.email.smtpPortExample": "Ex: \"25\", \"465\", \"587\"",
+ "admin.email.smtpPortExample": "E.g.: \"25\", \"465\", \"587\"",
"admin.email.smtpPortTitle": "SMTP Server Port:",
"admin.email.smtpServerDescription": "Location of SMTP email server.",
- "admin.email.smtpServerExample": "Ex: \"smtp.yourcompany.com\", \"email-smtp.us-east-1.amazonaws.com\"",
+ "admin.email.smtpServerExample": "E.g.: \"smtp.yourcompany.com\", \"email-smtp.us-east-1.amazonaws.com\"",
"admin.email.smtpServerTitle": "SMTP Server:",
"admin.email.smtpUsernameDescription": " Obtain this credential from administrator setting up your email server.",
- "admin.email.smtpUsernameExample": "Ex: \"admin@yourcompany.com\", \"AKIADTOVBGERKLCBV\"",
+ "admin.email.smtpUsernameExample": "E.g.: \"admin@yourcompany.com\", \"AKIADTOVBGERKLCBV\"",
"admin.email.smtpUsernameTitle": "SMTP Server Username:",
"admin.email.testing": "Testing...",
"admin.false": "false",
@@ -308,10 +310,20 @@
"admin.general.localization.serverLocaleTitle": "Default Server Language:",
"admin.general.log": "Logging",
"admin.general.policy": "Policy",
+ "admin.general.policy.allowEditPostAlways": "Any time",
+ "admin.general.policy.allowEditPostDescription": "Set policy on the length of time authors have to edit their messages after posting.",
+ "admin.general.policy.allowEditPostNever": "Never",
+ "admin.general.policy.allowEditPostTimeLimit": "seconds after posting",
+ "admin.general.policy.allowEditPostTitle": "Allow users to edit their messages:",
"admin.general.policy.permissionsAdmin": "Team and System Admins",
"admin.general.policy.permissionsAll": "All team members",
"admin.general.policy.permissionsAllChannel": "All channel members",
+ "admin.general.policy.permissionsDeletePostAdmin": "Team Admins and System Admins",
+ "admin.general.policy.permissionsDeletePostAll": "Message authors can delete their own messages, and Administrators can delete any message",
+ "admin.general.policy.permissionsDeletePostSystemAdmin": "System Admins",
"admin.general.policy.permissionsSystemAdmin": "System Admins",
+ "admin.general.policy.restrictPostDeleteDescription": "Set policy on who has permission to delete messages.",
+ "admin.general.policy.restrictPostDeleteTitle": "Allow which users to delete messages:",
"admin.general.policy.restrictPrivateChannelCreationDescription": "Set policy on who can create private groups.",
"admin.general.policy.restrictPrivateChannelCreationTitle": "Enable private group creation for:",
"admin.general.policy.restrictPrivateChannelDeletionCommandLineToolLink": "command line tool",
@@ -363,6 +375,7 @@
"admin.image.amazonS3BucketExample": "E.g.: \"mattermost-media\"",
"admin.image.amazonS3BucketTitle": "Amazon S3 Bucket:",
"admin.image.amazonS3EndpointDescription": "Hostname of your S3 Compatible Storage provider. Defaults to `s3.amazonaws.com`.",
+ "admin.image.amazonS3EndpointExample": "E.g.: \"s3.amazonaws.com\"",
"admin.image.amazonS3EndpointTitle": "Amazon S3 Endpoint:",
"admin.image.amazonS3IdDescription": "Obtain this credential from your Amazon EC2 administrator.",
"admin.image.amazonS3IdExample": "E.g.: \"AKIADTOVBGERKLCBV\"",
@@ -467,7 +480,7 @@
"admin.ldap.testSuccess": "AD/LDAP Test Successful",
"admin.ldap.uernameAttrDesc": "The attribute in the AD/LDAP server that will be used to populate the username field in Mattermost. This may be the same as the ID Attribute.",
"admin.ldap.userFilterDisc": "(Optional) Enter an AD/LDAP Filter to use when searching for user objects. Only the users selected by the query will be able to access Mattermost. For Active Directory, the query to filter out disabled users is (&(objectCategory=Person)(!(UserAccountControl:1.2.840.113556.1.4.803:=2))).",
- "admin.ldap.userFilterEx": "Ex. \"(objectClass=user)\"",
+ "admin.ldap.userFilterEx": "E.g.: \"(objectClass=user)\"",
"admin.ldap.userFilterTitle": "User Filter:",
"admin.ldap.usernameAttrEx": "E.g.: \"sAMAccountName\"",
"admin.ldap.usernameAttrTitle": "Username Attribute:",
@@ -513,7 +526,7 @@
"admin.metrics.enableDescription": "When true, Mattermost will enable performance monitoring collection and profiling. Please see <a href=\"http://docs.mattermost.com/deployment/metrics.html\" target='_blank'>documentation</a> to learn more about configuring performance monitoring for Mattermost.",
"admin.metrics.enableTitle": "Enable Performance Monitoring:",
"admin.metrics.listenAddressDesc": "The address the server will listen on to expose performance metrics.",
- "admin.metrics.listenAddressEx": "Ex \":8067\"",
+ "admin.metrics.listenAddressEx": "E.g.: \":8067\"",
"admin.metrics.listenAddressTitle": "Listen Address:",
"admin.mfa.bannerDesc": "Multi-factor authentication is only available for accounts with LDAP and email login methods. If there are users on your system with other login methods, it is recommended you set up multi-factor authentication directly with the SSO or SAML provider.",
"admin.mfa.cluster": "High",
@@ -564,7 +577,7 @@
"admin.rate.enableLimiterTitle": "Enable Rate Limiting: ",
"admin.rate.httpHeaderDescription": "When filled in, vary rate limiting by HTTP header field specified (e.g. when configuring NGINX set to \"X-Real-IP\", when configuring AmazonELB set to \"X-Forwarded-For\").",
"admin.rate.httpHeaderExample": "E.g.: \"X-Real-IP\", \"X-Forwarded-For\"",
- "admin.rate.httpHeaderTitle": "Vary rate limit by HTTP header",
+ "admin.rate.httpHeaderTitle": "Vary rate limit by HTTP header:",
"admin.rate.maxBurst": "Maximum Burst Size:",
"admin.rate.maxBurstDescription": "Maximum number of requests allowed beyond the per second query limit.",
"admin.rate.maxBurstExample": "E.g.: \"100\"",
@@ -831,10 +844,10 @@
"admin.team.dirDesc": "When true, teams that are configured to show in team directory will show on main page inplace of creating a new team.",
"admin.team.dirTitle": "Enable Team Directory: ",
"admin.team.maxChannelsDescription": "Maximum total number of channels per team, including both active and deleted channels.",
- "admin.team.maxChannelsExample": "Ex \"100\"",
+ "admin.team.maxChannelsExample": "E.g.: \"100\"",
"admin.team.maxChannelsTitle": "Max Channels Per Team:",
"admin.team.maxNotificationsPerChannelDescription": "Maximum total number of users in a channel before users typing messages, @all, @here, and @channel no longer send notifications because of performance.",
- "admin.team.maxNotificationsPerChannelExample": "Ex \"1000\"",
+ "admin.team.maxNotificationsPerChannelExample": "E.g.: \"1000\"",
"admin.team.maxNotificationsPerChannelTitle": "Max Notifications Per Channel:",
"admin.team.maxUsersDescription": "Maximum total number of users per team, including both active and inactive users.",
"admin.team.maxUsersExample": "E.g.: \"25\"",
@@ -901,13 +914,13 @@
"admin.webrtc.gatewayWebsocketUrlTitle": "Gateway WebSocket URL:",
"admin.webrtc.stunUriDescription": "Enter your STUN URI as stun:<your-stun-url>:<port>. STUN is a standardized network protocol to allow an end host to assist devices to access its public IP address if it is located behind a NAT.",
"admin.webrtc.stunUriExample": "E.g.: \"stun:webrtc.mattermost.com:5349\"",
- "admin.webrtc.stunUriTitle": "STUN URI",
+ "admin.webrtc.stunUriTitle": "STUN URI:",
"admin.webrtc.turnSharedKeyDescription": "Enter your TURN Server Shared Key. This is used to created dynamic passwords to establish the connection. Each password is valid for a short period of time.",
"admin.webrtc.turnSharedKeyExample": "E.g.: \"bXdkOWQxc3d0Ynk3emY5ZmsxZ3NtazRjaWg=\"",
"admin.webrtc.turnSharedKeyTitle": "TURN Shared Key:",
"admin.webrtc.turnUriDescription": "Enter your TURN URI as turn:<your-turn-url>:<port>. TURN is a standardized network protocol to allow an end host to assist devices to establish a connection by using a relay public IP address if it is located behind a symmetric NAT.",
"admin.webrtc.turnUriExample": "E.g.: \"turn:webrtc.mattermost.com:5349\"",
- "admin.webrtc.turnUriTitle": "TURN URI",
+ "admin.webrtc.turnUriTitle": "TURN URI:",
"admin.webrtc.turnUsernameDescription": "Enter your TURN Server Username.",
"admin.webrtc.turnUsernameExample": "E.g.: \"myusername\"",
"admin.webrtc.turnUsernameTitle": "TURN Username:",
@@ -923,8 +936,10 @@
"analytics.chart.meaningful": "Not enough data for a meaningful representation.",
"analytics.system.activeUsers": "Active Users With Posts",
"analytics.system.channelTypes": "Channel Types",
+ "analytics.system.dailyActiveUsers": "Daily Active Users",
"analytics.system.expiredBanner": "The Enterprise license expired on {date}. You have 15 days from this date to renew the license, please contact <a href='mailto:commercial@mattermost.com'>commercial@mattermost.com</a>.",
"analytics.system.expiringBanner": "The Enterprise license is expiring on {date}. To renew your license, please contact <a href='mailto:commercial@mattermost.com'>commercial@mattermost.com</a>.",
+ "analytics.system.monthlyActiveUsers": "Monthly Active Users",
"analytics.system.postTypes": "Posts, Files and Hashtags",
"analytics.system.privateGroups": "Private Groups",
"analytics.system.publicChannels": "Public Channels",
@@ -1022,7 +1037,6 @@
"backstage_sidebar.integrations.outgoing_webhooks": "Outgoing Webhooks",
"calling_screen": "Calling",
"center_panel.recent": "Click here to jump to recent messages. ",
- "chanel_header.addMembers": "Add Members",
"change_url.close": "Close",
"change_url.endWithLetter": "Must end with a letter or number",
"change_url.invalidUrl": "Invalid URL",
@@ -1040,6 +1054,7 @@
"channel_flow.handleTooShort": "Channel URL must be 2 or more lowercase alphanumeric characters",
"channel_flow.invalidName": "Invalid Channel Name",
"channel_flow.set_url_title": "Set {term} URL",
+ "channel_header.addMembers": "Add Members",
"channel_header.addToFavorites": "Add to Favorites",
"channel_header.channel": "Channel",
"channel_header.channelHeader": "Edit Channel Header",
@@ -1079,10 +1094,14 @@
"channel_loader.uploadedFile": " uploaded a file",
"channel_loader.uploadedImage": " uploaded an image",
"channel_loader.wrote": " wrote: ",
+ "channel_members_dropdown.channel_admin": "Channel Admin",
+ "channel_members_dropdown.channel_member": "Channel Member",
+ "channel_members_dropdown.make_channel_admin": "Make Channel Admin",
+ "channel_members_dropdown.make_channel_member": "Make Channel Member",
+ "channel_members_dropdown.remove_from_channel": "Remove From Channel",
+ "channel_members_dropdown.remove_member": "Remove Member",
"channel_members_modal.addNew": " Add New Members",
- "channel_members_modal.close": "Close",
- "channel_members_modal.remove": "Remove",
- "channel_memebers_modal.members": " Members",
+ "channel_members_modal.members": " Members",
"channel_modal.cancel": "Cancel",
"channel_modal.channel": "Channel",
"channel_modal.createNew": "Create New ",
@@ -1091,6 +1110,7 @@
"channel_modal.edit": "Edit",
"channel_modal.group": "Group",
"channel_modal.header": "Header",
+ "channel_modal.headerEx": "E.g.: \"[Link Title](http://example.com)\"",
"channel_modal.headerHelp": "Set text that will appear in the header of the {term} beside the {term} name. For example, include frequently used links by typing [Link Title](http://example.com).",
"channel_modal.modalTitle": "New ",
"channel_modal.name": "Name",
@@ -1101,6 +1121,7 @@
"channel_modal.publicChannel1": "Create a public channel",
"channel_modal.publicChannel2": "Create a new public channel anyone can join. ",
"channel_modal.purpose": "Purpose",
+ "channel_modal.purposeEx": "E.g.: \"A channel to file bugs and improvements\"",
"channel_notifications.allActivity": "For all activity",
"channel_notifications.allUnread": "For all unread messages",
"channel_notifications.globalDefault": "Global default ({notifyLevel})",
@@ -1113,6 +1134,7 @@
"channel_notifications.unreadInfo": "The channel name is bolded in the sidebar when there are unread messages. Selecting \"Only for mentions\" will bold the channel only when you are mentioned.",
"channel_select.placeholder": "--- Select a channel ---",
"channel_switch_modal.dm": "(Direct Message)",
+ "channel_switch_modal.failed_to_open": "Failed to open channel.",
"channel_switch_modal.help": "Type channel name. Use ↑↓ to browse, TAB to select, ↵ to confirm, ESC to dismiss",
"channel_switch_modal.not_found": "No matches found.",
"channel_switch_modal.submit": "Switch",
@@ -1285,15 +1307,14 @@
"find_team.submitError": "Please enter a valid email address",
"flag_post.flag": "Flag for follow up",
"flag_post.unflag": "Unflag",
+ "general_tab.chooseDescription": "Please choose a new description for your team",
"general_tab.chooseName": "Please choose a new name for your team",
"general_tab.codeDesc": "Click 'Edit' to regenerate Invite Code.",
- "general_tab.codeLongDesc": "The Invite Code is used as part of the URL in the team invitation link created by <strong>Get Team Invite Link</strong> in the main menu. Regenerating creates a new team invitation link and invalidates the previous link.",
+ "general_tab.codeLongDesc": "The Invite Code is used as part of the URL in the team invitation link created by {getTeamInviteLink} in the main menu. Regenerating creates a new team invitation link and invalidates the previous link.",
"general_tab.codeTitle": "Invite Code",
- "general_tab.dirDisabled": "Team Directory has been disabled. Please ask a System Admin to enable the Team Directory in the System Console team settings.",
- "general_tab.dirOff": "Team directory is turned off for this system.",
"general_tab.emptyDescription": "Click 'Edit' to add a team description.",
+ "general_tab.getTeamInviteLink": "Get Team Invite Link",
"general_tab.includeDirDesc": "Including this team will display the team name from the Team Directory section of the Home Page, and provide a link to the sign-in page.",
- "general_tab.includeDirTitle": "Include this team in the Team Directory",
"general_tab.no": "No",
"general_tab.openInviteDesc": "When allowed, a link to this team will be included on the landing page allowing anyone with an account to join this team.",
"general_tab.openInviteTitle": "Allow any user with an account on this server to join this team",
@@ -1550,6 +1571,7 @@
"login.passwordChanged": " Password updated successfully",
"login.session_expired": " Your session has expired. Please login again.",
"login.signIn": "Sign in",
+ "login.signInLoading": "Signing in...",
"login.signInWith": "Sign in with:",
"login.userNotFound": "We couldn't find an account matching your login credentials.",
"login.username": "Username",
@@ -1561,8 +1583,10 @@
"member_item.makeAdmin": "Make Admin",
"member_item.member": "Member",
"member_list.noUsersAdd": "No users to add.",
+ "members_popover.manageMembers": "Manage Members",
"members_popover.msg": "Message",
"members_popover.title": "Members",
+ "members_popover.viewMembers": "View Members",
"mfa.confirm.complete": "<strong>Set up complete!</strong>",
"mfa.confirm.okay": "Okay",
"mfa.confirm.secure": "Your account is now secure. Next time you sign in, you will be asked to enter a code from the Google Authenticator app on your phone.",
@@ -1665,6 +1689,7 @@
"post_info.mobile.unflag": "Unflag",
"post_info.permalink": "Permalink",
"post_info.reply": "Reply",
+ "post_message_view.edited": "(edited)",
"posts_view.loadMore": "Load more messages",
"posts_view.newMsg": "New Messages",
"posts_view.newMsgBelow": "New {count, plural, one {message} other {messages}} below",
@@ -1899,7 +1924,7 @@
"update_command.question": "Your changes may break the existing slash command. Are you sure you would like to update it?",
"update_command.update": "Update",
"upload_overlay.info": "Drop a file to upload it.",
- "user.settings.advance.embed_preview": "Show experimental previews of link content, when available",
+ "user.settings.advance.embed_preview": "For the first web link in a message, display a preview of website content below the message, if available",
"user.settings.advance.embed_toggle": "Show toggle for all embed previews",
"user.settings.advance.enabledFeatures": "{count, number} {count, plural, one {Feature} other {Features}} Enabled",
"user.settings.advance.formattingDesc": "If enabled, posts will be formatted to create links, show emoji, style the text, and add line breaks. By default, this setting is enabled. Changing this setting requires the page to be refreshed.",
@@ -1944,10 +1969,10 @@
"user.settings.display.channelDisplayTitle": "Channel Display Mode",
"user.settings.display.channeldisplaymode": "Select the width of the center channel.",
"user.settings.display.clockDisplay": "Clock Display",
- "user.settings.display.collapseDesc": "Expand links to show a preview of content, when available.",
- "user.settings.display.collapseDisplay": "Link previews",
- "user.settings.display.collapseOff": "Off",
- "user.settings.display.collapseOn": "On",
+ "user.settings.display.collapseDesc": "Set whether previews of image links show as expanded or collapsed by default. This setting can also be controlled using the slash commands /expand and /collapse.",
+ "user.settings.display.collapseDisplay": "Default appearance of image link previews",
+ "user.settings.display.collapseOff": "Collapsed",
+ "user.settings.display.collapseOn": "Expanded",
"user.settings.display.fixedWidthCentered": "Fixed width, centered",
"user.settings.display.fontDesc": "Select the font displayed in the Mattermost user interface.",
"user.settings.display.fontTitle": "Display Font",
@@ -2130,7 +2155,9 @@
"user.settings.security.lastUpdated": "Last updated {date} at {time}",
"user.settings.security.ldap": "AD/LDAP",
"user.settings.security.loginGitlab": "Login done through GitLab",
+ "user.settings.security.loginGoogle": "Login done through Google Apps",
"user.settings.security.loginLdap": "Login done through AD/LDAP",
+ "user.settings.security.loginOffice365": "Login done through Office 365",
"user.settings.security.loginSaml": "Login done through SAML",
"user.settings.security.logoutActiveSessions": "View and Logout of Active Sessions",
"user.settings.security.method": "Sign-in Method",
@@ -2159,9 +2186,11 @@
"user.settings.security.passwordErrorUppercaseNumberSymbol": "Your password must contain at least {min} characters made up of at least one uppercase letter, at least one number, and at least one symbol (e.g. \"~!@#$%^&*()\").",
"user.settings.security.passwordErrorUppercaseSymbol": "Your password must contain at least {min} characters made up of at least one uppercase letter and at least one symbol (e.g. \"~!@#$%^&*()\").",
"user.settings.security.passwordGitlabCantUpdate": "Login occurs through GitLab. Password cannot be updated.",
+ "user.settings.security.passwordGoogleCantUpdate": "Login occurs through Google Apps. Password cannot be updated.",
"user.settings.security.passwordLdapCantUpdate": "Login occurs through AD/LDAP. Password cannot be updated.",
"user.settings.security.passwordMatchError": "The new passwords you entered do not match.",
"user.settings.security.passwordMinLength": "Invalid minimum length, cannot show preview.",
+ "user.settings.security.passwordOffice365CantUpdate": "Login occurs through Office 365. Password cannot be updated.",
"user.settings.security.passwordSamlCantUpdate": "This field is handled through your login provider. If you want to change it, you need to do so through your login provider.",
"user.settings.security.retypePassword": "Retype New Password",
"user.settings.security.saml": "SAML",
diff --git a/webapp/i18n/es.json b/webapp/i18n/es.json
index c9adb3c48..7b3e4fb76 100644
--- a/webapp/i18n/es.json
+++ b/webapp/i18n/es.json
@@ -28,6 +28,7 @@
"activity_log.sessionsDescription": "Las sesiones son creadas cuando inicias sesión desde un nuevo navegador en un dispositivo. Las Sesiones te permiten utilizar Mattermost sin tener que volver a iniciar sesión por un período de tiempo especificado por el Administrador de Sistema. Si deseas cerrar sesión antes de que se cumpla este tiempo, Utiliza el botón de 'Cerrar Sesión' en la parte de abajo.",
"activity_log_modal.android": "Android",
"activity_log_modal.androidNativeApp": "Android App Nativa",
+ "activity_log_modal.desktop": "App Nativa de Escritorio",
"activity_log_modal.iphoneNativeApp": "iPhone App Nativa",
"add_command.autocomplete": "Autocompletar",
"add_command.autocomplete.help": "(Opcional) Mostrar el comando de barra en la lista de autocompletado.",
@@ -232,7 +233,7 @@
"admin.email.allowEmailSignInTitle": "Habilitar el inicio de sesión con el correo electrónico: ",
"admin.email.allowSignupDescription": "Cuando está en verdadero, Mattermost permite la creación de equipos y cuentas utilizando el correo electrónico y contraseña. Este valor debe estar en falso sólo cuando quieres limitar el inicio de sesión a través de servicios tipo OAuth o AD/LDAP.",
"admin.email.allowSignupTitle": "Habilitar la creación de la cuenta con el correo electrónico: ",
- "admin.email.allowUsernameSignInDescription": "Cuando es verdadero, Mattermost permite a los usuarios iniciar sesión con el nombre de usuario y contraseña. Esta opción normalmente se utiliza cuando la verificación de correo electrónico está deshabilitada.",
+ "admin.email.allowUsernameSignInDescription": "Cuando es verdadero, Mattermost permite a los usuarios iniciar sesión con el nombre de usuario y contraseña. Esta opción normalmente se utiliza cuando la verificación de correo electrónico está inhabilitada.",
"admin.email.allowUsernameSignInTitle": "Habilitar el inicio de sesión con nombre de usuario: ",
"admin.email.connectionSecurityTest": "Probar Conexión",
"admin.email.easHelp": "Conoce más acerca de como compilar y desplegar tus propias aplicaciones moviles desde un <a href=\"http://docs.mattermost.com/deployment/push.html#enterprise-app-store-eas\" target='_blank'>App Store de Empresa</a>.",
@@ -260,6 +261,7 @@
"admin.email.notificationEmailTitle": "Dirección para Notificación de correo electrónico:",
"admin.email.notificationOrganization": "Dirección del pie de página",
"admin.email.notificationOrganizationDescription": "Nombre de la organización y dirección que se muestra en las notificaciones de correo electrónico enviadas desde Mattermost, como \"© ABC Corporation, 565 Knight Way, Palo Alto, California, 94305, USA\". Si este campo se deja en blanco, el nombre dela organización y su dirección no serán mostrados.",
+ "admin.email.notificationOrganizationExample": "Ej: \"© ABC Corporation, 565 Knight Way, Palo Alto, California, 94305, USA\"",
"admin.email.notificationsDescription": "Normalmente se asigna como verdadero en producción. Cuando es verdadero, Mattermost intenta enviar las notificaciones por correo electrónico. Los desarrolladores puede que quieran dejar esta opción en falso para saltarse la configuración de correos para desarrollar más rápido.<br/>Asignar está opción como verdadero remueve la notificación de Modo de Prueba (requiere cerrar la sesión y abrirla nuevamente para que los cambios surjan efecto).",
"admin.email.notificationsTitle": "Habilitar Notificaciones por Correo Electrónico: ",
"admin.email.passwordSaltDescription": "Un salt de 32-caracteres es añadido a la firma de correos para restablecer la contraseña. Aleatoriamente generado en la instalación. Haz clic en \"Regenerar\" para crear un nuevo salt.",
@@ -308,10 +310,20 @@
"admin.general.localization.serverLocaleTitle": "Idioma predeterminado para el Servidor:",
"admin.general.log": "Registro de actividad",
"admin.general.policy": "Política",
+ "admin.general.policy.allowEditPostAlways": "Cualquier momento",
+ "admin.general.policy.allowEditPostDescription": "Establecer la política sobre la cantidad de tiempo que los autores tienen para editar sus mensajes después de la publicación.",
+ "admin.general.policy.allowEditPostNever": "Nunca",
+ "admin.general.policy.allowEditPostTimeLimit": "segundos después de la publicación",
+ "admin.general.policy.allowEditPostTitle": "Permitir a los siguientes usuarios editar sus mensajes:",
"admin.general.policy.permissionsAdmin": "Administradores de Equipo y Sistema",
"admin.general.policy.permissionsAll": "Todos los miembros del equipo",
"admin.general.policy.permissionsAllChannel": "Todos los miembros del canal",
+ "admin.general.policy.permissionsDeletePostAdmin": "Administradores de Equipo y Sistema",
+ "admin.general.policy.permissionsDeletePostAll": "Los autores de los mensajes pueden borrar sus propios mensajes, y los Administradores pueden borrar cualquier mensaje",
+ "admin.general.policy.permissionsDeletePostSystemAdmin": "Administradores de Sistema",
"admin.general.policy.permissionsSystemAdmin": "Administradores de Sistema",
+ "admin.general.policy.restrictPostDeleteDescription": "Establecer la política sobre quién tiene permiso para eliminar los mensajes.",
+ "admin.general.policy.restrictPostDeleteTitle": "Permitir a los siguientes usuarios borrar los mensajes:",
"admin.general.policy.restrictPrivateChannelCreationDescription": "Establece la política de quién puede crear grupos privados.",
"admin.general.policy.restrictPrivateChannelCreationTitle": "Habilitar la creación de grupos privados a:",
"admin.general.policy.restrictPrivateChannelDeletionCommandLineToolLink": "herramienta de línea de comandos",
@@ -363,6 +375,7 @@
"admin.image.amazonS3BucketExample": "Ej.: \"mattermost-media\"",
"admin.image.amazonS3BucketTitle": "Amazon S3 Bucket:",
"admin.image.amazonS3EndpointDescription": "Nombre de host del proveedor de Almacenamiento compatible con S3. Por defecto `s3.amazonaws.com`.",
+ "admin.image.amazonS3EndpointExample": "Ej: \"s3.amazonaws.com\"",
"admin.image.amazonS3EndpointTitle": "Extremo de Amazon S3:",
"admin.image.amazonS3IdDescription": "Obetener esta credencial del administrador de tu Amazon EC2",
"admin.image.amazonS3IdExample": "Ej.: \"AKIADTOVBGERKLCBV\"",
@@ -458,7 +471,7 @@
"admin.ldap.skipCertificateVerification": "Omitir la Verificación del Certificado:",
"admin.ldap.skipCertificateVerificationDesc": "Omite la verificación del certificado para las conexiones TLS o STARTTLS. No recomendado para ambientes de producción donde TLS es requerido. Utilizalo sólamente para pruebas.",
"admin.ldap.syncFailure": "Error de Sincronización: {error}",
- "admin.ldap.syncIntervalHelpText": "La Sincronización AD/LDAP actualiza la información de usuarios en Mattermost para reflejar las actualizaciones en el servidor AD/LDAP. Por ejemplo, cuando un el nombre de usuario cambia en el servidor AD/LDAP, el cambio es reflejado en Mattermost cuando se realiza la sincronización. Las cuentas eliminadas o deshabilitadas en el servidor AD/LDAP tendrán sus cuentas en Mattermost como \"Inactiva\" y las sesiones revocadas. Mattermost realiza la sincronización en el intervalo de ingresado. Por ejemplo, si se introduce 60, Mattermost sincroniza cada 60 minutos.",
+ "admin.ldap.syncIntervalHelpText": "La Sincronización AD/LDAP actualiza la información de usuarios en Mattermost para reflejar las actualizaciones en el servidor AD/LDAP. Por ejemplo, cuando un el nombre de usuario cambia en el servidor AD/LDAP, el cambio es reflejado en Mattermost cuando se realiza la sincronización. Las cuentas eliminadas o inhabilitadas en el servidor AD/LDAP tendrán sus cuentas en Mattermost como \"Inactiva\" y las sesiones revocadas. Mattermost realiza la sincronización en el intervalo de ingresado. Por ejemplo, si se introduce 60, Mattermost sincroniza cada 60 minutos.",
"admin.ldap.syncIntervalTitle": "Intervalo de Sincronización (minutos):",
"admin.ldap.syncNowHelpText": "Inicia una sincronización AD/LDAP inmediatamente.",
"admin.ldap.sync_button": "Sincronizar AD/LDAP Ahora",
@@ -466,7 +479,7 @@
"admin.ldap.testHelpText": "Comprueba si el servidor Mattermost puede conectar con el servidor AD/LDAP especificado. Consulta el archivo de registro de mensajes de error para más detalles.",
"admin.ldap.testSuccess": "Prueba de AD/LDAP Satisfactoria",
"admin.ldap.uernameAttrDesc": "El atributo en el servidor AD/LDAP que se utilizará para poblar el nombre de usuario en Mattermost. Este puede ser igual al Atributo Id.",
- "admin.ldap.userFilterDisc": "(Opcional) Introduce un Filtro AD/LDAP a utilizar para buscar los objetos de usuario. Sólo los usuarios seleccionados por la consulta será capaz de acceder a Mattermost. Para Active Directory, la consulta para filtrar a los usuarios con deshabilitados es (&(objectCategory=Persona)(!(UserAccountControl:1.2.840.113556.1.4.803:=2))).",
+ "admin.ldap.userFilterDisc": "(Opcional) Introduce un Filtro AD/LDAP a utilizar para buscar los objetos de usuario. Sólo los usuarios seleccionados por la consulta será capaz de acceder a Mattermost. Para Active Directory, la consulta para filtrar a los usuarios con inhabilitados es (&(objectCategory=Persona)(!(UserAccountControl:1.2.840.113556.1.4.803:=2))).",
"admin.ldap.userFilterEx": "Ej: \"(objectClass=user)\"",
"admin.ldap.userFilterTitle": "Filtro de Usuario:",
"admin.ldap.usernameAttrEx": "Ej.: \"sAMAccountName\"",
@@ -513,7 +526,7 @@
"admin.metrics.enableDescription": "Cuando es verdadero, Mattermost habilitará el monitoreo de desempeño, la recolección y la elaboración de perfiles. Por favor, consulta la <a href=\"http://docs.mattermost.com/deployment/metrics.html\" target='_blank'>documentación</a> para obtener más información acerca de la configuración del monitoreo del rendimiento para Mattermost.",
"admin.metrics.enableTitle": "Habilitar el Monitoreo de Desempeño:",
"admin.metrics.listenAddressDesc": "La dirección del servidor que escuchará para mostrar las métricas de rendimiento.",
- "admin.metrics.listenAddressEx": "Ej \":8067\"",
+ "admin.metrics.listenAddressEx": "Ej: \":8067\"",
"admin.metrics.listenAddressTitle": "Dirección de escucha:",
"admin.mfa.bannerDesc": "La autenticación de múltiples factores sólo está disponible para los métodos de inicio de sesión con cuentas LDAP y de correo electrónico. Si hay usuarios en su sistema con otros métodos de inicio de sesión, se recomienda configurar la autenticación de múltiples factores directamente con el SSO o proveedor SAML.",
"admin.mfa.cluster": "Alta",
@@ -653,12 +666,12 @@
"admin.save": "Guardar",
"admin.saving": "Guardando...",
"admin.security.connection": "Conexiones",
- "admin.security.inviteSalt.disabled": "El salt para invitaciones no puede ser cambiado, mientras que el envío de correos electrónicos está deshabilitado.",
+ "admin.security.inviteSalt.disabled": "El salt para invitaciones no puede ser cambiado, mientras que el envío de correos electrónicos está inhabilitado.",
"admin.security.login": "Inicio de Sesión",
"admin.security.password": "Contraseña",
- "admin.security.passwordResetSalt.disabled": "El salt para restablecer contraseñas no puede ser cambiado, mientras que el envío de correos electrónicos está deshabilitado.",
+ "admin.security.passwordResetSalt.disabled": "El salt para restablecer contraseñas no puede ser cambiado, mientras que el envío de correos electrónicos está inhabilitado.",
"admin.security.public_links": "Enlaces Públicos",
- "admin.security.requireEmailVerification.disabled": "Verificación de correo electrónico no puede ser cambiado, mientras que el envío de correos electrónicos está deshabilitado.",
+ "admin.security.requireEmailVerification.disabled": "Verificación de correo electrónico no puede ser cambiado, mientras que el envío de correos electrónicos está inhabilitado.",
"admin.security.session": "Sesiones",
"admin.security.signup": "Registro",
"admin.select_team.close": "Cerrar",
@@ -669,7 +682,7 @@
"admin.service.attemptTitle": "Máximo de intentos de conexión:",
"admin.service.cmdsDesc": "Cuando es verdadero, los comandos de barra serán permitidos. Revisa la <a href='http://docs.mattermost.com/developer/slash-commands.html' target='_blank'>documentación</a> para obtener más información.",
"admin.service.cmdsTitle": "Habilitar Comandos de Barra Personalizados: ",
- "admin.service.corsDescription": "Habilitar solicitudes HTTP de origen cruzado desde un dominio específico. Utiliza \"*\" si quieres permitir CORS desde cualquier dominio o dejalo en blanco para deshabilitarlo.",
+ "admin.service.corsDescription": "Habilitar solicitudes HTTP de origen cruzado desde un dominio específico. Utiliza \"*\" si quieres permitir CORS desde cualquier dominio o déjalo en blanco para inhabilitarlo.",
"admin.service.corsEx": "http://ejemplo.com",
"admin.service.corsTitle": "Habilitar la procedencia de las solicitudes cruzadas de:",
"admin.service.developerDesc": "Cuando es verdadero, los errores de JavaScript se muestran en una barra roja en la parte superior de la interfaz de usuario. No se recomienda su uso en producción. ",
@@ -831,10 +844,10 @@
"admin.team.dirDesc": "Cuando es verdadero, Los equipos que esten configurados para mostrarse en el directorio de equipos se mostrarán en vez de crear un nuevo equipo.",
"admin.team.dirTitle": "Habilitar Directorio de Equipos: ",
"admin.team.maxChannelsDescription": "Máximo número total de canales por equipo, incluyendo canales activos y borrados.",
- "admin.team.maxChannelsExample": "Ej \"100\"",
+ "admin.team.maxChannelsExample": "Ej: \"100\"",
"admin.team.maxChannelsTitle": "Max Canales Por Equipo:",
"admin.team.maxNotificationsPerChannelDescription": "Cantidad máxima de usuarios en un canal para que las notificaciones de \"escribiendo un mensaje\", @all, @here y @channel no sean enviadas para mejorar el rendimiento.",
- "admin.team.maxNotificationsPerChannelExample": "Ej \"1000\"",
+ "admin.team.maxNotificationsPerChannelExample": "Ej: \"1000\"",
"admin.team.maxNotificationsPerChannelTitle": "Cantidad Max de Notificaciones por Canal:",
"admin.team.maxUsersDescription": "Número máximo de usuarios por equipo, incluyendo usuarios activos e inactivos.",
"admin.team.maxUsersExample": "Ej.: \"25\"",
@@ -860,7 +873,7 @@
"admin.team.uploadDesc": "Personalizar la experiencia de usuario mediante la adición de una imagen personalizada en la pantalla de inicio de sesión. Se Recomienda que el tamaño máximo de la imagen sea menos de 2 MB.",
"admin.team.uploaded": "Subida!",
"admin.team.uploading": "Subiendo..",
- "admin.team.userCreationDescription": "Cuando es falso, a posibilidad de crear cuentas es deshabilitada. El botón crear cuentas arrojará error cuando sea presionado.",
+ "admin.team.userCreationDescription": "Cuando es falso, a posibilidad de crear cuentas es inhabilitada. El botón crear cuentas arrojará error cuando sea presionado.",
"admin.team.userCreationTitle": "Permitir la Creación de Cuentas: ",
"admin.team_analytics.activeUsers": "Active Users With Posts",
"admin.team_analytics.totalPosts": "Total de Mensajes",
@@ -923,12 +936,14 @@
"analytics.chart.meaningful": "No hay suficiente data para tener una representación significativa.",
"analytics.system.activeUsers": "Usuarios Activos con Mensajes",
"analytics.system.channelTypes": "Tipos de Canales",
+ "analytics.system.dailyActiveUsers": "Usuarios Activos Diarios",
"analytics.system.expiredBanner": "La licencia Empresarial vence el {date}. Tienes 15 días desde esta notificación para renovar la licencia, por favor contacta a <a href='mailto:commercial@mattermost.com'>commercial@mattermost.com</a>.",
"analytics.system.expiringBanner": "La licencia Empresarial vence el {date}. Para renovar tu licencia, por favor contacta a <a href='mailto:commercial@mattermost.com'>commercial@mattermost.com</a>.",
+ "analytics.system.monthlyActiveUsers": "Usuarios Activos Mensuales",
"analytics.system.postTypes": "Mesajes, Archivos y Hashtags",
"analytics.system.privateGroups": "Grupos Privados",
"analytics.system.publicChannels": "Canales Públicos",
- "analytics.system.skippedIntensiveQueries": "Para maximizar el rendimiento, algunas estadísticas están deshabilitadas. Puedes volver a activarlas en el archivo config.json. Ver: <a href='https://docs.mattermost.com/administration/statistics.html' target='_blank'>https://docs.mattermost.com/administration/statistics.html</a>",
+ "analytics.system.skippedIntensiveQueries": "Para maximizar el rendimiento, algunas estadísticas están inhabilitadas. Puedes volver a activarlas en el archivo config.json. Ver: <a href='https://docs.mattermost.com/administration/statistics.html' target='_blank'>https://docs.mattermost.com/administration/statistics.html</a>",
"analytics.system.textPosts": "Mensajes de sólo Texto",
"analytics.system.title": "Estadísticas del Sistema",
"analytics.system.totalChannels": "Total de Canales",
@@ -1022,7 +1037,6 @@
"backstage_sidebar.integrations.outgoing_webhooks": "Webhooks de Salida",
"calling_screen": "Llamando",
"center_panel.recent": "Clic aquí para ir a los mensajes más recientes. ",
- "chanel_header.addMembers": "Agregar Miembros",
"change_url.close": "Cerrar",
"change_url.endWithLetter": "Debe terminar con una letra o número",
"change_url.invalidUrl": "URL Inválida",
@@ -1040,6 +1054,7 @@
"channel_flow.handleTooShort": "Dirección URL del canal debe ser de 2 o más caracteres alfanuméricos en minúsculas",
"channel_flow.invalidName": "Nombre de Canal Inválido",
"channel_flow.set_url_title": "Asignar URL de {term}",
+ "channel_header.addMembers": "Agregar Miembros",
"channel_header.addToFavorites": "Añadir a favoritos",
"channel_header.channel": "Canal",
"channel_header.channelHeader": "Editar encabezado del canal",
@@ -1079,10 +1094,14 @@
"channel_loader.uploadedFile": " subió un archivo",
"channel_loader.uploadedImage": " subió una imagen",
"channel_loader.wrote": " escribió: ",
+ "channel_members_dropdown.channel_admin": "Admin del Canal",
+ "channel_members_dropdown.channel_member": "Miembro del Canal",
+ "channel_members_dropdown.make_channel_admin": "Hacer Admin de Canal",
+ "channel_members_dropdown.make_channel_member": "Hacer Miembro del Canal",
+ "channel_members_dropdown.remove_from_channel": "Remover del Canal",
+ "channel_members_dropdown.remove_member": "Remover Miembro",
"channel_members_modal.addNew": " Agregar nuevos Miembros",
- "channel_members_modal.close": "Cerrar",
- "channel_members_modal.remove": "Eliminar",
- "channel_memebers_modal.members": " Miembros",
+ "channel_members_modal.members": " Miembros",
"channel_modal.cancel": "Cancelar",
"channel_modal.channel": "Canal",
"channel_modal.createNew": "Crear Nuevo ",
@@ -1091,6 +1110,7 @@
"channel_modal.edit": "Editar",
"channel_modal.group": "Grupo",
"channel_modal.header": "Encabezado",
+ "channel_modal.headerEx": "Ej: \"[Título del enlace](http://example.com)\"",
"channel_modal.headerHelp": "Establece el texto que aparecerá en el encabezado del {term} al lado del nombre del {term}. Por ejemplo, incluye enlaces que se usan con frecuencia escribiendo [Título del Enlace](http://example.com).",
"channel_modal.modalTitle": "Nuevo ",
"channel_modal.name": "Nombre",
@@ -1101,6 +1121,7 @@
"channel_modal.publicChannel1": "Crear un canal público",
"channel_modal.publicChannel2": "Crear un canal público al que cualquiera puede unirse. ",
"channel_modal.purpose": "Propósito",
+ "channel_modal.purposeEx": "Ej: \"Un canal para describir errores y mejoras\"",
"channel_notifications.allActivity": "Para toda actividad",
"channel_notifications.allUnread": "Para todos los mensajes sin leer",
"channel_notifications.globalDefault": "Predeterminada ({notifyLevel})",
@@ -1113,6 +1134,7 @@
"channel_notifications.unreadInfo": "El nombre del canal está en negritas en la barra lateral cuando hay mensajes sin leer. Al elegir \"Sólo para menciones\" sólo lo dejará en negritas cuando seas mencionado.",
"channel_select.placeholder": "--- Selecciona un canal ---",
"channel_switch_modal.dm": "(Mensaje Directo)",
+ "channel_switch_modal.failed_to_open": "No se pudo abrir el canal.",
"channel_switch_modal.help": "Escribe el nombre del canal. Utiliza ↑↓ para navegar, TAB para seleccionar, ↵ para confirmar, ESC para descartar",
"channel_switch_modal.not_found": "No se encontró ninguna coincidencia.",
"channel_switch_modal.submit": "Cambiar",
@@ -1285,15 +1307,14 @@
"find_team.submitError": "Por favor ingresa una dirección válida",
"flag_post.flag": "Indicador de seguimiento",
"flag_post.unflag": "Desmarcar",
+ "general_tab.chooseDescription": "Por favor escoge una nueva descripción para tu equipo",
"general_tab.chooseName": "Por favor escoge otro nombre para tu equipo",
"general_tab.codeDesc": "Haz clic en 'Editar' para regenerar el código de Invitación.",
- "general_tab.codeLongDesc": "El Código de Invitación es utilizado como parte del URL del enlace creado por la opción <strong>Obtener Enlace de invitación</strong> en el menú principal. Regenerar este código crea un nuevo enlace e invalida los enlaces anteriores.",
+ "general_tab.codeLongDesc": "El Código de invitación se utiliza como parte de la URL en el enlace de invitación al equipo creado por {getTeamInviteLink} en el menú principal. La regeneración se crea un nuevo enlace de invitación al equipo e invalida el enlace anterior.",
"general_tab.codeTitle": "Código de Invitación",
- "general_tab.dirDisabled": "El directorio de Equipos ha sido deshabilitado. Por favor solicita a un Administrador de Sistema que habilite la opción de Directorio de Equipos en la Consola del Sistema.",
- "general_tab.dirOff": "El directorio de Equipos ha sido deshabilitado para este sistema.",
"general_tab.emptyDescription": "Clic en 'Editar' para añadir una descripción del equipo.",
+ "general_tab.getTeamInviteLink": "Enlace invitación al equipo",
"general_tab.includeDirDesc": "Incluir este equipo mostrará el nombre del equipo en la sección de Directorio de Equipos en la página de inicio, y proveerá un enlace para la página de inicio de sesión.",
- "general_tab.includeDirTitle": "Incluir este Equipo en el Directorio de Equipos",
"general_tab.no": "No",
"general_tab.openInviteDesc": "Cuando está permitido, un enlace para este equipo será incluido en la página de inicio permitiendo a cualquier persona con una cuenta unirse a este equipo.",
"general_tab.openInviteTitle": "Permitir que se una al equipo cualquier usuario con una cuenta en este servidor",
@@ -1340,7 +1361,7 @@
"help.commands.builtin": "## Comandos integrados\nLos siguientes comandos de barra están disponibles en todas las instalaciones de Mattermost:",
"help.commands.builtin2": "Empezieza escribiendo `/ ` y una lista de opciones de comandos de barra aparecerá encima del campo de texto. Las sugerencias de autocompletado ayudan al proveer un ejemplo formateado en texto de color negro y una breve descripción del comando de barra en texto de color gris.",
"help.commands.custom": "## Comandos Personalizados\nComandos de barra peronalizados se integran con aplicaciones externas. Por ejemplo, un equipo puede configurar comandos de barra personalizados para comprobar registros internos sobre la salud del `/paciente joe smith` o compruebe semanalmente el clima en una ciudad usando `/clima de toronto en la semana`. Consulte con su Administrador del Sistema o abra la lista de autocompletado escribiendo `/` para determinar si su equipo tiene configurado algunos comandos de barra personalizados.",
- "help.commands.custom2": "Los comandos de barra Personalizados están deshabilitados de forma predeterminada y pueden ser activados por el Administrador del Sistema en la **Consola del Sistema** > **Integraciones** > **Integraciones Personalizadas**. Aprende más acerca de la configuración personalizada de comandos de barra en la [documentación para desarrollar comandos de barra](http://docs.mattermost.com/developer/slash-commands.html).",
+ "help.commands.custom2": "Los comandos de barra Personalizados están inhabilitados de forma predeterminada y pueden ser activados por el Administrador del Sistema en la **Consola del Sistema** > **Integraciones** > **Integraciones Personalizadas**. Aprende más acerca de la configuración personalizada de comandos de barra en la [documentación para desarrollar comandos de barra](http://docs.mattermost.com/developer/slash-commands.html).",
"help.commands.intro": "Los comandos de barra realizan operaciones en Mattermost escribiendo en el campo de texto. Introduzca un `/` seguido por un comando y algunos argumentos para realizar acciones.\n\nTodas las instalaciones de Mattermost vienen con comandos de barra integrados y los comandos de barra personalizados se pueden configurar para interactuar con aplicaciones externas. Aprende más acerca de la configuración personalizada de comandos de barra en la [documentación para desarrollar comandos de barra](http://docs.mattermost.com/developer/slash-commands.html).",
"help.commands.title": "# Ejecutar Comandos\n___",
"help.composing.deleting": "## Eliminar un mensaje\nElimina un mensaje haciendo clic en el icono **[...]** junto a cualquier mensaje de texto que has compuesto, a continuación, haz clic en **Borrar**. Los Administradores de Sistema y Equipo pueden borrar cualquier mensaje en su sistema o equipo.",
@@ -1550,6 +1571,7 @@
"login.passwordChanged": " La contraseña ha sido actualizada",
"login.session_expired": " Tu sesión ha expirado. Por favor inicia sesión nuevamente.",
"login.signIn": "Entrar",
+ "login.signInLoading": "Iniciando sesión…",
"login.signInWith": "Iniciar sesión con:",
"login.userNotFound": "No pudimos encontrar una cuenta que coincida con tus credenciales.",
"login.username": "Nombre de usuario",
@@ -1561,8 +1583,10 @@
"member_item.makeAdmin": "Convertir en Admin de Equipo",
"member_item.member": "Miembro",
"member_list.noUsersAdd": "No hay usuarios que agregar.",
+ "members_popover.manageMembers": "Administrar Miembros",
"members_popover.msg": "Mensaje",
"members_popover.title": "Miembros",
+ "members_popover.viewMembers": "Ver Miembros",
"mfa.confirm.complete": "<strong>¡Configuración completada!</strong>",
"mfa.confirm.okay": "Aceptar",
"mfa.confirm.secure": "Tu cuenta ahora está segura. La próxima vez que inicies sesión, se te solicitará que ingreses el código de la app de Google Authenticator de tu teléfono.",
@@ -1665,6 +1689,7 @@
"post_info.mobile.unflag": "Desmarcar",
"post_info.permalink": "Enlace permanente",
"post_info.reply": "Responder",
+ "post_message_view.edited": "(editado)",
"posts_view.loadMore": "Cargar más mensajes",
"posts_view.newMsg": "Nuevos Mensajes",
"posts_view.newMsgBelow": "{count, plural, one {Nuevo mensaje} other {Nuevos mensajes}} ▼",
@@ -1777,9 +1802,9 @@
"signup.office365": "Office 365",
"signup.title": "Crea una cuenta con:",
"signup_team.createTeam": "O Crea un Equipo",
- "signup_team.disabled": "La creación de Equipos ha sido deshabilitada.",
+ "signup_team.disabled": "La creación de Equipos ha sido inhabilitada.",
"signup_team.join_open": "Equipos a los que te puedes unir: ",
- "signup_team.noTeams": "No hay equipos en el Directorio de Equipos y la creación de equipos ha sido deshabilitada.",
+ "signup_team.noTeams": "No hay equipos en el Directorio de Equipos y la creación de equipos ha sido inhabilitada.",
"signup_team.no_open_teams": "No hay equipos están disponibles para unirse. Por favor, solicita una invitación a tu administrador.",
"signup_team.no_open_teams_canCreate": "No hay equipos están disponibles para unirse. Por favor, crear un nuevo equipo, o solicita una invitación a tu administrador.",
"signup_team.no_teams": "No se han creado equipos. Por favor contacta a tu administrador.",
@@ -1899,7 +1924,7 @@
"update_command.question": "Tus cambios puede que rompan el comando de barra existente. ¿Estás seguro de que deseas actualizar?",
"update_command.update": "Actualizar",
"upload_overlay.info": "Arrastra un archivo para subirlo.",
- "user.settings.advance.embed_preview": "Mostrar vistas previas experimentales del contenido de los enlaces, cuando esté disponible",
+ "user.settings.advance.embed_preview": "Para lel primer enlace web en un mensaje, se mostrará una vista previa del contenido del sitio web a continuación del mensaje, si está disponible",
"user.settings.advance.embed_toggle": "Capacidad de Mostrar/Esconder las previsualizaciones",
"user.settings.advance.enabledFeatures": "{count, number} {count, plural, one {Característica} other {Caracteristicas}} Habilitadas",
"user.settings.advance.formattingDesc": "Si está activada, se dará formato a los mensajes, creando enlaces, mostrando emoticones, el estilo del texto, y añadir saltos de línea. De forma predeterminada, esta opción está habilitada. El cambio de esta configuración requiere que la página se actualice.",
@@ -1944,10 +1969,10 @@
"user.settings.display.channelDisplayTitle": "Modo en que se muestra el Canal",
"user.settings.display.channeldisplaymode": "Selecciona el ancho de la vista central.",
"user.settings.display.clockDisplay": "Visualización del Reloj",
- "user.settings.display.collapseDesc": "Ampliar los vínculos para mostrar una vista previa del contenido, cuando esté disponible.",
- "user.settings.display.collapseDisplay": "Vista previa del enlace",
- "user.settings.display.collapseOff": "Apagado",
- "user.settings.display.collapseOn": "Encendido",
+ "user.settings.display.collapseDesc": "Establecer si las vistas previas de los enlaces con imagen se deben mostrar como expandidos o contraídos de forma predeterminada. Este ajuste también puede ser controlado mediante el uso del los comandos de barra /expand y /collapse.",
+ "user.settings.display.collapseDisplay": "Apariencia predeterminada de lasa vistas previas de los enlaces con imagenes",
+ "user.settings.display.collapseOff": "Colapsado",
+ "user.settings.display.collapseOn": "Expandido",
"user.settings.display.fixedWidthCentered": "De ancho fijo, centrado",
"user.settings.display.fontDesc": "Selecciona la fuente con la que quieres ver la interfaz de Mattermost.",
"user.settings.display.fontTitle": "Fuente de Visualización",
@@ -2107,8 +2132,8 @@
"user.settings.push_notification.allActivityOffline": "Para toda la actividad cuando esté desconectado",
"user.settings.push_notification.allActivityOnline": "De toda la actividad cuando esté en línea, ausente o desconectado",
"user.settings.push_notification.away": "Ausente o desconectado",
- "user.settings.push_notification.disabled": "Deshabilitado por el Administrador de Sistema",
- "user.settings.push_notification.disabled_long": "Las Notificaciones Push para dispositivos móviles han sido deshabilitadas por el Administrador del Sistema.",
+ "user.settings.push_notification.disabled": "Inhabilitado por el Administrador de Sistema",
+ "user.settings.push_notification.disabled_long": "Las Notificaciones Push para dispositivos móviles han sido inhabilitadas por el Administrador del Sistema.",
"user.settings.push_notification.info": "Se enviarán notificaciones a tu dispositivo móvil cuando haya actividad en Mattermost.",
"user.settings.push_notification.off": "Apagado",
"user.settings.push_notification.offline": "Desconectado",
@@ -2130,7 +2155,9 @@
"user.settings.security.lastUpdated": "Última actualización {date} a las {time}",
"user.settings.security.ldap": "AD/LDAP",
"user.settings.security.loginGitlab": "Inicio de sesión realizado a través de GitLab",
+ "user.settings.security.loginGoogle": "Inicio de sesión realizado a través de Google Apps",
"user.settings.security.loginLdap": "Inicio de sesión realizado a través de AD/LDAP",
+ "user.settings.security.loginOffice365": "Inicio de sesión realizado a través de Office 365",
"user.settings.security.loginSaml": "Inicio de sesión realizado a través de SAML",
"user.settings.security.logoutActiveSessions": "Visualizar y cerrar las sesiones activas",
"user.settings.security.method": "Método de inicio de sesión",
@@ -2159,9 +2186,11 @@
"user.settings.security.passwordErrorUppercaseNumberSymbol": "La contraseña debe contener al menos {min} caracteres compuesta de al menos una letra mayúscula, al menos un número, y al menos un símbolo (por ejemplo,\"~!@#$%^&*()\").",
"user.settings.security.passwordErrorUppercaseSymbol": "La contraseña debe contener al menos {min} caracteres compuesta de al menos una letra mayúscula y al menos un símbolo (por ejemplo,\"~!@#$%^&*()\").",
"user.settings.security.passwordGitlabCantUpdate": "El inicio de sesión ocurre a través GitLab. La contraseña no se puede actualizar.",
+ "user.settings.security.passwordGoogleCantUpdate": "El inicio de sesión ocurre a través Google Apps. La contraseña no se puede actualizar.",
"user.settings.security.passwordLdapCantUpdate": "El inicio de sesión ocurre a través AD/LDAP. La contraseña no se puede actualizar.",
"user.settings.security.passwordMatchError": "La nuevas contraseñas que ingresaste no coinciden.",
"user.settings.security.passwordMinLength": "Longitud mínima no válida, no se puede mostrar la vista previa.",
+ "user.settings.security.passwordOffice365CantUpdate": "El inicio de sesión ocurre a través Office 365. La contraseña no se puede actualizar.",
"user.settings.security.passwordSamlCantUpdate": "La contraseña es manejada a través del proveedor de inicio de sesión. Si deseas cambiarla, deberás hacerlo a través del proveedor de identidad.",
"user.settings.security.retypePassword": "Reescribe la Nueva Contraseña",
"user.settings.security.saml": "SAML",
diff --git a/webapp/i18n/fr.json b/webapp/i18n/fr.json
index e1f6e6afe..b464930b9 100644
--- a/webapp/i18n/fr.json
+++ b/webapp/i18n/fr.json
@@ -3,7 +3,7 @@
"about.copyright": "Copyright 2016 Mattermost, Inc. Tous droits réservés",
"about.database": "Base de données :",
"about.date": "Date de compilation :",
- "about.enterpriseEditionLearn": "Apprenez-en plus sur l’Édition Entreprise à : ",
+ "about.enterpriseEditionLearn": "Pour en savoir davantage sur l’Édition Entreprise : ",
"about.enterpriseEditionSt": "Communication moderne derrière votre pare-feu.",
"about.enterpriseEditione1": "Édition Entreprise",
"about.hash": "Clé de compilation :",
@@ -28,6 +28,7 @@
"activity_log.sessionsDescription": "Les sessions sont créées lorsque vous vous connectez depuis un nouveau navigateur. Les sessions vous permettent d'utiliser Mattermost sans devoir vous connecter à nouveau durant un temps défini par l'administrateur système. Si vous souhaitez vous déconnecter plus tôt, utilisez le bouton \"Se déconnecter\" ci-dessous pour mettre fin à votre session.",
"activity_log_modal.android": "Android",
"activity_log_modal.androidNativeApp": "Application Android",
+ "activity_log_modal.desktop": "Native Desktop App",
"activity_log_modal.iphoneNativeApp": "Application pour iPhone",
"add_command.autocomplete": "Auto-complétion",
"add_command.autocomplete.help": "(Facultatif) Afficher les commandes slash dans la liste d'auto-complétion.",
@@ -58,8 +59,8 @@
"add_command.trigger.helpReserved": "Réservé : {link}",
"add_command.trigger.helpReservedLinkText": "voir la liste des commandes slash incluses",
"add_command.trigger.placeholder": "Mot-clé de déclenchement par exemple \"hello\"",
- "add_command.triggerInvalidLength": "un mot-clé doit contenir entre {min} à {max} caractères",
- "add_command.triggerInvalidSlash": "Un mot-clé ne peut commencer par un /",
+ "add_command.triggerInvalidLength": "Un mot-clé déclencheur doit contenir de {min} à {max} caractères",
+ "add_command.triggerInvalidSlash": "Un mot-clé déclencheur ne peut commencer par un /",
"add_command.triggerInvalidSpace": "Un mot-clé ne doit pas contenir d’espaces",
"add_command.triggerRequired": "Un mot-clé est requis",
"add_command.url": "URL de requête",
@@ -156,7 +157,7 @@
"admin.cluster.interNodeUrlsTitle": "URLs de communication entre noeuds :",
"admin.cluster.loadedFrom": "Le fichier de configuration a été chargé par le noeud ID {clusterId}. Veuillez vous référer au guide de résolution des problèmes de notre <a href=\"http://docs.mattermost.com/deployment/cluster.html\" target=\"_blank\">documentation</a> si vous accédez à la Console Système à partir d'un répartiteur de charge et que vous rencontrez des problèmes.",
"admin.cluster.noteDescription": "Changer les propriétés de cette section requiert un redémarrage du serveur avant de prendre effet. Lorsque le mode Haute Disponibilité est activé, la Console Système est mise en lecture-seule et ne peut être changée que par le fichier de configuration.",
- "admin.cluster.should_not_change": "ATTENTION : Ces paramètres ne se synchroniseront pas avec les autres serveurs du cluster. La communication entre nœuds en mode Haute Disponibilité ne démarrera pas tant que le fichier config.json ne sera pas identique sur chacun des serveurs et que Mattermost n'aura pas été redémarré. Veuillez vous référer à la <a href=\"http://docs.mattermost.com/deployment/cluster.html\" target=\"_blank\">documentation</a> pour savoir comment ajouter ou supprimer un serveur d'un cluster. Si vous accédez à la Console Système à partir d'un répartiteur de charges et que vous rencontrez des problèmes, veuillez vous référer au guide de résolution des problèmes de notre <a href=\"http://docs.mattermost.com/deployment/cluster.html\" target=\"_blank\">documentation</a>.",
+ "admin.cluster.should_not_change": "ATTENTION : Ces paramètres ne se synchroniseront pas avec les autres serveurs du cluster. La communication entre nœuds en mode Haute Disponibilité ne démarrera pas tant que le fichier config.json ne sera pas identique sur chacun des serveurs et que Mattermost n'aura pas été redémarré. Veuillez vous référer à la <a href=\"http://docs.mattermost.com/deployment/cluster.html\" target=\"_blank\">documentation</a> pour savoir comment ajouter ou supprimer un serveur d'un cluster. Si vous accédez à la Console système à partir d'un répartiteur de charges et que vous rencontrez des problèmes, veuillez vous référer au guide de résolution des problèmes de notre <a href=\"http://docs.mattermost.com/deployment/cluster.html\" target=\"_blank\">documentation</a>.",
"admin.cluster.status_table.config_hash": "MD5 du fichier de configuration",
"admin.cluster.status_table.hostname": "Nom d'hôte",
"admin.cluster.status_table.id": "ID du nœud",
@@ -172,14 +173,14 @@
"admin.compliance.enableDesc": "Si activé, Mattermost autorise les rapports de conformité depuis l'onglet <strong>Conformité et vérification</strong>. Consultez la <a href=\"https://docs.mattermost.com/administration/compliance.html\" target=\"_blank\">documentation</a> pour en apprendre davantage.",
"admin.compliance.enableTitle": "Activer le rapport de conformité :",
"admin.compliance.false": "non",
- "admin.compliance.noLicense": "<h4 class=\"banner__heading\">Note :</h4><p>La conformité est une option disponible sur l'édition Entreprise. Votre licence ne permet pas d'utiliser cette fonction. Cliquez <a href=\"http://mattermost.com\" target=\"_blank\">ici</a> pour des informations sur la licence Entreprise et connaître son prix.</p>",
+ "admin.compliance.noLicense": "<h4 class=\"banner__heading\">Note :</h4><p>La conformité est une option disponible sur l'édition Entreprise. Votre licence ne permet pas d'utiliser cette fonction. Veuillez cliquer <a href=\"http://mattermost.com\" target=\"_blank\">ici</a> pour des informations sur la licence Entreprise et connaître son prix.</p>",
"admin.compliance.save": "Enregistrer",
"admin.compliance.saving": "Enregistrement des paramètres...",
"admin.compliance.title": "Configuration de la conformité",
"admin.compliance.true": "vrai",
"admin.compliance_reports.desc": "Profession :",
"admin.compliance_reports.desc_placeholder": "Ex : \"Audit 445 pour les RH\"",
- "admin.compliance_reports.emails": "Adresses électroniques :",
+ "admin.compliance_reports.emails": "Adresses e-mail :",
"admin.compliance_reports.emails_placeholder": "Ex : \"bill@example.com, bob@example.com »",
"admin.compliance_reports.from": "De :",
"admin.compliance_reports.from_placeholder": "Ex : \"2016-03-18\"",
@@ -228,14 +229,14 @@
"admin.database.title": "Paramètres de base de données",
"admin.developer.title": "Paramètres de développement",
"admin.email.agreeHPNS": " J’ai compris et j’accepte les <a href=\"https://about.mattermost.com/hpns-terms/\" target=\"_blank\">termes de service</a> et la <a href=\"https://about.mattermost.com/hpns-privacy/\" target=\"_blank\">politique de protection de la vie privée</a> du service de notifications Push hébergé par Mattermost.",
- "admin.email.allowEmailSignInDescription": "Si activé, les utilisateurs pourront se connecter avec leur adresse électronique et mot de passe.",
- "admin.email.allowEmailSignInTitle": "Activer la connexion avec adresse électronique : ",
- "admin.email.allowSignupDescription": "Si activé, la création d'équipes et de comptes en utilisant une adresse électronique et un mot de passe sera autorisée. Cette valeur devrait être fausse seulement si vous souhaitez limiter les connexions via un service SSO comme OAuth ou LDAP.",
- "admin.email.allowSignupTitle": "Activer la création de compte avec adresse électronique : ",
- "admin.email.allowUsernameSignInDescription": "Si activé, les utilisateurs seront autorisés à se connecter avec leur nom d'utilisateur et leur mot de passe. Cette option n'est généralement utilisée que lorsque la vérification de l'adresse électronique est désactivée.",
+ "admin.email.allowEmailSignInDescription": "Si activé, les utilisateurs pourront se connecter avec leur adresse e-mail et leur mot de passe.",
+ "admin.email.allowEmailSignInTitle": "Activer la connexion avec une adresse e-mail : ",
+ "admin.email.allowSignupDescription": "Si activé, la création d'équipes et de comptes en utilisant une adresse e-mail et un mot de passe sera autorisée. Cette valeur devrait être fausse seulement si vous souhaitez limiter les connexions via un service SSO comme OAuth ou LDAP.",
+ "admin.email.allowSignupTitle": "Activer la création de comptes avec une adresse e-mail : ",
+ "admin.email.allowUsernameSignInDescription": "Si activé, les utilisateurs seront autorisés à se connecter avec leur nom d'utilisateur et leur mot de passe. Cette option n'est généralement utilisée que lorsque la vérification de l'adresse e-mail est désactivée.",
"admin.email.allowUsernameSignInTitle": "Activer la connexion avec nom d'utilisateur : ",
"admin.email.connectionSecurityTest": "Tester la connexion",
- "admin.email.easHelp": "En savoir plus sur la compilation et le déploiement de vos propres applications mobiles à partir de l'<a href=\"http://docs.mattermost.com/deployment/push.html#enterprise-app-store-eas\" target=\"_blank\">Enterprise App Store</a>.",
+ "admin.email.easHelp": "En savoir plus sur la compilation et le déploiement de vos propres applications mobiles à partir de l'<a href=\"http://docs.mattermost.com/deployment/push.html#enterprise-app-store-eas\" target=\"_blank\">App Store Entreprise</a>.",
"admin.email.emailFail": "Echec de la connexion : {error}",
"admin.email.emailSuccess": "Aucune erreur signalée lors de l'envoi de l'e-mail. Vérifiez votre boîte de réception pour vous en assurer.",
"admin.email.enableEmailBatching.clusterEnabled": "L'envoi d'e-mails par lot ne peut pas être activé lorsque le mode Haute Disponibilité est activé.",
@@ -244,14 +245,14 @@
"admin.email.enableEmailBatchingTitle": "Activer l'envoi d'e-mails par lot :",
"admin.email.fullPushNotification": "Envoyer un extrait du message complet",
"admin.email.genericPushNotification": "Envoyer une description générale avec les noms des utilisateurs et des canaux",
- "admin.email.inviteSaltDescription": "Clé de salage de 32 caractères ajouté aux e-mails d'invitation. Générée aléatoirement lors de l'installation. Cliquez sur \"Regénérer\" pour créer une nouvelle clé de salage.",
+ "admin.email.inviteSaltDescription": "Clé de salage de 32 caractères ajouté aux e-mails d'invitation. Générée aléatoirement lors de l'installation. Veuillez cliquer sur \"Regénérer\" pour créer une nouvelle clé de salage.",
"admin.email.inviteSaltExample": "Ex. : \"bjlSR4QqkXFBr7TP4oDzlfZmcNuH9Yo\"",
"admin.email.inviteSaltTitle": "Clé de salage des e-mails d'invitation :",
"admin.email.mhpns": "La connexion à iOS et aux applications Android est cryptée",
"admin.email.mhpnsHelp": "Téléchargez l'<a href=\"https://itunes.apple.com/us/app/mattermost/id984966508?mt=8\" target=\"_blank\">application iOS Mattermost</a> depuis iTunes. Téléchargez l'<a href=\"https://play.google.com/store/apps/details?id=com.mattermost.mattermost&hl=en\" target=\"_blank\">application Android Mattermost</a> depuis le Google Play. Apprenez-en plus sur <a href=\"http://docs.mattermost.com/deployment/push.html#hosted-push-notifications-service-hpns\" target=\"_blank\">HPNS</a>.",
"admin.email.mtpns": "Utilisez iOS et Android sur iTunes et Google Play avec TPNS",
"admin.email.mtpnsHelp": "Téléchargez l'<a href=\"https://itunes.apple.com/us/app/mattermost/id984966508?mt=8\" target=\"_blank\">application iOS Mattermost</a> depuis iTunes. Téléchargez l'<a href=\"https://play.google.com/store/apps/details?id=com.mattermost.mattermost&hl=en\" target=\"_blank\">application Android Mattermost</a> depuis le Google Play. Apprenez-en plus sur <a href=\"http://docs.mattermost.com/deployment/push.html#test-push-notifications-service-tpns\" target='_blank'>TPNS</a>.",
- "admin.email.nofificationOrganizationExample": "Ex. \"© ABC Corporation, 565 Knight Way, Palo Alto, California, 94305, USA\"",
+ "admin.email.nofificationOrganizationExample": "Exemple : \"(c)MonEntreprise, 12 avenue Niel, 75017 Paris, France\"",
"admin.email.notificationDisplayDescription": "Afficher le nom du compte de messagerie utilisé lors de l'envoi de notifications par Mattermost.",
"admin.email.notificationDisplayExample": "Ex. : \"Notification Mattermost\", \"Système\", \"No-reply\"",
"admin.email.notificationDisplayTitle": "Nom affiché dans les notifications :",
@@ -259,10 +260,11 @@
"admin.email.notificationEmailExample": "Ex. : \"mattermost@yourcompany.com\", \"admin@yourcompany.com\"",
"admin.email.notificationEmailTitle": "Notification depuis l'adresse :",
"admin.email.notificationOrganization": "Adresse e-mail du pied de page pour les notifications e-mail :",
- "admin.email.notificationOrganizationDescription": "Nom de société et adresse affichés sur les notifications par courrier électronique, comme \"(c) MaSociété, 21 avenue Niel, 75017 Paris, France\". Si ce champ est vide, il ne sera pas inclus dans les courriers électroniques.",
+ "admin.email.notificationOrganizationDescription": "Nom de société et adresse affichés sur les notifications envoyées par e-mail, comme \"© MonEntreprise, 55 Rue du Faubourg Saint-Honoré, 75008 Paris, France\". Si ce champ est vide, il ne sera pas inclus dans les e-mails.",
+ "admin.email.notificationOrganizationExample": "Exemple : \"(c)MonEntreprise, 12 avenue Niel, 75017 Paris, France\"",
"admin.email.notificationsDescription": "En général, activé en production. Si activé, Mattermost essaye d'envoyer des notifications par courrier électronique. Les développeurs peuvent en revanche désactiver cette option pour gagner du temps.<br />Activer cette option retire la bannière \"Mode découverte\" (cela nécessite de se déconnecter puis se re-connecter après avoir activé l'option).",
"admin.email.notificationsTitle": "Activer les notifications par e-mail :",
- "admin.email.passwordSaltDescription": "Clé de salage de 32 caractères utilisé pour générer la clé de réinitialisation du mot de passe. Générée aléatoirement à l'installation. Cliquez sur \"re-générer\" pour créer une nouvelle clé de salage.",
+ "admin.email.passwordSaltDescription": "Clé de salage de 32 caractères utilisé pour générer la clé de réinitialisation du mot de passe. Générée aléatoirement à l'installation. Veuillez cliquer sur \"regénérer\" pour créer une nouvelle clé de salage.",
"admin.email.passwordSaltExample": "Ex. : \"bjlSR4QqkXFBr7TP4oDzlfZmcNuH9Yo\"",
"admin.email.passwordSaltTitle": "Clé de salage de réinitialisation du mot de passe :",
"admin.email.pushContentDesc": "Choisir \"Envoyer une description générale avec les noms des utilisateurs et des canaux\" permet aux notifications push d'envoyer des messages sans détail, en incluant juste les noms d'utilisateurs et de canaux.<br /><br />Choisir \"Envoyer un extrait du message complet\" envoie des extraits des messages qui déclenchent les notifications, et peuvent inclure des informations confidentielles visibles sur le terminal des utilisateurs notifiés. Si votre serveur de notifications Push est en dehors de votre pare-feu, il est HAUTEMENT RECOMMANDÉ d'utiliser cette option uniquement avec le protocole \"https\".",
@@ -274,9 +276,9 @@
"admin.email.pushServerEx": "Exemple : \"http://push-test.mattermost.com\"",
"admin.email.pushServerTitle": "Serveur de notifications push :",
"admin.email.pushTitle": "Envoyer des notifications push : ",
- "admin.email.requireVerificationDescription": "En général activé en production. Si activé, Mattermost impose une vérification de l'adresse électronique avant d'autoriser la connexion. Vous pouvez désactiver cette option en développement.",
- "admin.email.requireVerificationTitle": "Imposer la vérification de l'adresse électronique : ",
- "admin.email.selfPush": "Configuration manuel du service de notification Push",
+ "admin.email.requireVerificationDescription": "En général activé en production. Si activé, Mattermost impose une vérification de l'adresse e-mail avant d'autoriser la connexion. Vous pouvez désactiver cette option en développement.",
+ "admin.email.requireVerificationTitle": "Imposer la vérification de l'adresse e-mail : ",
+ "admin.email.selfPush": "Spécifiez manuellement la configuration du service de notifications Push",
"admin.email.smtpPasswordDescription": " Récupérez ces informations de la part de l'administrateur de votre serveur de mails.",
"admin.email.smtpPasswordExample": "Ex. : \"yourpassword\", \"jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY\"",
"admin.email.smtpPasswordTitle": "Mot de passe du serveur SMTP :",
@@ -308,10 +310,20 @@
"admin.general.localization.serverLocaleTitle": "Langue par défaut du serveur :",
"admin.general.log": "Journalisation",
"admin.general.policy": "Règles",
+ "admin.general.policy.allowEditPostAlways": "Any time",
+ "admin.general.policy.allowEditPostDescription": "Set policy on the length of time authors have to edit their messages after posting.",
+ "admin.general.policy.allowEditPostNever": "Jamais",
+ "admin.general.policy.allowEditPostTimeLimit": "seconds after posting",
+ "admin.general.policy.allowEditPostTitle": "Allow users to edit their messages:",
"admin.general.policy.permissionsAdmin": "Administrateurs d'équipe et administrateurs système",
"admin.general.policy.permissionsAll": "Tous les membres de l'équipe",
"admin.general.policy.permissionsAllChannel": "Tous les membres du canal",
+ "admin.general.policy.permissionsDeletePostAdmin": "Administrateurs d'équipe et administrateurs système",
+ "admin.general.policy.permissionsDeletePostAll": "Message authors can delete their own messages, and Administrators can delete any message",
+ "admin.general.policy.permissionsDeletePostSystemAdmin": "Administrateurs système",
"admin.general.policy.permissionsSystemAdmin": "Administrateurs système",
+ "admin.general.policy.restrictPostDeleteDescription": "Set policy on who has permission to delete messages.",
+ "admin.general.policy.restrictPostDeleteTitle": "Allow which users to delete messages:",
"admin.general.policy.restrictPrivateChannelCreationDescription": "Choisit qui peut créer des groupes privés.",
"admin.general.policy.restrictPrivateChannelCreationTitle": "Activer la création de groupes privés pour :",
"admin.general.policy.restrictPrivateChannelDeletionCommandLineToolLink": "outil en ligne de commande",
@@ -326,7 +338,7 @@
"admin.general.policy.restrictPublicChannelDeletionTitle": "Activer la suppression de canaux publics pour :",
"admin.general.policy.restrictPublicChannelManagementDescription": "Choisit qui peut renommer et définir l'en-tête ou la description des canaux publics.",
"admin.general.policy.restrictPublicChannelManagementTitle": "Activer le renommage de canaux publics pour :",
- "admin.general.policy.teamInviteDescription": "Choisir qui peut inviter d'autres personnes à une équipe en utilisant <b>Inviter un nouveau membre</b> pour inviter de nouveaux utilisateurs par courrier électronique, ou l'option <b>Obtenir un lien d'invitation d'équipe</b> du menu principal. Si l'option <b>Obtenir un lien d'invitation d'équipe</b> est utilisée pour partager un lien, vous pouvez faire expirer le code d'invitation depuis <b>Configuration de l'équipe</b> > <b>Code d'invitation</b> une fois que les utilisateurs désirés ont rejoint l'équipe.",
+ "admin.general.policy.teamInviteDescription": "Choisir qui peut inviter d'autres personnes à une équipe en utilisant <b>Inviter un nouveau membre</b> pour inviter des nouveaux utilisateurs par e-mail, ou l'option <b>Obtenir un lien d'invitation d'équipe</b> du menu principal. Si l'option <b>Obtenir un lien d'invitation d'équipe</b> est utilisée pour partager un lien, vous pouvez faire expirer le code d'invitation depuis <b>Configuration de l'équipe</b> > <b>Code d'invitation</b> une fois que les utilisateurs désirés ont rejoint l'équipe.",
"admin.general.policy.teamInviteTitle": "Autoriser l'envoi d'invitation depuis :",
"admin.general.privacy": "Confidentialité",
"admin.general.usersAndTeams": "Utilisateur et équipes",
@@ -363,6 +375,7 @@
"admin.image.amazonS3BucketExample": "Ex. : \"mattermost-media\"",
"admin.image.amazonS3BucketTitle": "Bucket S3 Amazon :",
"admin.image.amazonS3EndpointDescription": "Nom d'hôte de votre fournisseur de stockage compatible S3. Par défaut : 's3.amazonaws.com'.",
+ "admin.image.amazonS3EndpointExample": "E.g.: \"s3.amazonaws.com\"",
"admin.image.amazonS3EndpointTitle": "Endpoint Amazon S3 :",
"admin.image.amazonS3IdDescription": "Demandez cette information à votre administrateur AWS.",
"admin.image.amazonS3IdExample": "Ex. : \"AKIADTOVBGERKLCBV\"",
@@ -393,7 +406,7 @@
"admin.image.profileWidthDescription": "Largeur de la photo de profil.",
"admin.image.profileWidthExample": "Ex. : \"1024\"",
"admin.image.profileWidthTitle": "Largeur de photo de profil :",
- "admin.image.publicLinkDescription": "Clé de salage de 32 caractères ajoutée aux e-mails d'invitation. Générée aléatoirement lors de l'installation. Cliquez sur \"Regénérer\" pour créer une nouvelle clé de salage.",
+ "admin.image.publicLinkDescription": "Clé de salage de 32 caractères ajoutée aux e-mails d'invitation. Générée aléatoirement lors de l'installation. Veuillez cliquer sur \"Regénérer\" pour créer une nouvelle clé de salage.",
"admin.image.publicLinkExample": "Ex. : \"gxHVDcKUyP2y1eiyW8S8na1UYQAfq6J6\"",
"admin.image.publicLinkTitle": "Clé de salage publique :",
"admin.image.shareDescription": "Permettre aux utilisateurs de partager des liens et images publics.",
@@ -418,9 +431,9 @@
"admin.ldap.bindPwdTitle": "Mot de passe de l'utilisateur de recherche :",
"admin.ldap.bindUserDesc": "Nom de l'utilisateur LDAP utilisé pour rechercher les autres utilisateurs. Il peut s'agir d'un utilisateur créé spécifiquement pour Mattermost, et disposant de droits restreints à l'arborescence LDAP concernée par Mattermoste.",
"admin.ldap.bindUserTitle": "Nom d'utilisateur pour la recherche :",
- "admin.ldap.emailAttrDesc": "Attribut du serveur LDAP utilisé pour le champ \"adresse électronique\" dans Mattermost.",
+ "admin.ldap.emailAttrDesc": "Attribut du serveur LDAP utilisé pour le champ \"adresse e-mail\" dans Mattermost.",
"admin.ldap.emailAttrEx": "Ex. : \"mail\" ou \"userPrincipalName\"",
- "admin.ldap.emailAttrTitle": "Attribut \"adresse électronique\" :",
+ "admin.ldap.emailAttrTitle": "Attribut \"adresse e-mail\" :",
"admin.ldap.enableDesc": "Si activé, Mattermost permet de s'authentifier avec le serveur LDAP",
"admin.ldap.enableTitle": "Activer la connexion avec LDAP :",
"admin.ldap.firstnameAttrDesc": "(Optionnel) L'attribut dans le serveur AD/LDAP qui sera utilisé pour les prénoms des utilisateurs de Mattermost. Lorsque défini, les utilisateurs ne pourront pas éditer leur prénom étant donné qu'il est alors synchronisé avec le serveur LDAP. Lorsque laissé vide, les utilisateurs peuvent définir leur propre prénom dans les paramètres du compte.",
@@ -442,7 +455,7 @@
"admin.ldap.nicknameAttrDesc": "(Optionnel) L'attribut dans le serveur AD/LDAP qui sera utilisé pour les surnoms des utilisateurs de Mattermost. Lorsque défini, les utilisateurs ne pourront pas éditer leur surnoms étant donné qu'il est alors synchronisé avec le serveur LDAP. Lorsque laissé vide, les utilisateurs peuvent définir leur propre surnom dans les paramètres du compte.",
"admin.ldap.nicknameAttrEx": "Ex. : \"surnom\"",
"admin.ldap.nicknameAttrTitle": "Attribut \"nom d'utilisateur\" :",
- "admin.ldap.noLicense": "<h4 class=\"banner__heading\">Note :</h4><p>AD/LDAP est une fonctionnalité de l'édition d’Enterprise. Votre licence actuelle ne permet pas d'utiliser AD/LDAP. Cliquez <a href=\"http://mattermost.com\" target=\"_blank\">ici</a> pour plus d'informations et connaître les tarifs de l'édition d’Enterprise.</p>",
+ "admin.ldap.noLicense": "<h4 class=\"banner__heading\">Note :</h4><p>AD/LDAP est une fonctionnalité de l'Edition Entreprise. Votre licence actuelle ne permet pas d'utiliser AD/LDAP. Veuillez cliquer <a href=\"http://mattermost.com\" target=\"_blank\">ici</a> pour plus d'informations et connaître les tarifs de l'Edition Entreprise.</p>",
"admin.ldap.portDesc": "Le port utilisé par Mattermost pour vous connecter au serveur LDAP. Par défaut : 389.",
"admin.ldap.portEx": "Ex. : \"389\"",
"admin.ldap.portTitle": "Port du serveur LDAP :",
@@ -458,7 +471,7 @@
"admin.ldap.skipCertificateVerification": "Passer la vérification",
"admin.ldap.skipCertificateVerificationDesc": "Saute l'étape de vérification des certificats pour les connexions TLS ou STARTTLS . Non recommandé pour les environnements de production où TLS est nécessaire. Pour tester seulement.",
"admin.ldap.syncFailure": "Erreur de synchronisation : {error}",
- "admin.ldap.syncIntervalHelpText": "La synchronisation LDAP met à jour les informations utilisateurs de Mattermost pour refléter les changements du serveur LDAP. Par exemple, lorsque le nom d'un utilisateur change sur le serveur LDAP, le changement est appliqué sur Mattermost lors de la synchronisation. Les comptes supprimés ou désactivés sur le serveur LDAP auront leur compte Mattermost mis comme \"Inactif\" et auront leur session de révoquée. Mattermost réalise la synchronisation dans l'intervalle entré. Par exemple, si 60 est entré, Mattermost se synchronisera toutes les 60 minutes.",
+ "admin.ldap.syncIntervalHelpText": "La synchronisation LDAP met à jour les informations utilisateurs de Mattermost pour refléter les changements du serveur LDAP. Par exemple, lorsque le nom d'un utilisateur change sur le serveur LDAP, le changement est appliqué sur Mattermost lors de la synchronisation. Les comptes supprimés ou désactivés sur le serveur LDAP auront leur compte Mattermost mis comme \"Inactif\" et auront leur session de révoquée. Mattermost réalise la synchronisation dans l'intervalle entré. Par exemple, si 60 est spécifié, Mattermost se synchronisera toutes les 60 minutes.",
"admin.ldap.syncIntervalTitle": "Intervalle de synchronisation (en minutes)",
"admin.ldap.syncNowHelpText": "Provoque une synchronisation LDAP immédiate.",
"admin.ldap.sync_button": "Synchroniser maintenant l'annuaire LDAP",
@@ -475,13 +488,13 @@
"admin.license.chooseFile": "Parcourir",
"admin.license.edition": "Edition: ",
"admin.license.key": "Clé de licence: ",
- "admin.license.keyRemove": "Supprimer la Licence Enterprise et rétrograder le serveur",
+ "admin.license.keyRemove": "Supprimer la licence Entreprise et rétrograder le serveur",
"admin.license.noFile": "Aucun fichier chargé",
"admin.license.removing": "Suppression de la licence...",
"admin.license.title": "Édition et licence",
"admin.license.type": "Licence : ",
"admin.license.upload": "Télécharger",
- "admin.license.uploadDesc": "Téléchargez une licence de Mattermost pour mettre à niveau ce serveur. <a href=\"http://mattermost.com\" target=\"_blank\">Consultez Mattermost</a> pour en apprendre plus au sujet des avantages de la version Entreprise ou pour savoir comment acheter une clé.",
+ "admin.license.uploadDesc": "Téléchargez une licence de Mattermost pour mettre à niveau ce serveur vers l'Edition Entreprise. <a href=\"http://mattermost.com\" target=\"_blank\">Consultez Mattermost</a> pour en apprendre plus au sujet des avantages de l'Edition Entreprise ou pour savoir comment acheter une clé.",
"admin.license.uploading": "Téléchargement de la licence…",
"admin.log.consoleDescription": "En principe désactivée en production. Utilisé pour le développement, pour afficher les messages d'information sur la console (stdout).",
"admin.log.consoleTitle": "Affiche les journaux sur la console : ",
@@ -513,7 +526,7 @@
"admin.metrics.enableDescription": "Lorsqu'activé, Mattermost activera la collecte des rapports de performance et de profilage. Veuillez vous référer à la <a href=\"http://docs.mattermost.com/deployment/metrics.html\" target='_blank'>documentation</a> pour en savoir plus sur la configuration du suivi des performances de Mattermost.",
"admin.metrics.enableTitle": "Activer le suivi des performances :",
"admin.metrics.listenAddressDesc": "L'adresse d'écoute du serveur pour publier les mesures de performances.",
- "admin.metrics.listenAddressEx": "Ex. : \":8067\"",
+ "admin.metrics.listenAddressEx": "Ex. : \":8065\"",
"admin.metrics.listenAddressTitle": "Adresse IP du serveur :",
"admin.mfa.bannerDesc": "L'authentification multi-facteurs n'est disponible que pour les comptes avec authentification LDAP ou email. Si vous avez des utilisateurs avec d'autres méthodes d'authentification, il est recommandé de paramétrer l'authentification multi-facteurs directement avec le fournisseur SSO ou SAML.",
"admin.mfa.cluster": "Haute",
@@ -522,7 +535,7 @@
"admin.nav.logout": "Se déconnecter",
"admin.nav.report": "Signaler un problème",
"admin.nav.switch": "Sélection de l'équipe",
- "admin.notifications.email": "Adresse électronique",
+ "admin.notifications.email": "Adresse e-mail",
"admin.notifications.push": "notifications Push sur mobile",
"admin.notifications.title": "Paramètres de notification",
"admin.oauth.gitlab": "GitLab",
@@ -552,8 +565,8 @@
"admin.password.requirementsDescription": "Lettres obligatoires pour un mot de passe valide.",
"admin.password.symbol": "Au moins un symbole (ex : \"~!@#$%^&*()\")",
"admin.password.uppercase": "Au moins une majuscule",
- "admin.privacy.showEmailDescription": "Si désactivé, masque les adresses électronique des utilisateurs dans l'interface, y compris pour les responsables d'équipe. Cette option est pratique quand Mattermost est utilisé pour gérer des équipes dans lesquelles les utilisateurs préfèrent garder leurs informations privées.",
- "admin.privacy.showEmailTitle": "Afficher l'adresse électronique : ",
+ "admin.privacy.showEmailDescription": "Si désactivé, masque les adresses e-mail des utilisateurs dans l'interface, y compris pour les responsables d'équipe. Cette option est pratique quand Mattermost est utilisé pour gérer des équipes dans lesquelles les utilisateurs préfèrent garder leurs informations privées.",
+ "admin.privacy.showEmailTitle": "Afficher l'adresse e-mail : ",
"admin.privacy.showFullNameDescription": "Si désactivé, les utilisateurs ne peuvent voir le nom complet des autres utilisateurs (y compris le propriétaire de l'équipe et les adminsitrateurs). Le nom d'utilisateur est affiché à la place du nom de la personne.",
"admin.privacy.showFullNameTitle": "Afficher le nom complet : ",
"admin.purge.button": "Purger tous les caches",
@@ -591,16 +604,16 @@
"admin.reset_password.close": "Quitter",
"admin.reset_password.newPassword": "Nouveau mot de passe",
"admin.reset_password.select": "Sélectionner",
- "admin.reset_password.submit": "Veuillez saisir au moins {chars} caractères.",
+ "admin.reset_password.submit": "Veuillez spécifier au moins {chars} caractères.",
"admin.reset_password.titleReset": "Réinitialiser le mot de passe",
- "admin.reset_password.titleSwitch": "Basculez vers l’addresse électronique / mot de passe de votre compte",
+ "admin.reset_password.titleSwitch": "Basculez le type d'authentification sur le couple l’adresse e-mail / mot de passe",
"admin.saml.assertionConsumerServiceURLDesc": "Entrez https://<votre-url-mattermost>/login/sso/saml. Veillez à saisir HTTP ou HTTPS dans l'URL suivant votre configuration. Ce champ est aussi connu comme étant l'URL d'Assertion Consumer Service.",
"admin.saml.assertionConsumerServiceURLEx": "Ex. : \"https://<your-mattermost-url>/login/sso/saml\"",
"admin.saml.assertionConsumerServiceURLTitle": "URL du service d'identification :",
- "admin.saml.bannerDesc": "User attributes in SAML server, including user deactivation or removal, are updated in Mattermost during user login. Learn more at: <a href=\"https://docs.mattermost.com/deployment/sso-saml.html\">https://docs.mattermost.com/deployment/sso-saml.html</a>",
- "admin.saml.emailAttrDesc": "Attribut du SAML pour le champ \"adresse électronique\" dans Mattermost.",
+ "admin.saml.bannerDesc": "Les attributs de l'utilisateur spécifiés dans le serveur SAML, y compris la désactivation ou la suppression d'utilisateurs, sont mis à jour dans Mattermost lorsque l'utilisateur tente de se connecter. Pour en savoir plus, rendez-vous sur : <a href=\"https://docs.mattermost.com/deployment/sso-saml.html\">https://docs.mattermost.com/deployment/sso-saml.html</a>",
+ "admin.saml.emailAttrDesc": "Attribut du SAML pour le champ \"adresse e-mail\" dans Mattermost.",
"admin.saml.emailAttrEx": "Ex. : \"Email\" ou \"EmailPrincipal\"",
- "admin.saml.emailAttrTitle": "Attribut \"adresse électronique\" :",
+ "admin.saml.emailAttrTitle": "Attribut \"adresse e-mail\" :",
"admin.saml.enableDescription": "Si activé, Mattermost autorise la connexion en utilisant SAML. Consultez la <a href='http://docs.mattermost.com/deployment/sso-saml.html' target='_blank'>documentation</a> pour en apprendre davantage sur la configuration de SAML pour Mattermost.",
"admin.saml.enableTitle": "Activer la connexion avec SAML :",
"admin.saml.encryptDescription": "Si activé, Mattermost déchiffre les commandes SAML chiffrées avec le certificat public de votre serveur d'authentification.",
@@ -681,7 +694,7 @@
"admin.service.googleDescription": "Définissez cette clé pour activer l'affichage des titres pour les aperçus de vidéos YouTube. Sans la clé, les aperçus YouTube seront créés à partir des liens apparaissant des messages ou commentaires mais ils ne montreront pas le titre de la vidéo. Regardez un <a href=\"https://www.youtube.com/watch?v=Im69kzhpR3I\" target=\"_blank\">tutoriel Google Developers</a> pour des instructions sur comment obtenir une clé.",
"admin.service.googleExample": "Ex. : \"7rAh6iwQCkV4cA1Gsg3fgGOXJAQ43QV\"",
"admin.service.googleTitle": "Clé API Google :",
- "admin.service.iconDescription": "Lorsqu'activé, les webhooks, commandes slash et autres intégrations, telles que <a href=\"https://docs.mattermost.com/integrations/zapier.html\" target=\"_blank\">Zapier</a>, seront autorisées à changer la photo du profil à partir duquel elles émettent des messages. Note : Si les intégrations sont également autorisées à redéfinir les noms d'utilisateur, des utilisateurs pourraient effectuer des attaques de phishing en se faisant passer pour d'autres utilisateurs.",
+ "admin.service.iconDescription": "Lorsqu'activé, les webhooks, commandes slash et autres intégrations, telles que <a href=\"https://docs.mattermost.com/integrations/zapier.html\" target=\"_blank\">Zapier</a>, seront autorisées à changer la photo de profil à partir duquel elles émettent des messages. Note : Si les intégrations sont également autorisées à redéfinir les noms d'utilisateur, des utilisateurs pourraient effectuer des attaques de phishing en se faisant passer pour d'autres utilisateurs.",
"admin.service.iconTitle": "Activer les intégrations à redéfinir les images de profil utilisateur :",
"admin.service.insecureTlsDesc": "Si activé, toute requête sortante HTTPS acceptera les certificats non-vérifiés et auto-signés. Par exemple, les requêtes webhook sortantes avec un certificat TLS auto-signé, vers n'importe quel domaine, seront autorisées. Attention, cela rend votre serveur vulnérable aux attaques de type \"man in the middle\".",
"admin.service.insecureTlsTitle": "Autoriser les connexions sortantes non-sécurisées : ",
@@ -695,7 +708,7 @@
"admin.service.mfaDesc": "Si activé, Les utilisateurs auront la possibilité d'ajouter l'authentification multi- facteur à leur compte . Ils auront besoin d'un smartphone et une application d'authentification tels que Google Authenticator .",
"admin.service.mfaTitle": "Activité l’authentification multi-facteurs:",
"admin.service.mobileSessionDays": "Durée de la session sur les applis mobiles (en jours) :",
- "admin.service.mobileSessionDaysDesc": "Le nombre de jours entre la dernière fois qu'un utilisateur a entré ses identifiants et l'expiration de la session de l'utilisateur. Après le changement de ce paramètre, la nouvelle durée de session prendra effet la prochaine fois que les utilisateurs entreront leurs identifiants.",
+ "admin.service.mobileSessionDaysDesc": "Le nombre de jours entre la dernière fois qu'un utilisateur a spécifié ses identifiants et l'expiration de la session de l'utilisateur. Après le changement de ce paramètre, la nouvelle durée de session prendra effet la prochaine fois que les utilisateurs saisiront leurs identifiants.",
"admin.service.outWebhooksDesc": "Si activé, les webhooks sortants seront autorisés. Consultez la <a href='http://docs.mattermost.com/developer/webhooks-outgoing.html' target='_blank'>documentation</a> pour en apprendre davantage.",
"admin.service.outWebhooksTitle": "Activer les webhooks sortants : ",
"admin.service.overrideDescription": "Lorsqu'activé, les webhooks, commandes slash et autres intégrations, telles que <a href=\"https://docs.mattermost.com/integrations/zapier.html\" target=\"_blank\">Zapier</a>, seront autorisées à changer le nom d'utilisateur à partir duquel elles émettent des messages. Note : Si les intégrations sont également autorisées à redéfinir les photos de profil utilisateur, des utilisateurs pourraient effectuer des attaques de phishing en se faisant passer pour d'autres utilisateurs.",
@@ -714,7 +727,7 @@
"admin.service.siteURLDescription": "L'URL, incluant le numéro de port et le protocol, que les utilisateurs utilisent pour accéder à Mattermost. Ce champ peut être laissé vide à moins que vous ne configuriez l'envoi d'email par lots dans <b>Notifications > Email</b>. Lorsque le champ est vide, l'URL est configurée automatiquement sur base du trafic entrant.",
"admin.service.siteURLExample": "Ex. : \"https://mattermost.example.com:1234\"",
"admin.service.ssoSessionDays": "Durée de la session SSO (en jours) :",
- "admin.service.ssoSessionDaysDesc": "Le nombre de jours entre la dernière fois qu'un utilisateur a entré ses identifiants et l'expiration de la session de l'utilisateur. Si la méthode d'authentification est SAML ou GitLab, l'utilisateur pourra être automatiquement connecté à nouveau dans Mattermost s'ils sont déjà connectés dans SAML ou GitLab. Après le changement de ce paramètre, il prendra effet la prochaine fois que les utilisateurs entreront leurs identifiants.",
+ "admin.service.ssoSessionDaysDesc": "Le nombre de jours entre la dernière fois qu'un utilisateur a spécifié ses identifiants et l'expiration de la session de l'utilisateur. Si la méthode d'authentification est SAML ou GitLab, l'utilisateur pourra être automatiquement connecté à nouveau dans Mattermost s'ils sont déjà connectés dans SAML ou GitLab. Après le changement de ce paramètre, il prendra effet la prochaine fois que les utilisateurs saisiront leurs identifiants.",
"admin.service.testingDescription": "(Option de développement) Si activé, la commande \"/loadtest\" est active et charge des données de test. Changer ce paramètre nécessite de redémarrer le serveur.",
"admin.service.testingTitle": "Activer les commandes de test : ",
"admin.service.tlsCertFile": "Fichier du certificat TLS :",
@@ -743,7 +756,7 @@
"admin.sidebar.customization": "Personnalisation",
"admin.sidebar.database": "Base de données",
"admin.sidebar.developer": "Développeur",
- "admin.sidebar.email": "Adresse électronique",
+ "admin.sidebar.email": "Adresse e-mail",
"admin.sidebar.external": "Services externes",
"admin.sidebar.files": "Ficher",
"admin.sidebar.general": "Général",
@@ -804,8 +817,8 @@
"admin.sql.warning": "Attention : Ré-générer cette clé de salage peut provoquer des valeurs vides dans certaines colonnes de la base de données.",
"admin.support.aboutDesc": "Faites un lien vers la page À propos pour plus d'informations sur votre déploiement Mattermost, par exemple son utilisation et son public dans votre organisation. Par défaut, c'est un lien vers la page d'information de Mattermost.",
"admin.support.aboutTitle": "Lien \"À propos\" :",
- "admin.support.emailHelp": "Adresse électronique affichée sur les notifications de courrier électronique et au cours du tutoriel pour permettre aux utilisateurs de poser des questions.",
- "admin.support.emailTitle": "Adresse électronique de support :",
+ "admin.support.emailHelp": "Adresse e-mail qui apparaît sur les notifications envoyées par e-mail mais également au cours du tutoriel pour permettre aux utilisateurs de poser des questions.",
+ "admin.support.emailTitle": "Adresse e-mail de support :",
"admin.support.helpDesc": "Lien vers la documentation utilisateur de l'équipe. Laissez cette valeur par défaut sauf si votre organisation souhaite créer elle-même cette documentation.",
"admin.support.helpTitle": "Lien d'aide :",
"admin.support.noteDescription": "Si vous faites un lien vers un site externe, les URLs doivent commencer par http:// ou https://.",
@@ -834,7 +847,7 @@
"admin.team.maxChannelsExample": "Ex. : \"100\"",
"admin.team.maxChannelsTitle": "Nombre maximum de canaux par équipe :",
"admin.team.maxNotificationsPerChannelDescription": "Nombre utilisateurs maximum au delà duquel les messages comprenant des mentions @all, @here, et @channel n'engendreront plus l'envoi de notifications à cause des performances.",
- "admin.team.maxNotificationsPerChannelExample": "Ex. : \"1000\"",
+ "admin.team.maxNotificationsPerChannelExample": "Ex. : \"10000\"",
"admin.team.maxNotificationsPerChannelTitle": "Nombre maximum de notifications par canal :",
"admin.team.maxUsersDescription": "Nombre maximum d'utilisateurs par équipe, actifs et inactifs.",
"admin.team.maxUsersExample": "Ex. : \"25\"",
@@ -885,7 +898,7 @@
"admin.user_item.mfaYes": "<strong>MFA</strong> : Oui",
"admin.user_item.resetMfa": "supprimer l’identification à facteurs-multiples",
"admin.user_item.resetPwd": "Réinitialiser le mot de passe",
- "admin.user_item.switchToEmail": "Basculer vers votre adresse électronique / mot de passe",
+ "admin.user_item.switchToEmail": "Basculer le type de connexion sur le couple adresse e-mail / mot de passe",
"admin.user_item.sysAdmin": "Administrateur système",
"admin.user_item.teamAdmin": "Administrateur d'équipe",
"admin.webrtc.enableDescription": "Lorsqu'activé, Mattermost autorise les appels vidéo en <strong>tête-à-tête</strong>. Les appels par WebRTC sont disponibles sur Chrome, Firefox et les applications Mattermost de bureau.",
@@ -923,8 +936,10 @@
"analytics.chart.meaningful": "Pas assez de données pour afficher quelque chose de pertinent.",
"analytics.system.activeUsers": "Utilisateurs actifs avec messages",
"analytics.system.channelTypes": "Types de canaux",
- "analytics.system.expiredBanner": "La licence d'Enterprise a expiré le {date}. Vous avez 15 jours à compter de cette date pour renouveler la licence, veuillez contacter <a href='mailto:commercial@mattermost.com'>commercial@mattermost.com </a>.",
- "analytics.system.expiringBanner": "La licence d'Entreprise expire le {date}. Pour renouveler votre licence, veuillez contacter<a href='mailto:commercial@mattermost.com'>commercial@mattermost.com</a>.",
+ "analytics.system.dailyActiveUsers": "Daily Active Users",
+ "analytics.system.expiredBanner": "La licence Entreprise a expiré le {date}. Vous avez 15 jours à compter de cette date pour renouveler la licence, veuillez contacter <a href='mailto:commercial@mattermost.com'>commercial@mattermost.com</a>.",
+ "analytics.system.expiringBanner": "La licence Entreprise expire le {date}. Pour renouveler votre licence, veuillez contacter<a href='mailto:commercial@mattermost.com'>commercial@mattermost.com</a>.",
+ "analytics.system.monthlyActiveUsers": "Monthly Active Users",
"analytics.system.postTypes": "Messages, fichiers et hashtags",
"analytics.system.privateGroups": "Groupes privés",
"analytics.system.publicChannels": "Canaux publics",
@@ -1006,7 +1021,7 @@
"audit_table.userAdded": "{username} ajouté au canal/groupe {channelName}",
"audit_table.userId": "Identifiant de l'utilisateur",
"audit_table.userRemoved": "{username} retiré du canal/groupe {channelName}",
- "audit_table.verified": "Votre adresse électronique est maintenant vérifiée",
+ "audit_table.verified": "Votre adresse e-mail est maintenant vérifiée",
"authorize.access": "Autoriser l'accès à <strong>{appName}</strong> ?",
"authorize.allow": "Autoriser",
"authorize.app": "L'application <strong>{appName}</strong> souhaite accéder et modifier vos informations de base.",
@@ -1021,8 +1036,7 @@
"backstage_sidebar.integrations.oauthApps": "Applications OAuth 2.0",
"backstage_sidebar.integrations.outgoing_webhooks": "Webhooks sortants",
"calling_screen": "Appel",
- "center_panel.recent": "Cliquez ici pour voir les messages les plus récents. ",
- "chanel_header.addMembers": "Ajouter des membres",
+ "center_panel.recent": "Veuillez cliquer ici pour voir les messages récents. ",
"change_url.close": "Fermer",
"change_url.endWithLetter": "Doit se terminer par une lettre ou un nombre",
"change_url.invalidUrl": "URL non valide",
@@ -1033,33 +1047,34 @@
"channelHeader.removeFromFavorites": "Retirer des favoris",
"channel_flow.alreadyExist": "Il existe déjà un canal avec cette URL",
"channel_flow.changeUrlDescription": "Certains caractères ne sont pas autorisés dans les URL et doivent être retirés.",
- "channel_flow.changeUrlTitle": "Changer l'URL de {term}",
+ "channel_flow.changeUrlTitle": "Changer l'URL {term}",
"channel_flow.channel": "Canal",
"channel_flow.create": "Créer {term}",
"channel_flow.group": "Groupe",
"channel_flow.handleTooShort": "L'URL du canal doit comporter au moins 2 caractères alphanumériques minuscules",
"channel_flow.invalidName": "Nom du canal incorrect",
"channel_flow.set_url_title": "Choisir l'URL de {term}",
+ "channel_header.addMembers": "Ajouter des membres",
"channel_header.addToFavorites": "Ajouter aux favoris",
"channel_header.channel": "Canal",
"channel_header.channelHeader": "Éditer l'entête du canal",
"channel_header.delete": "Supprimer {term}",
- "channel_header.flagged": "Messages avec indicateur",
+ "channel_header.flagged": "Messages marqués d'un indicateur",
"channel_header.group": "Groupe",
"channel_header.leave": "Quitter {term}",
"channel_header.manageMembers": "Gérer les membres",
"channel_header.notificationPreferences": "Préférences de notification",
"channel_header.recentMentions": "Mentions récentes",
"channel_header.removeFromFavorites": "Retirer des favoris",
- "channel_header.rename": "Renommer {term}",
- "channel_header.setHeader": "Éditer l'entête {term}",
- "channel_header.setPurpose": "Éditer le descriptif {term}",
+ "channel_header.rename": "Renommer le {term}",
+ "channel_header.setHeader": "Éditer l'entête du {term}",
+ "channel_header.setPurpose": "Éditer le descriptif du {term}",
"channel_header.viewInfo": "Informations",
"channel_header.viewMembers": "Voir les membres",
"channel_header.webrtc.call": "Démarrer un appel vidéo",
"channel_header.webrtc.offline": "L'utilisateur est hors ligne",
"channel_header.webrtc.unavailable": "Passer un nouvel appel n'est pas possible tant que l'appel en cours n'a pas été terminé",
- "channel_info.about": "À propos",
+ "channel_info.about": "À propos de",
"channel_info.close": "Fermer",
"channel_info.header": "En-tête :",
"channel_info.id": "ID : ",
@@ -1068,7 +1083,7 @@
"channel_info.purpose": "Description :",
"channel_info.url": "URL :",
"channel_invite.add": " Ajouter",
- "channel_invite.addNewMembers": "Ajouter de nouveaux membres à ",
+ "channel_invite.addNewMembers": "Ajouter des nouveaux membres à ",
"channel_invite.close": "Fermer",
"channel_loader.connection_error": "Oups... Il semble que votre connexion internet ait un problème...",
"channel_loader.posted": "Posté",
@@ -1079,32 +1094,38 @@
"channel_loader.uploadedFile": " téléchargé un fichier",
"channel_loader.uploadedImage": " téléchargé une image",
"channel_loader.wrote": " a écrit : ",
- "channel_members_modal.addNew": " Ajouter de nouveaux membres",
- "channel_members_modal.close": "Fermer",
- "channel_members_modal.remove": "Supprimer",
- "channel_memebers_modal.members": " Membres",
+ "channel_members_dropdown.channel_admin": "Channel Admin",
+ "channel_members_dropdown.channel_member": "Membres du canal",
+ "channel_members_dropdown.make_channel_admin": "Make Channel Admin",
+ "channel_members_dropdown.make_channel_member": "Make Channel Member",
+ "channel_members_dropdown.remove_from_channel": "Remove From Channel",
+ "channel_members_dropdown.remove_member": "Remove Member",
+ "channel_members_modal.addNew": " Ajouter des nouveaux membres",
+ "channel_members_modal.members": " Membres",
"channel_modal.cancel": "Annuler",
"channel_modal.channel": "Canal",
- "channel_modal.createNew": "Créer un(e) nouveau(elle) ",
- "channel_modal.descriptionHelp": "Décrivez comment ce {term} doit être utilisé.",
+ "channel_modal.createNew": "Créer un nouveau ",
+ "channel_modal.descriptionHelp": "Décrit comment ce {term} doit être utilisé.",
"channel_modal.displayNameError": "Ce champ est obligatoire",
"channel_modal.edit": "Modifier",
"channel_modal.group": "Groupe",
"channel_modal.header": "En-tête",
- "channel_modal.headerHelp": "Voir le text qui apparaitra en en-tête de {term} en regard du nom de {term}. Par exemple, saisissez des liens fréquemment utilisés en tapant [Link Title](http://example.com).",
+ "channel_modal.headerEx": "E.g.: \"[Link Title](http://example.com)\"",
+ "channel_modal.headerHelp": "Définit le texte qui apparaitra en en-tête de {term} en regard du nom du {term}. Par exemple, saisissez des liens fréquemment utilisés en tapant [Lien de titre](http://exemple.com).",
"channel_modal.modalTitle": "Nouveau ",
"channel_modal.name": "Nom",
"channel_modal.nameEx": "Exemple : \"Problèmes\", \"Marketing\", \"Éducation\"",
"channel_modal.optional": "(facultatif)",
- "channel_modal.privateGroup1": "Créer un nouveau groupe privé avec des membres restreints. ",
+ "channel_modal.privateGroup1": "Crée un nouveau groupe privé avec des membres restreints. ",
"channel_modal.privateGroup2": "Créer un groupe privé",
"channel_modal.publicChannel1": "Créer un canal public",
- "channel_modal.publicChannel2": "Créer un canal public que tout le monde peut rejoindre. ",
+ "channel_modal.publicChannel2": "Crée un canal public que tout le monde peut rejoindre. ",
"channel_modal.purpose": "Description",
+ "channel_modal.purposeEx": "E.g.: \"A channel to file bugs and improvements\"",
"channel_notifications.allActivity": "Pour toute l'activité",
"channel_notifications.allUnread": "Pour les messages non-lus",
"channel_notifications.globalDefault": "Par défaut ({notifyLevel})",
- "channel_notifications.markUnread": "Marquer la canal comme non-lu",
+ "channel_notifications.markUnread": "Marquer le canal comme non-lu",
"channel_notifications.never": "Jamais",
"channel_notifications.onlyMentions": "Seulement pour les mentions",
"channel_notifications.override": "Choisir une autre option que \"Par défaut\" remplacera le réglage par défaut. Les notifications sur le bureau sont disponibles sur Firefox, Safari et Chrome.",
@@ -1113,11 +1134,12 @@
"channel_notifications.unreadInfo": "Le nom du canal est en gras dans la barre latérale lorsqu'il y a des messages non-plus. Choisir \"Seulement pour les mentions\" mettra en gras le canal seulement si vous être mentionné.",
"channel_select.placeholder": "--- Sélectionnez un canal ---",
"channel_switch_modal.dm": "(Message privé)",
+ "channel_switch_modal.failed_to_open": "Failed to open channel.",
"channel_switch_modal.help": "Saisissez le nom du canal. Utilisez ↑ ↓ pour parcourir, TAB pour sélectionner, ↵ pour confirmer, ESC pour rejeter",
"channel_switch_modal.not_found": "Aucune correspondance trouvée.",
"channel_switch_modal.submit": "Basculer",
"channel_switch_modal.title": "Changer de canal",
- "claim.account.noEmail": "Aucune adresse électronique indiquée",
+ "claim.account.noEmail": "Aucune adresse e-mail spécifiée",
"claim.email_to_ldap.enterLdapPwd": "Saisissez l'identifiant et le mode de passe de votre compte LDAP",
"claim.email_to_ldap.enterPwd": "Saisissez le mot de passe pour votre compte {site}",
"claim.email_to_ldap.ldapId": "Identifiant LDAP",
@@ -1136,9 +1158,9 @@
"claim.email_to_oauth.ssoNote": "Vous devez déjà avoir un compte {type} valide",
"claim.email_to_oauth.ssoType": "Une fois votre compte associé, vous ne pourrez vous connectez que via le SSO {type}",
"claim.email_to_oauth.switchTo": "Changer de compte pour {uiType}",
- "claim.email_to_oauth.title": "Changer l'adresse électronique/mot de passe pour {uiType}",
+ "claim.email_to_oauth.title": "Changer l'adresse e-mail/mot de passe pour {uiType}",
"claim.ldap_to_email.confirm": "Confirmer le mot de passe",
- "claim.ldap_to_email.email": "Vous devrez utiliser l'adresse électronique {email} pour vous connecter.",
+ "claim.ldap_to_email.email": "Vous devrez utiliser l'adresse e-mail {email} pour vous connecter.",
"claim.ldap_to_email.enterLdapPwd": "Saisissez votre {ldapPassword} pour votre compte {site}",
"claim.ldap_to_email.enterPwd": "Saisissez un nouveau mot de passe pour votre compte",
"claim.ldap_to_email.ldapPasswordError": "Veuillez saisir votre mot de passe LDAP.",
@@ -1146,17 +1168,17 @@
"claim.ldap_to_email.pwd": "Mot de passe",
"claim.ldap_to_email.pwdError": "Veuillez saisir votre mot de passe.",
"claim.ldap_to_email.pwdNotMatch": "Les mots de passe ne correspondent pas.",
- "claim.ldap_to_email.ssoType": "Une fois votre compte modifié, vous ne pourrez plus vous connecter qu'avec votre adresse électronique et votre mot de passe.",
- "claim.ldap_to_email.switchTo": "Basculez votre compte vers adresse électronique / mot de passe",
- "claim.ldap_to_email.title": "Basculer votre compte de LDAP vers adresse électronique / mot de passe",
+ "claim.ldap_to_email.ssoType": "Une fois votre compte modifié, vous ne pourrez plus vous connecter qu'avec votre adresse e-mail et votre mot de passe.",
+ "claim.ldap_to_email.switchTo": "Basculez le type de connexion de votre compte sur le couple adresse e-mail / mot de passe",
+ "claim.ldap_to_email.title": "Basculer le type de connexion de votre compte de LDAP vers le couple adresse e-mail / mot de passe",
"claim.oauth_to_email.confirm": "Confirmez le mot de passe",
- "claim.oauth_to_email.description": "Une fois votre compte modifié, vous ne pourrez plus vous connecter qu'à l'aide de votre adresse électronique et votre mot de passe.",
+ "claim.oauth_to_email.description": "Une fois votre compte modifié, vous ne pourrez plus vous connecter qu'à l'aide de votre adresse e-mail et votre mot de passe.",
"claim.oauth_to_email.enterNewPwd": "Saisissez le mot de passe pour votre compte {site}",
"claim.oauth_to_email.enterPwd": "Veuillez saisir un mot de passe.",
"claim.oauth_to_email.newPwd": "Nouveau mot de passe",
"claim.oauth_to_email.pwdNotMatch": "Le mot de passe ne correspond pas.",
- "claim.oauth_to_email.switchTo": "Basculer de {type} vers adresse électronique et mot de passe",
- "claim.oauth_to_email.title": "Basculer du compte {type} vers l'adresse électronique",
+ "claim.oauth_to_email.switchTo": "Basculer le type de connexion de {type} vers le couple adresse e-mail et mot de passe",
+ "claim.oauth_to_email.title": "Basculer la connexion de type {type} vers l'utilisation d'une adresse e-mail",
"confirm_modal.cancel": "Annuler",
"connecting_screen": "Connexion en cours",
"create_comment.addComment": "Commenter...",
@@ -1169,7 +1191,7 @@
"create_post.error_message": "Votre message est trop long. Nombre de caractères : {length}/{limit}",
"create_post.post": "Article",
"create_post.shortcutsNotSupported": "Les raccourcis clavier ne sont pas pris en charge sur votre appareil.",
- "create_post.tutorialTip": "<h4>Envoyer des messages</h4><p>Entrez votre message ici et tapez <strong>Entrée</strong> pour l'envoyer.</p><p>Cliquez sur le bouton <strong>pièce-jointe</strong> pour télécharger une image ou un fichier.</p>",
+ "create_post.tutorialTip": "<h4>Envoyer des messages</h4><p>Veuillez saisir votre message ici et tapez <strong>Entrée</strong> pour l'envoyer.</p><p>Cliquez sur le bouton <strong>pièce-jointe</strong> pour télécharger une image ou un fichier.</p>",
"create_post.write": "Écrire un message...",
"create_team.agreement": "En créant votre compte et en utilisant {siteName}, vous acceptez nos <a href={TermsOfServiceLink}>Conditions Générales d'Utilisation</a> et notre <a href={PrivacyPolicyLink}>Politique des données personnelles</a>. Si vous le les acceptez pas, vous ne pouvez pas utiliser {siteName}.",
"create_team.display_name.back": "Retour à l’étape précedente",
@@ -1209,7 +1231,7 @@
"edit_channel_header_modal.description": "Modifiez le texte affiché à côté du nom du canal dans l'en-tête du canal.",
"edit_channel_header_modal.error": "L'en-tête du canal est trop long, saisissez un texte plus court",
"edit_channel_header_modal.save": "Enregistrer",
- "edit_channel_header_modal.title": "Modifier l'en-tête pour {channel}",
+ "edit_channel_header_modal.title": "Modifier l'en-tête de {channel}",
"edit_channel_header_modal.title_dm": "Modifier l'en-tête",
"edit_channel_purpose_modal.body": "Décrit de quelle manière ce {type} devrait être utilisé. Ce texte apparaît dans la liste des canaux dans le menu \"Suite...\" et guide les personnes pour décider si elles devraient le rejoindre ou non.",
"edit_channel_purpose_modal.cancel": "Annuler",
@@ -1224,7 +1246,7 @@
"edit_post.edit": "Modifier {title}",
"edit_post.editPost": "Modifier le message...",
"edit_post.save": "Enregistrer",
- "email_signup.address": "Adresse électronique",
+ "email_signup.address": "Adresse e-mail",
"email_signup.createTeam": "Créer une équipe",
"email_signup.emailError": "Veuillez spécifier une adresse e-mail valide.",
"email_signup.find": "Rechercher mes équipes",
@@ -1233,9 +1255,9 @@
"email_verify.notVerifiedBody": "Veuillez vérifier votre adresse e-mail dans l'attente de la réception d'un e-mail de vérification.",
"email_verify.resend": "Renvoyer l'e-mail",
"email_verify.sent": "L'e-mail de vérification a été envoyé.",
- "email_verify.verified": "L'adresse électronique de {siteName} a été vérifiée",
- "email_verify.verifiedBody": "<p>Votre adresse électronique a été vérifiée ! Cliquez <a href={url}>ici</a> pour vous connecter.</p>",
- "email_verify.verifyFailed": "Impossible de vérifier votre adresse électronique.",
+ "email_verify.verified": "L'adresse e-mail de {siteName} a été vérifiée",
+ "email_verify.verifiedBody": "<p>Votre adresse e-mail a été vérifiée ! Veuillez cliquer <a href={url}>ici</a> pour vous connecter.</p>",
+ "email_verify.verifyFailed": "Impossible de vérifier votre adresse e-mail.",
"emoji_list.actions": "Actions",
"emoji_list.add": "Ajouter un emoji personnalisé",
"emoji_list.creator": "Auteur",
@@ -1253,9 +1275,9 @@
"error.not_found.title": "Page non trouvée",
"error.not_supported.message": "La navigation privée n'est pas prise en charge.",
"error.not_supported.title": "Le navigateur n'est pas pris en charge",
- "error_bar.expired": "La licence d'Entreprise a expiré. Vous disposez de 15 jours à compter de l'expiration pour renouveler la licence, veuillez contacter commercial@mattermost.com pour plus d'informations",
- "error_bar.expiring": "La licence d'Entreprise expire le {date}. Pour renouveler votre licence, veuillez contacter commercial@mattermost.com",
- "error_bar.past_grace": "La licence d'Entreprise a expiré, veuillez contacter votre administrateur système pour plus d'informations",
+ "error_bar.expired": "La licence Entreprise a expiré. Vous disposez de 15 jours à compter de l'expiration pour renouveler la licence, veuillez contacter commercial@mattermost.com pour plus d'informations",
+ "error_bar.expiring": "La licence Entreprise expire le {date}. Pour renouveler votre licence, veuillez contacter commercial@mattermost.com",
+ "error_bar.past_grace": "La licence Entreprise a expiré, veuillez contacter votre administrateur système pour plus d'informations",
"error_bar.preview_mode": "Mode découverte : Les notifications par e-mail ne sont pas configurées",
"file_attachment.download": "Télécharger",
"file_info_preview.size": "Taille ",
@@ -1276,24 +1298,23 @@
"filtered_user_list.searchButton": "Rechercher",
"filtered_user_list.show": "Filtre :",
"filtered_user_list.team_only": "Membre de l’équipe",
- "find_team.email": "Adresse électronique",
+ "find_team.email": "Adresse e-mail",
"find_team.findDescription": "Un e-mail a été envoyé avec les liens vers les équipes dont vous êtes membre.",
"find_team.findTitle": "Trouvez votre équipe",
"find_team.getLinks": "Obtenez par e-mail des liens vers les équipes dont vous êtes membre.",
"find_team.placeholder": "vous@domaine.com",
"find_team.send": "Envoyer",
- "find_team.submitError": "Veuillez entrer une adresse électronique valide",
- "flag_post.flag": "Ajoutez un indicateur pour poursuivre",
+ "find_team.submitError": "Veuillez spécifier une adresse e-mail valide",
+ "flag_post.flag": "Marquez le message d'un indicateur pour le suivi",
"flag_post.unflag": "Supprimer l'indicateur",
+ "general_tab.chooseDescription": "Choisissez un nom pour votre équipe",
"general_tab.chooseName": "Choisissez un nom pour votre équipe",
- "general_tab.codeDesc": "Cliquez sur \"Modifier\" pour réinitialiser le code d'invitation",
+ "general_tab.codeDesc": "Veuillez cliquer sur 'Modifier' pour réinitialiser le code d'invitation",
"general_tab.codeLongDesc": "Le code d'invitation est utilisé dans l'URL contenant l'invitation à votre équipe créé depuis le menu principal grâce à <strong>Obtenir un lien d’invitation d’équipe</strong>. Regénérer cette clé rend toutes les invitations déjà envoyées invalide.",
"general_tab.codeTitle": "Code d'invitation",
- "general_tab.dirDisabled": "L'annuaire d'équipe a été désactivé. Veuillez demander à un administrateur système d'activer l'annuaire d'équipe dans la console système.",
- "general_tab.dirOff": "L'annuaire des équipes est désactivé sur ce site. ",
- "general_tab.emptyDescription": "Cliquez sur 'Modifier' pour ajouter une description d'équipe.",
+ "general_tab.emptyDescription": "Veuillez cliquer sur 'Modifier' pour ajouter une description d'équipe.",
+ "general_tab.getTeamInviteLink": "Créer une d'invitation",
"general_tab.includeDirDesc": "Inclure cette équipe affichera le nom de l'équipe dans l'annuaire sur la page d'accueil, ainsi qu'un lien pour rejoindre cette équipe.",
- "general_tab.includeDirTitle": "Afficher cette équipe dans l'annuaire",
"general_tab.no": "Non",
"general_tab.openInviteDesc": "Lorsque autorisé, un lien vers cette équipe sera inclus sur la page d'accueil permettant à quiconque avec un compte de rejoindre cette équipe.",
"general_tab.openInviteTitle": "Permettre à n'importe quel utilisateur disposant d'un compte de rejoindre cette équipe",
@@ -1315,7 +1336,7 @@
"get_app.mattermostInc": "Mattermost, Inc",
"get_app.openMattermost": "Ouvrir Mattermost",
"get_link.clipboard": " Lien copié",
- "get_link.close": "Quitter",
+ "get_link.close": "Fermer",
"get_link.copy": "Copier l'URL",
"get_post_link_modal.help": "Le lien ci-dessous permet aux utilisateurs de voir votre message.",
"get_post_link_modal.title": "Copier le lien permanent",
@@ -1479,12 +1500,12 @@
"integrations.outgoingWebhook.description": "Les webhooks sortants permettent des intégrations externes afin de recevoir et de répondre aux messages",
"integrations.outgoingWebhook.title": "Webhooks sortants",
"integrations.successful": "Installation réussie",
- "intro_messages.DM": "Vous êtes au début de votre historique de messages avec {teammate}.<br />Les messages privés et les fichiers partagés ici ne sont visibles pour personne d'autre.",
+ "intro_messages.DM": "Vous êtes au début de votre historique de messages avec {teammate}.<br />Les messages privés et les fichiers partagés ici ne sont pas visibles par les autres utilisateurs.",
"intro_messages.anyMember": " Tout membre peut rejoindre et lire ce canal.",
"intro_messages.beginning": "Début de {name}",
"intro_messages.channel": "canal",
"intro_messages.creator": "Ceci est le début de {type} {name}, créé par {creator} le {date}.",
- "intro_messages.default": "<h4 class='channel-intro__title'>Bienvenue sur {display_name}</h4><p class='channel-intro__content'><strong>Bienvenue sur {display_name}!</strong><br/><br/>Ceci est le premier canal que les membres de votre équipe verront lors de leur inscription. Utilisez-le pour poster des informations que tout le monde doit voir.</p>",
+ "intro_messages.default": "<h4 class='channel-intro__title'>Bienvenue sur {display_name}</h4><p class='channel-intro__content'><strong>Bienvenue sur {display_name}!</strong><br/><br/>Ceci est le premier canal que les membres de votre équipe verront lors de leur inscription. Utilisez-le pour poster des informations que tout le monde devrait voir.</p>",
"intro_messages.group": "Groupe privé",
"intro_messages.invite": "Inviter d'autres membres sur ce {type}",
"intro_messages.inviteOthers": "Inviter d'autres membres dans cette équipe",
@@ -1493,13 +1514,13 @@
"intro_messages.onlyInvited": " Seuls les membres invités peuvent voir ce groupe privé.",
"intro_messages.purpose": " L'objectif de {type} est : {purpose}.",
"intro_messages.setHeader": "Définir l'en-tête",
- "intro_messages.teammate": "Vous êtes au début de votre historique de messages avec cette personne. Les messages privés et les fichiers partagés ici ne sont visibles pour personne d'autre.",
+ "intro_messages.teammate": "Vous êtes au début de votre historique de messages avec cet utilisateur. Les messages privés et les fichiers partagés ici ne sont pas visibles par les autres utilisateurs.",
"invite_member.addAnother": "Ajouter un autre",
"invite_member.autoJoin": "Les membres automatiquement invités rejoignent le canal <strong>{channel}</strong>.",
"invite_member.cancel": "Annuler",
"invite_member.content": "Les e-mails sont désactivés pour votre équipe et ne peuvent pas être envoyés. Contactez votre administrateur système pour activer l'envoi d'e-mails et l'envoi d'invitations par e-mail.",
"invite_member.disabled": "La création d'utilisateurs est désactivée pour votre équipe. Veuillez contacter votre administrateur système.",
- "invite_member.emailError": "Veuillez saisir une adresse électronique valide",
+ "invite_member.emailError": "Veuillez saisir une adresse e-mail valide",
"invite_member.firstname": "Prénom",
"invite_member.inviteLink": "Lien d'invitation à l'équipe",
"invite_member.lastname": "Nom de famille",
@@ -1515,7 +1536,7 @@
"ldap_signup.ldap": "Créer une nouvelle équipe avec un compte LDAP",
"ldap_signup.length_error": "Le nom doit contenir de 3 à 15 caractères",
"ldap_signup.teamName": "Entrez le nom de votre nouvelle équipe",
- "ldap_signup.team_error": "Saisissez le nom de votre équipe",
+ "ldap_signup.team_error": "Veuillez saisir le nom de votre équipe",
"leave_team_modal.desc": "Vous serez retiré de toutes les chaînes publiques et privées. Si l'équipe est privé, vous ne serez pas en mesure de rejoindre l'équipe. Êtes-vous sûr?",
"leave_team_modal.no": "Non",
"leave_team_modal.title": "Quitter l'équipe ?",
@@ -1525,7 +1546,7 @@
"login.create": "Créer maintenant",
"login.createTeam": "Créer une nouvelle équipe",
"login.createTeamAdminOnly": "Cette option n'est disponible que pour les administrateurs système, et ne s'affiche pas pour les autres utilisateurs.",
- "login.email": "Adresse électronique",
+ "login.email": "Adresse e-mail",
"login.find": "Trouver vos autres équipes",
"login.forgot": "Mot de passe perdu",
"login.gitlab": "GitLab",
@@ -1534,15 +1555,15 @@
"login.ldapUsername": "Nom d’utilisateur LDAP",
"login.ldapUsernameLower": "Nom d’utilisateur LDAP",
"login.noAccount": "Pas de compte utilisateur ? ",
- "login.noEmail": "Veuillez saisir votre adresse électronique.",
- "login.noEmailLdapUsername": "Veuillez saisir votre adresse électronique ou {ldapUsername}",
- "login.noEmailUsername": "Veuillez saisir votre adresse électronique ou votre nom d'utilisateur",
- "login.noEmailUsernameLdapUsername": "Veuillez entrer votre adresse électronique, votre nom d'utilisateur ou {ldapUsername}",
+ "login.noEmail": "Veuillez saisir votre adresse e-mail.",
+ "login.noEmailLdapUsername": "Veuillez saisir votre adresse e-mail ou {ldapUsername}",
+ "login.noEmailUsername": "Veuillez saisir votre adresse e-mail ou votre nom d'utilisateur",
+ "login.noEmailUsernameLdapUsername": "Veuillez spécifier votre adresse e-mail, votre nom d'utilisateur ou {ldapUsername}",
"login.noLdapUsername": "Veuillez saisir votre {ldapUsername}",
"login.noMethods": "Aucune méthode configurée pour s'inscrive, veuillez contacter votre administrateur système.",
"login.noPassword": "Veuillez saisir votre mot de passe",
- "login.noUsername": "Veuillez entrer votre nom d'utilisateur",
- "login.noUsernameLdapUsername": "Saisissez votre nom d'utilisateur ou {ldapUsername}",
+ "login.noUsername": "Veuillez spécifier votre nom d'utilisateur",
+ "login.noUsernameLdapUsername": "Veuillez saisir votre nom d'utilisateur ou {ldapUsername}",
"login.office365": "Office 365",
"login.on": "activé {siteName}",
"login.or": "ou",
@@ -1550,25 +1571,28 @@
"login.passwordChanged": " Mot de passe mis a jour avec succès",
"login.session_expired": " Votre session a expiré. Merci de vous reconnecter.",
"login.signIn": "Connexion",
+ "login.signInLoading": "Signing in...",
"login.signInWith": "Se connecter avec:",
"login.userNotFound": "Nous ne trouvons aucun compte correspondants a vos identifiants de connexions.",
"login.username": "Nom d'utilisateur",
- "login.verified": " Adresse électronique vérifiée",
- "login_mfa.enterToken": "Pour compléter le processus de connexion , entrer un token de la authentificateur de votre smartphone",
+ "login.verified": " Adresse e-mail vérifiée",
+ "login_mfa.enterToken": "Pour terminer le processus de connexion, veuillez spécifier le jeton apparaissant dans l'application d'authentification de votre smartphone.",
"login_mfa.submit": "Envoyer",
"login_mfa.token": "MFA Token",
- "login_mfa.tokenReq": "Entrer un MFA Token",
+ "login_mfa.tokenReq": "Veuillez spécifier un jeton d'authentification multi-facteurs (MFA)",
"member_item.makeAdmin": "Passer Administrateur",
"member_item.member": "Membre",
"member_list.noUsersAdd": "Aucun utilisateur à ajouter.",
- "members_popover.msg": "Message",
+ "members_popover.manageMembers": "Gérer les membres",
+ "members_popover.msg": "Envoyer un message",
"members_popover.title": "Membres",
+ "members_popover.viewMembers": "Voir les membres",
"mfa.confirm.complete": "<strong>Installation terminée !</strong>",
"mfa.confirm.okay": "OK",
- "mfa.confirm.secure": "Votre compte est maintenant sécurisé. La prochaine fois que vous vous connectez, vous serez invité à entrer un code à partir de l'application Google Authenticator sur votre téléphone.",
+ "mfa.confirm.secure": "Votre compte est maintenant sécurisé. La prochaine fois que vous vous connectez, vous serez invité à spécifier un code à partir de l'application Google Authenticator sur votre téléphone.",
"mfa.setup.badCode": "Code non valide. Si ce problème persiste, contactez votre Administrateur Système.",
"mfa.setup.code": "Code MFA",
- "mfa.setup.codeError": "Veuillez entrer le code de Google Authenticator.",
+ "mfa.setup.codeError": "Veuillez saisir le code de Google Authenticator.",
"mfa.setup.required": "<strong>L'authentification multi-facteurs est requise sur {siteName}.</strong>",
"mfa.setup.save": "Enregistrer",
"mfa.setup.secret": "Clé secrète : {secret}",
@@ -1587,9 +1611,9 @@
"mobile.routes.selectTeam": "Choisir une équipe",
"more_channels.close": "Fermer",
"more_channels.create": "Créer un nouveau canal",
- "more_channels.createClick": "Cliquez sur \"Créer un nouveau canal\" pour en créer un",
+ "more_channels.createClick": "Veuillez cliquer sur \"Créer un nouveau canal\" pour en créer un nouveau",
"more_channels.join": "Rejoindre",
- "more_channels.noMore": "Plus de canal à rejoindre",
+ "more_channels.noMore": "Il n'y a plus d'autre canal que vous pouvez rejoindre",
"more_channels.title": "Plus de canaux",
"more_direct_channels.close": "Fermer",
"more_direct_channels.message": "Message",
@@ -1610,10 +1634,10 @@
"navbar.toggle1": "Basculer la barre latérale",
"navbar.toggle2": "Basculer la barre latérale",
"navbar.viewInfo": "Afficher Informations",
- "navbar_dropdown.about": "À propos de Mattermost",
+ "navbar_dropdown.about": "À propos",
"navbar_dropdown.accountSettings": "Paramètres du compte",
"navbar_dropdown.console": "Console système",
- "navbar_dropdown.create": "Créer une nouvelle équipe",
+ "navbar_dropdown.create": "Créer une équipe",
"navbar_dropdown.emoji": "Emoji personnalisés",
"navbar_dropdown.help": "Aide",
"navbar_dropdown.integrations": "Intégrations",
@@ -1626,21 +1650,21 @@
"navbar_dropdown.report": "Signaler un problème",
"navbar_dropdown.switchTeam": "Basculer sur {team}",
"navbar_dropdown.switchTo": "Basculer vers ",
- "navbar_dropdown.teamLink": "Obtenir un lien d'invitation d'équipe",
+ "navbar_dropdown.teamLink": "Créer une d'invitation",
"navbar_dropdown.teamSettings": "Configuration de l'équipe",
"navbar_dropdown.viewMembers": "Voir les membres",
"notification.dm": "Message privé",
"passwordRequirements": "Pré-requis du mot de passe :",
"password_form.change": "Modifier mon mot de passe",
- "password_form.click": "Cliquez <a href={url}>ici</a> pour vous connecter.",
+ "password_form.click": "Veuillez cliquer <a href={url}>ici</a> pour vous connecter.",
"password_form.enter": "Saisissez un nouveau mot de passe pour votre compte {siteName}.",
"password_form.error": "Veuillez saisir au moins {chars} caractères.",
"password_form.pwd": "Mot de passe",
"password_form.title": "Réinitialisation mot de passe",
"password_form.update": "Votre mot de passe a été correctement mis à jour.",
"password_send.checkInbox": "Veuillez contrôler votre boite de réception.",
- "password_send.description": "Pour réinitialiser votre mot de passe, saisissez l'adresse électronique que vous avez utilisé pour vous inscrire.",
- "password_send.email": "Adresse électronique",
+ "password_send.description": "Pour réinitialiser votre mot de passe, veuillez saisir l'adresse e-mail que vous avez utilisée pour vous inscrire",
+ "password_send.email": "Adresse e-mail",
"password_send.error": "Veuillez spécifier une adresse e-mail valide.",
"password_send.link": "Si le compte existe, une demande de réinitialisation du mot de passe sera envoyée à l'adresse e-mail : <br/><b>{email}</b><br/><br/>",
"password_send.reset": "Réinitialiser mon mot de passe",
@@ -1665,6 +1689,7 @@
"post_info.mobile.unflag": "Supprimer l'indicateur",
"post_info.permalink": "Lien permanent",
"post_info.reply": "Répondre",
+ "post_message_view.edited": "(edited)",
"posts_view.loadMore": "Charger plus de messages",
"posts_view.newMsg": "Nouveaux messages",
"posts_view.newMsgBelow": "Nouveau {count, plural, one {message} other {messages}} dessous",
@@ -1702,7 +1727,7 @@
"rhs_comment.mobile.unflag": "Supprimer l'indicateur",
"rhs_comment.permalink": "Lien permanent",
"rhs_header.backToCallTooltip": "Retour à l'appel",
- "rhs_header.backToFlaggedTooltip": "Retourner aux messages avec indicateur",
+ "rhs_header.backToFlaggedTooltip": "Retourner aux messages marqués d'un indicateur",
"rhs_header.backToResultsTooltip": "Retour aux résultats",
"rhs_header.closeSidebarTooltip": "Fermer la barre latérale",
"rhs_header.closeTooltip": "Fermer la barre latérale",
@@ -1718,19 +1743,19 @@
"rhs_root.permalink": "Lien permanent",
"search_bar.cancel": "Annuler",
"search_bar.search": "Rechercher",
- "search_bar.usage": "<h4>Options de recherche</h4><ul><li><span>Utilisez </span><b>\"des guillemets\"</b><span> pour rechercher des phrases</span></li><li><span>Utilisez </span><b>from:</b><span> pour rechercher les messages d'utilisateur spécifiques et </span><b>in:</b><span> pour rechercher les messages sur des canaux spécifiques</span></li></ul>",
+ "search_bar.usage": "<h4>Options de recherche</h4><ul><li><span>Utilisez </span><b>\"des guillemets\"</b><span> pour rechercher des phrases</span></li><li><span>Utilisez </span><b>from:</b><span> pour rechercher des messages d'utilisateur spécifiques et </span><b>in:</b><span> pour rechercher des messages sur des canaux spécifiques</span></li></ul>",
"search_header.results": "Résultats de la recherche",
"search_header.title2": "Mentions récentes",
- "search_header.title3": "Postes avec indicateur",
+ "search_header.title3": "Messages marqués d'un indicateur",
"search_item.direct": "Message privé (avec {username})",
"search_item.jump": "Lien (jump)",
"search_results.because": "<ul><li>Pour rechercher une partie d'un mot (ex. rechercher \"réa\" en souhaitant \"réagir\" ou \"réaction\"), ajoutez un * au terme recherché</li><li>En raison du nombre de résultats, les recherches sur deux lettres ou sur les mots communs tels que \"ce\", \"un\" et \"est\" n'apparaîtront pas dans les résultats de la recherche</li></ul>",
"search_results.noResults": "Aucun résultat. Recommencer ?",
- "search_results.usage": "<ul><li>Utilisez <b>\"des guillemets\"</b> pour rechercher des phrases</li><li>Utilisez <b>from:</b> pour rechercher les messages d'utilisateur spécifiques et <b>in:</b> pour rechercher les messages sur des canaux spécifiques</li></ul>",
- "search_results.usageFlag1": "Vous n'avez pas encore ajouté des indicateurs aux messages",
- "search_results.usageFlag2": "Vous pouvez ajouter un indicateur aux messages et aux commentaires en cliquant sur ",
+ "search_results.usage": "<ul><li>Utilisez <b>\"des guillemets\"</b> pour rechercher des phrases</li><li>Utilisez <b>from:</b> pour rechercher des messages d'utilisateurs spécifiques et <b>in:</b> pour rechercher des messages sur des canaux spécifiques</li></ul>",
+ "search_results.usageFlag1": "Vous n'avez pas encore de messages marqués d'un indicateur.",
+ "search_results.usageFlag2": "Vous pouvez marquer un message ou un commentaire en cliquant sur ",
"search_results.usageFlag3": " l'icône à côté de l'horodateur.",
- "search_results.usageFlag4": "Les indicateurs sont un moyen de marquer des messages de suivi. Vos indicateurs sont personnels et ne peuvent pas être vus par d'autres utilisateurs.",
+ "search_results.usageFlag4": "Marquer un message est un bon moyen d'assurer le suivi. Marquer un message est personnel et ne peut être vu par les autres utilisateurs.",
"setting_item_max.cancel": "Annuler",
"setting_item_max.save": "Enregistrer",
"setting_item_min.edit": "Modifier",
@@ -1751,26 +1776,26 @@
"sidebar.otherMembers": "En dehors de l’équipe",
"sidebar.pg": "Groupes privés",
"sidebar.removeList": "Retirer de la liste",
- "sidebar.tutorialScreen1": "<h4>Canaux</h4><p><strong>Les canaux</strong> organisent les conversations en sujets distincts. Ils sont ouverts à tout le monde dans votre équipe. Pour envoyer des messages privés, utilisez <strong>Messages Privés</strong> pour une personne ou <strong>Groupes Privés</strong> pour plusieurs personnes.</p>",
- "sidebar.tutorialScreen2": "<h4>Canaux \"{townsquare}\" et \"{offtopic}\"</h4><p>Voici deux canaux publics pour commencer :</p><p><strong>{townsquare}</strong> (\"centre-ville\") est l'endroit idéal pour communiquer avec toute l'équipe. Tous les membres de votre équipe sont membres de ce canal.</p><p><strong>{offtopic}</strong> (\"hors-sujet\") est l'endroit pour se détendre et parler d'autre chose que de travail. Vous et votre équipe décidez des autres canaux à créer.</p>",
+ "sidebar.tutorialScreen1": "<h4>Canaux</h4><p><strong>Les canaux</strong> organisent les conversations en sujets distincts. Ils sont ouverts à tous les utilisateurs de votre équipe. Pour envoyer des messages privés, utilisez <strong>Messages privés</strong> pour une personne ou <strong>Groupes privés</strong> pour plusieurs personnes.</p>",
+ "sidebar.tutorialScreen2": "<h4>Canaux \"{townsquare}\" et \"{offtopic}\"</h4><p>Voici deux canaux publics pour commencer :</p><p><strong>{townsquare}</strong> est l'endroit idéal pour communiquer avec toute l'équipe. Tous les membres de votre équipe sont membres de ce canal.</p><p><strong>{offtopic}</strong> (est l'endroit pour se détendre et parler d'autre chose que du travail. Vous et votre équipe décidez des autres canaux à créer.</p>",
"sidebar.tutorialScreen3": "<h4>Créer et rejoindre des canaux</h4><p>Cliquez sur <strong>\"Plus...\"</strong> pour créer un nouveau canal ou rejoindre un canal existant.</p><p>Vous pouvez aussi créer un nouveau canal ou un groupe privé en cliquant sur le symbole <strong>\"+\"</strong> à côté du nom du canal ou de l'en-tête du groupe privé.</p>",
"sidebar.unreadAbove": "Message(s) non-lu(s) ci-dessus",
"sidebar.unreadBelow": "Message(s) non-lu(s) ci-dessous",
- "sidebar_header.tutorial": "<h4>Menu principal</h4><p>Le <strong>Menu Principal</strong> est l'endroit où vous pouvez <strong>inviter de nouveaux membres</strong>, accéder aux <strong>paramètres de votre compte</strong> et configurer les <strong>couleurs de votre thème</strong>.</p><p>Les administrateurs d'équipe peuvent aussi accéder aux <strong>paramètres de l'équipe</strong>.</p><p>Les administrateurs système trouveront la <strong>console système</strong> pour administrer tout le site.</p>",
+ "sidebar_header.tutorial": "<h4>Menu principal</h4><p>Le <strong>Menu Principal</strong> est l'endroit où vous pouvez <strong>inviter des nouveaux membres</strong>, accéder aux <strong>paramètres de votre compte</strong> et configurer les <strong>couleurs de votre thème</strong>.</p><p>Les administrateurs d'équipe peuvent aussi accéder aux <strong>paramètres de l'équipe</strong>.</p><p>Les administrateurs système trouveront la <strong>console système</strong> pour administrer tout le site.</p>",
"sidebar_right_menu.accountSettings": "Paramètres du compte",
"sidebar_right_menu.console": "Console système",
- "sidebar_right_menu.flagged": "Postes avec indicateur",
+ "sidebar_right_menu.flagged": "Messages marqués d'un indicateur",
"sidebar_right_menu.help": "Aide",
- "sidebar_right_menu.inviteNew": "Inviter un nouveau membre",
+ "sidebar_right_menu.inviteNew": "Inviter un membre",
"sidebar_right_menu.logout": "Se déconnecter",
"sidebar_right_menu.manageMembers": "Gérer les membres",
- "sidebar_right_menu.nativeApps": "Télécharger les applications",
+ "sidebar_right_menu.nativeApps": "Télécharger les apps",
"sidebar_right_menu.recentMentions": "Mentions récentes",
"sidebar_right_menu.report": "Signaler un problème",
- "sidebar_right_menu.teamLink": "Obtenir un lien d'invitation d'équipe",
+ "sidebar_right_menu.teamLink": "Créer un lien d'invitation d'équipe",
"sidebar_right_menu.teamSettings": "Configuration de l'équipe",
"sidebar_right_menu.viewMembers": "Voir les membres",
- "signup.email": "Adresse électronique et mot de passe",
+ "signup.email": "Adresse e-mail et mot de passe",
"signup.gitlab": "Authentification unifiée avec GitLab",
"signup.google": "Compte Google",
"signup.ldap": "Informations d'authentification AD/LDAP",
@@ -1792,8 +1817,8 @@
"signup_user_completed.choosePwd": "Choisissez votre mot de passe",
"signup_user_completed.chooseUser": "Choisissez votre nom d'utilisateur",
"signup_user_completed.create": "Créer un compte",
- "signup_user_completed.emailHelp": "Une adresse électronique est obligatoire pour s'inscrire",
- "signup_user_completed.emailIs": "Votre adresse électronique est <strong>{email}</strong>. Vous utiliserez cette adresse pour vous connecter à {siteName}.",
+ "signup_user_completed.emailHelp": "Une adresse e-mail est obligatoire pour s'inscrire",
+ "signup_user_completed.emailIs": "Votre adresse e-mail est <strong>{email}</strong>. Vous utiliserez cette adresse pour vous connecter à {siteName}.",
"signup_user_completed.expired": "Vous avez déjà utilisé cette invitation pour vous inscrire, ou bien l'invitation a expiré.",
"signup_user_completed.gitlab": "avec GitLab",
"signup_user_completed.google": "avec Google",
@@ -1805,23 +1830,23 @@
"signup_user_completed.office365": "avec Office 365",
"signup_user_completed.onSite": "activé {siteName}",
"signup_user_completed.or": "ou",
- "signup_user_completed.passwordLength": "Veuillez entrer au moins {min} caractères",
+ "signup_user_completed.passwordLength": "Veuillez spécifier au moins {min} caractères",
"signup_user_completed.required": "Champ obligatoire",
"signup_user_completed.reserved": "Ce nom d'utilisateur est réservé, veuillez en choisir un autre.",
- "signup_user_completed.signIn": "Cliquez ici pour vous connecter.",
+ "signup_user_completed.signIn": "Veuillez cliquer ici pour vous connecter.",
"signup_user_completed.userHelp": "Les noms d'utilisateurs doivent commencer par une lettre et contenir entre {min} et {max} caractères composés de chiffres, lettres minuscules et des symboles '.', '-' et '_'",
"signup_user_completed.usernameLength": "Les noms d'utilisateurs doivent commencer par une lettre et contenir entre {min} et {max} caractères composés de chiffres, lettres minuscules et des symboles '.', '-' et '_'",
- "signup_user_completed.validEmail": "Veuillez entrer une adresse électronique valide",
+ "signup_user_completed.validEmail": "Veuillez spécifier une adresse e-mail valide",
"signup_user_completed.welcome": "Bienvenue sur :",
- "signup_user_completed.whatis": "Quelle est votre adresse électronique ?",
+ "signup_user_completed.whatis": "Quelle est votre adresse e-mail ?",
"signup_user_completed.withLdap": "Avec vos information d’identifications LDAP",
"sso_signup.find": "Trouver mes équipes",
"sso_signup.gitlab": "Créer une équipe avec un compte GitLab",
"sso_signup.google": "Créer une équipe avec un compte Google Apps",
"sso_signup.length_error": "Le nom doit contenir de 3 à 15 caractères",
"sso_signup.teamName": "Entrez le nom de votre nouvelle équipe",
- "sso_signup.team_error": "Saisissez le nom de votre équipe",
- "suggestion.mention.all": "Avertit tout le monde dans le canal, utilisez dans {townsquare} pour notifier toute l'équipe",
+ "sso_signup.team_error": "Veuillez spécifier le nom de votre équipe",
+ "suggestion.mention.all": "Avertit tout le monde dans le canal, utilisez-le dans {townsquare} pour notifier toute l'équipe",
"suggestion.mention.channel": "Notifier tout le monde dans le canal",
"suggestion.mention.channels": "Mes canaux",
"suggestion.mention.here": "Notifier toutes les personnes connectées dans ce canal",
@@ -1849,7 +1874,7 @@
"team_import_tab.importing": " Import...",
"team_import_tab.successful": " Import réussi : ",
"team_import_tab.summary": "Afficher le récapitulatif",
- "team_member_modal.close": "Quitter",
+ "team_member_modal.close": "Fermer",
"team_member_modal.members": "Membres de {team}",
"team_members_dropdown.confirmDemoteDescription": "Si vous vous retirez le rôle d'administrateur et qu'il n'y a aucun autre administrateur désigné, vous devrez en désigner un en utilisant les outils en ligne de commande depuis un terminal sur le serveur.",
"team_members_dropdown.confirmDemoteRoleTitle": "Confirmez le retrait de votre rôle d'administrateur",
@@ -1879,15 +1904,15 @@
"textbox.quote": ">citation",
"textbox.strike": "barré",
"tutorial_intro.allSet": "C'est parti !",
- "tutorial_intro.end": "Cliquez sur \"Suivant\" pour entrer dans {channel}. C'est le premier canal que les membres voient quand ils s'inscrivent. Utilisez-le pour poster des messages que tout le monde doit lire en premier.",
+ "tutorial_intro.end": "Veuillez cliquer sur \"Suivant\" pour entrer dans {channel}. Il s'agit du premier canal que les membres voient lorsqu'ils s'inscrivent. Utilisez-le pour poster des messages que tout le monde devrait lire en premier.",
"tutorial_intro.invite": "Inviter des membres",
- "tutorial_intro.mobileApps": "Installer les applications pour {link} pour un accès facile et des notifications en mobilité.",
+ "tutorial_intro.mobileApps": "Installez les applications pour {link} pour un accès facile et ainsi recevoir des notifications même lorsque vous êtes en déplacement.",
"tutorial_intro.mobileAppsLinkText": "PC, Mac, iOS et Android",
"tutorial_intro.next": "Suivant",
"tutorial_intro.screenOne": "<h3>Bienvenue dans :</h3><h1>Mattermost</h1><p>Toute la communication de votre équipe à un seul endroit, consultable instantanément et disponible partout.</p><p>Gardez votre équipe soudée et aider-la à accomplir les tâches qui importent vraiment.</p>",
"tutorial_intro.screenTwo": "<h3>Comment fonctionne Mattermost :</h3><p>Vous pouvez échanger dans des canaux publics, des groupes privés ou des messages privés.</p><p>Tout est archivé et peut être recherché depuis n'importe quel navigateur web de bureau, tablette ou mobile.</p>",
"tutorial_intro.skip": "Passer le tutoriel",
- "tutorial_intro.support": "Besoin de quoi que ce soit, envoyez-nous un e-mail à : ",
+ "tutorial_intro.support": "Vous avez besoin d'aide ? Envoyez-nous un e-mail à : ",
"tutorial_intro.teamInvite": "Inviter des collègues",
"tutorial_intro.whenReady": " quand vous serez prêt.",
"tutorial_tip.next": "Suivant",
@@ -1899,13 +1924,13 @@
"update_command.question": "Vos modifications peuvent casser la commande Slash existante. Voulez-vous vraiment la mettre à jour ?",
"update_command.update": "Mettre à jour",
"upload_overlay.info": "Faites glisser un fichier pour le télécharger.",
- "user.settings.advance.embed_preview": "Voir des aperçus des contenus des liens, si disponibles",
+ "user.settings.advance.embed_preview": "For the first web link in a message, display a preview of website content below the message, if available",
"user.settings.advance.embed_toggle": "Voir un aperçu pour tous les messages inclus",
"user.settings.advance.enabledFeatures": "{count, number} {count, plural, one {Feature} other {Features}} Activée",
"user.settings.advance.formattingDesc": "Si activé, les messages seront formatés pour créer des liens, montrer des emoji, le style du texte et ajouter des sauts de ligne. Par défaut, ce paramètre est activé. La modification de ce paramètre nécessite le rafraîchissement de la page.",
"user.settings.advance.formattingTitle": "Activé le formatage des messages",
"user.settings.advance.joinLeaveDesc": "Lorsqu'activé, les messages systèmes indiquant qu'un utilisateur s'est connecté ou a quitté un canal seront visibles. Lorsque désactivé, ces messages seront masqués. Un message sera toutefois toujours affiché lorsque vous êtes ajouté à un canal, de façon à ce que vous en soyez quand même informé.",
- "user.settings.advance.joinLeaveTitle": "Permettre de rejoindre/quitter les messages",
+ "user.settings.advance.joinLeaveTitle": "Active les messages indiquant qu'un utilisateur a rejoint/quitté le canal",
"user.settings.advance.markdown_preview": "Voir l'option d'aperçu du markdown dans la zone de saisie de message",
"user.settings.advance.off": "Désactivé",
"user.settings.advance.on": "Activé",
@@ -1913,15 +1938,15 @@
"user.settings.advance.preReleaseTitle": "Activer les fonctionnalités expérimentales",
"user.settings.advance.sendDesc": "Si activé, 'Entrée' insère une nouvelle ligne et 'Ctrl + Entrée' envoie le message.",
"user.settings.advance.sendTitle": "Envoyer vos messages avec Ctrl+Entrée",
- "user.settings.advance.slashCmd_autocmp": "Autoriser les applications externes à propose l'auto-complétion",
+ "user.settings.advance.slashCmd_autocmp": "Autoriser les applications externes à proposer l'auto-complétion des commandes slash",
"user.settings.advance.title": "Paramètres avancés",
"user.settings.advance.webrtc_preview": "Activer la possibilité de passer et de recevoir des appels WebRTC en tête-à-tête",
"user.settings.custom_theme.awayIndicator": "Indicateur \"absent\"",
"user.settings.custom_theme.buttonBg": "Fond de bouton",
"user.settings.custom_theme.buttonColor": "Texte de bouton",
- "user.settings.custom_theme.centerChannelBg": "Fond du canal central",
+ "user.settings.custom_theme.centerChannelBg": "Fond de l'espace central",
"user.settings.custom_theme.centerChannelColor": "Text du canal central",
- "user.settings.custom_theme.centerChannelTitle": "Styles du canal central",
+ "user.settings.custom_theme.centerChannelTitle": "Styles de l'espace central",
"user.settings.custom_theme.codeTheme": "Code du thème",
"user.settings.custom_theme.copyPaste": "Copiez/Collez pour partager les couleurs du thème :",
"user.settings.custom_theme.linkButtonTitle": "Styles des boutons et liens",
@@ -1942,13 +1967,13 @@
"user.settings.custom_theme.sidebarTitle": "Style des barres latérales",
"user.settings.custom_theme.sidebarUnreadText": "Texte non-lu de barre latérale",
"user.settings.display.channelDisplayTitle": "Mode d’affichage du canal",
- "user.settings.display.channeldisplaymode": "Sélectionner la largeur du canal central.",
+ "user.settings.display.channeldisplaymode": "Veuillez spécifier la largeur de l'espace central.",
"user.settings.display.clockDisplay": "Affichage de l'horloge",
- "user.settings.display.collapseDesc": "Développer les aperçus du contenu des liens, si disponibles.",
- "user.settings.display.collapseDisplay": "Aperçu des liens",
- "user.settings.display.collapseOff": "Désactivé",
- "user.settings.display.collapseOn": "Activé",
- "user.settings.display.fixedWidthCentered": "Largeur fixe, centrée",
+ "user.settings.display.collapseDesc": "Set whether previews of image links show as expanded or collapsed by default. This setting can also be controlled using the slash commands /expand and /collapse.",
+ "user.settings.display.collapseDisplay": "Default appearance of image link previews",
+ "user.settings.display.collapseOff": "Collapsed",
+ "user.settings.display.collapseOn": "Expanded",
+ "user.settings.display.fixedWidthCentered": "Largeur fixe, centré",
"user.settings.display.fontDesc": "Choisissez la police de caractères utilisée pour l'interface de Mattermost.",
"user.settings.display.fontTitle": "Police d'affichage",
"user.settings.display.fullScreen": "Largeur pleine",
@@ -1964,7 +1989,7 @@
"user.settings.display.normalClock": "Horloge 12 heures (exemple : 4:00 PM)",
"user.settings.display.preferTime": "Choisissez l'affichage des heures dans l'application.",
"user.settings.display.showFullname": "Afficher prénom et nom",
- "user.settings.display.showNickname": "Afficher le pseudo s'il existe, sinon afficher prénom et nom",
+ "user.settings.display.showNickname": "Afficher le pseudo s'il existe, sinon afficher le prénom d'abord puis le nom",
"user.settings.display.showUsername": "Afficher le nom d'utilisateur (défaut)",
"user.settings.display.teammateDisplay": "Affichage des membres de l'équipe",
"user.settings.display.theme.applyToAllTeams": "Appliquer le nouveau thème à toutes mes équipes",
@@ -1979,21 +2004,21 @@
"user.settings.general.checkEmailNoAddress": "Vérifiez votre boîte de réception pour valider votre nouvelle adresse e-mail.",
"user.settings.general.close": "Quitter",
"user.settings.general.confirmEmail": "E-mail de confirmation",
- "user.settings.general.email": "Adresse électronique",
+ "user.settings.general.email": "Adresse e-mail",
"user.settings.general.emailGitlabCantUpdate": "La connexion se produit par Gitlab. L'addresse Electronique ne peut pas être mis à jour . Adresse e-mail utilisée pour les notifications est {email} .",
- "user.settings.general.emailGoogleCantUpdate": "La connexion se produit par Gitlab. L'adresse Electronique ne peut pas être mise à jour. L'adresse électronique utilisée pour les notifications est {email} .",
- "user.settings.general.emailHelp1": "L'adresse électronique est utilisée pour la connexion, les notifications et la réinitialisation du mot de passe. Votre adresse électronique doit être validée si vous le changez.",
- "user.settings.general.emailHelp2": "L'envoi d'-emails a été désactivé par votre administrateur système. Aucune notification pare-mail ne peut être envoyée.",
- "user.settings.general.emailHelp3": "L'adresse électronique est utilisée pour la connexion, les notifications et la réinitialisation du mot de passe.",
+ "user.settings.general.emailGoogleCantUpdate": "La connexion s'effectue par Gitlab. L'adresse e-mail ne peut pas être mise à jour. L'adresse e-mail utilisée pour les notifications est {email} .",
+ "user.settings.general.emailHelp1": "L'adresse e-mail est utilisée pour la connexion, les notifications et la réinitialisation du mot de passe. Votre adresse e-mail doit être validée si vous la changez.",
+ "user.settings.general.emailHelp2": "L'envoi d'e-mails a été désactivé par votre administrateur système. Aucune notification par e-mail ne peut être envoyée.",
+ "user.settings.general.emailHelp3": "L'adresse e-mail est utilisée pour la connexion, les notifications et la réinitialisation du mot de passe.",
"user.settings.general.emailHelp4": "Un e-mail de vérification a été envoyé à {email}.",
- "user.settings.general.emailLdapCantUpdate": "La connexion s'effectue par le biais d'AD/LDAP. L'adresse e-mail ne peut pas être mise à jour. L'adresse e-mail utilisée pour les notifications est {email}.",
- "user.settings.general.emailMatch": "Les adresses électroniques que vous avez saisies ne correspondent pas.",
- "user.settings.general.emailOffice365CantUpdate": "La connexion se produit par Office 365. L'adresse électronique ne peut pas être mise à jour. L'adresse électronique utilisée pour les notifications est {email} .",
+ "user.settings.general.emailLdapCantUpdate": "La connexion s'effectue par AD/LDAP. L'adresse e-mail ne peut pas être mise à jour. L'adresse e-mail utilisée pour les notifications est {email}.",
+ "user.settings.general.emailMatch": "Les adresses e-mail que vous avez saisies ne correspondent pas.",
+ "user.settings.general.emailOffice365CantUpdate": "La connexion s'effectue par Office 365. L'adresse e-mail ne peut pas être mise à jour. L'adresse e-mail utilisée pour les notifications est {email} .",
"user.settings.general.emailSamlCantUpdate": "La connexion s'effectue par le biais de SAML. L'adresse e-mail ne peut pas être mise à jour. L'adresse e-mail utilisée pour les notifications est {email}.",
- "user.settings.general.emailUnchanged": "Votre nouvelle adresse électronique est la même que l'ancienne.",
- "user.settings.general.emptyName": "Cliquez sur ‘Modifier’ pour ajouter votre nom complet",
- "user.settings.general.emptyNickname": "Cliquez sur ‘Modifier’ pour ajouter un surnom",
- "user.settings.general.emptyPosition": "Cliquez sur 'Modifier' pour ajouter votre intitulé de poste / rôle",
+ "user.settings.general.emailUnchanged": "Votre nouvelle adresse e-mail est la même que l'ancienne.",
+ "user.settings.general.emptyName": "Veuillez cliquer sur ‘Modifier’ pour spécifier votre nom complet",
+ "user.settings.general.emptyNickname": "Veuillez cliquer sur ‘Modifier’ pour ajouter un surnom",
+ "user.settings.general.emptyPosition": "Veuillez cliquer sur 'Modifier' pour ajouter votre intitulé de poste / rôle",
"user.settings.general.field_handled_externally": "Ce champ est géré par le service d'authentification. Si vous souhaitez le modifier, vous devez le faire par le biais de votre service d'authentification.",
"user.settings.general.firstName": "Prénom",
"user.settings.general.fullName": "Nom complet",
@@ -2005,22 +2030,22 @@
"user.settings.general.loginLdap": "Connexion avec LDAP ({email})",
"user.settings.general.loginOffice365": "Dernière connexion avec Office 365 ({email})",
"user.settings.general.loginSaml": "Connexion avec SAML ({email})",
- "user.settings.general.newAddress": "Nouvelle adresse : {email}<br />Vérifiez votre messagerie pour valider votre adresse électronique.",
+ "user.settings.general.newAddress": "Nouvelle adresse : {email}<br />Vérifiez vos e-mails pour valider votre adresse e-mail.",
"user.settings.general.nickname": "Pseudo",
- "user.settings.general.nicknameExtra": "Vous pouvez utiliser un pseudo à la place de vos prénom, nom et nom d'utilisateur. Ceci est pratique lorsque deux personnes de votre équipe ont des noms proches.",
- "user.settings.general.notificationsExtra": "Par défaut, vous recevez une notification quand quelqu'un écrit votre prénom. Allez aux réglages {notify} pour modifier ce paramètre.",
+ "user.settings.general.nicknameExtra": "Vous pouvez utiliser un pseudo à la place de vos prénom, nom et nom d'utilisateur. Ceci est pratique lorsque deux personnes de votre équipe ont des noms similaires phonétiquement.",
+ "user.settings.general.notificationsExtra": "Par défaut, vous recevez une notification lorsqu'un utilisateur cite votre prénom. Rendez vous dans les réglages {notify} pour modifier ce paramètre.",
"user.settings.general.notificationsLink": "Notifications",
"user.settings.general.position": "Rôle",
- "user.settings.general.positionExtra": "Utilisez le champ poste pour votre rôle ou intitulé de poste. Il sera affiché dans votre infobulle de profil utilisateur.",
- "user.settings.general.primaryEmail": "Adresse de courrier électronique principale",
- "user.settings.general.profilePicture": "Photo du profil",
+ "user.settings.general.positionExtra": "Veuillez utiliser ce champ pour spécifier votre rôle ou intitulé de poste. Il sera affiché dans votre infobulle de profil utilisateur.",
+ "user.settings.general.primaryEmail": "Adresse e-mail principale",
+ "user.settings.general.profilePicture": "Photo de profil",
"user.settings.general.title": "Configuration générale",
- "user.settings.general.uploadImage": "Cliquez sur ‘Modifier’ pour télécharger une image",
+ "user.settings.general.uploadImage": "Veuillez cliquer sur ‘Modifier’ pour télécharger une image",
"user.settings.general.username": "Nom d'utilisateur",
- "user.settings.general.usernameInfo": "Choisissez quelque chose de facile à se souvenir pour vos collègues.",
+ "user.settings.general.usernameInfo": "Veuillez spécifier un nom d'utilisateur facile à reconnaître et à mémoriser pour vos collègues.",
"user.settings.general.usernameReserved": "Ce nom est réservé, veuillez en choisir un autre.",
"user.settings.general.usernameRestrictions": "Les noms d'utilisateurs doivent commencer par une lettre et contenir entre {min} et {max} caractères composés de chiffres, lettres minuscules et des symboles '.', '-' et '_'.",
- "user.settings.general.validEmail": "Veuillez entrer une adresse électronique valide",
+ "user.settings.general.validEmail": "Veuillez spécifier une adresse e-mail valide",
"user.settings.general.validImage": "Seules les images JPG ou PNG sont autorisées pour les photos de profil",
"user.settings.import_theme.cancel": "Annuler",
"user.settings.import_theme.importBody": "Pour importer un thème, rendez-vous sur une Slack team et cliquez sur \"Preferences -> Sidebar Theme\". Ouvrez la fenêtre de personnalisation, copiez les couleurs du thèmes et collez-les ici :",
@@ -2053,10 +2078,10 @@
"user.settings.notifications.channelWide": "Mentions globales \"@channel\", \"@all\"",
"user.settings.notifications.close": "Quitter",
"user.settings.notifications.comments": "Notifications de réponse",
- "user.settings.notifications.commentsAny": "Déclencher des notifications sur les messages de fils de réponse que je débute ou dans lesquels je participe",
- "user.settings.notifications.commentsInfo": "En plus des notifications qui apparaissent lorsque vous êtes mentionné, vous pouvez choisir de recevoir, en plus, des notifications pour les réponses aux fils que vous avez lancés.",
- "user.settings.notifications.commentsNever": "Ne pas déclencher de notifications sur les messages dans les fils de réponse à moins que je ne sois mentionné",
- "user.settings.notifications.commentsRoot": "Déclencher des notifications pour les messages dans les discussions que j'ai commencé",
+ "user.settings.notifications.commentsAny": "Recevoir des notifications sur les messages de fils de réponse que je débute ou dans lesquels je participe",
+ "user.settings.notifications.commentsInfo": "En plus des notifications que vous recevez lorsque vous êtes mentionné, vous pouvez choisir de recevoir des notifications pour chaque réponse apportée à un message que vous avez envoyé.",
+ "user.settings.notifications.commentsNever": "Ne pas recevoir de notifications pour toutes réponses apportées à un fil de réponses à moins que je ne sois mentionné",
+ "user.settings.notifications.commentsRoot": "Recevoir des notifications pour pour toutes réponses apportées à un fil de messages que j'ai débuté",
"user.settings.notifications.desktop": "Envoyer des notifications sur le bureau",
"user.settings.notifications.desktop.allFirefoxForever": "Pour toute activité, visible indéfiniment",
"user.settings.notifications.desktop.allFirefoxTimed": "Pour toute activité, visible {seconds} secondes",
@@ -2124,20 +2149,22 @@
"user.settings.security.currentPassword": "Mot de passe actuel",
"user.settings.security.currentPasswordError": "Veuillez saisir votre mot de passe actuel",
"user.settings.security.deauthorize": "Supprimer l'autorisation",
- "user.settings.security.emailPwd": "Adresse électronique et mot de passe",
+ "user.settings.security.emailPwd": "Adresse e-mail et mot de passe",
"user.settings.security.gitlab": "GitLab",
"user.settings.security.google": "Google",
"user.settings.security.lastUpdated": "Dernière mise à jour le {date} à {time}",
"user.settings.security.ldap": "AD/LDAP",
"user.settings.security.loginGitlab": "Connexion avec GitLab",
+ "user.settings.security.loginGoogle": "Login done through Google Apps",
"user.settings.security.loginLdap": "Connexion avec LDAP",
+ "user.settings.security.loginOffice365": "Login done through Office 365",
"user.settings.security.loginSaml": "Se connecter par SAML",
"user.settings.security.logoutActiveSessions": "Consulter et déconnecter les sessions actives",
"user.settings.security.method": "Méthode de connexion",
"user.settings.security.newPassword": "Nouveau mot de passe",
"user.settings.security.noApps": "Aucune application OAuth 2.0 autorisée.",
"user.settings.security.oauthApps": "Applications OAuth 2.0",
- "user.settings.security.oauthAppsDescription": "Cliquez sur \"Modifier\" pour gérer votre Application OAuth 2.0",
+ "user.settings.security.oauthAppsDescription": "Veuillez cliquer sur 'Modifier' pour gérer vos applications OAuth 2.0",
"user.settings.security.oauthAppsHelp": "Les applications agissent en votre nom pour accéder à vos données sur la base des autorisations que vous leur accordez.",
"user.settings.security.office365": "Office 365",
"user.settings.security.oneSignin": "Vous ne pouvez avoir qu'une seule méthode de connexion à la fois. Changer de méthode de connexion provoquera l'envoi d'un e-mail vous notifiant que le changement a réussi.",
@@ -2159,13 +2186,15 @@
"user.settings.security.passwordErrorUppercaseNumberSymbol": "Votre mot de passe doit contenir au moins {min} caractères et au moins une lettre majuscule, un chiffre et un symbole (parmi \"~!@#$%^&*()\").",
"user.settings.security.passwordErrorUppercaseSymbol": "Votre mot de passe doit contenir au moins {min} caractères et au moins une lettre majuscule et un symbole (parmi \"~!@#$%^&*()\").",
"user.settings.security.passwordGitlabCantUpdate": "La connexion se produit à travers GitLab. Le mot de passe ne peut pas être mis à jour.",
+ "user.settings.security.passwordGoogleCantUpdate": "La connexion se produit à travers GitLab. Le mot de passe ne peut pas être mis à jour.",
"user.settings.security.passwordLdapCantUpdate": "La connexion se produit via LDAP . Le mot de passe ne peut pas être mis à jour.",
"user.settings.security.passwordMatchError": "Les nouveaux mots de passe que vous avez saisis ne correspondent pas.",
"user.settings.security.passwordMinLength": "Longueur minimum invalide, impossible d'afficher l'aperçu.",
+ "user.settings.security.passwordOffice365CantUpdate": "La connexion se produit à travers GitLab. Le mot de passe ne peut pas être mis à jour.",
"user.settings.security.passwordSamlCantUpdate": "Ce champ est géré par le service d'authentification. Si vous souhaitez le modifier, vous devez le faire par le biais de votre service d'authentification.",
- "user.settings.security.retypePassword": "Saisissez le nouveau mot de passe",
+ "user.settings.security.retypePassword": "Répéter le nouveau mot de passe",
"user.settings.security.saml": "SAML",
- "user.settings.security.switchEmail": "Utilisation de l'adresse électronique et du mot de passe",
+ "user.settings.security.switchEmail": "Utilisation de l'adresse e-mail et du mot de passe",
"user.settings.security.switchGitlab": "Utiliser GitLab SSO",
"user.settings.security.switchGoogle": "Utiliser Google SSO",
"user.settings.security.switchLdap": "Changer pour utiliser LDAP",
diff --git a/webapp/i18n/ja.json b/webapp/i18n/ja.json
index fbf60c1cc..ddc8fc243 100644
--- a/webapp/i18n/ja.json
+++ b/webapp/i18n/ja.json
@@ -28,6 +28,7 @@
"activity_log.sessionsDescription": "セッションは新しいブラウザーまたはデバイスからログインからログインした時に作成されます。Mattermostはシステム管理者が指定した期間内であればログインし直すことなく使用できます。すぐにログアウトしたい場合には、「ログアウト」ボタンを使用することで、セッションを終了させることができます。",
"activity_log_modal.android": "Android",
"activity_log_modal.androidNativeApp": "Androidネイティブアプリ",
+ "activity_log_modal.desktop": "ネイティブデスクトップアプリ",
"activity_log_modal.iphoneNativeApp": "iPhoneネイティブアプリ",
"add_command.autocomplete": "自動補完",
"add_command.autocomplete.help": "(オプション) 自動補完リストにスラッシュコマンドを表示する。",
@@ -260,6 +261,7 @@
"admin.email.notificationEmailTitle": "通知電子メールでの電子メールアドレス:",
"admin.email.notificationOrganization": "通知のフッターの住所:",
"admin.email.notificationOrganizationDescription": "Mattermostからの通知電子メールに表示する組織名と住所をを設定します。例: \"© ABC Corporation, 565 Knight Way, Palo Alto, California, 94305, USA\"。この欄を空欄にすると、組織名と住所は表示されません。",
+ "admin.email.notificationOrganizationExample": "例: \"© ABC Corporation, 565 Knight Way, Palo Alto, California, 94305, USA\"",
"admin.email.notificationsDescription": "本番環境では有効に設定してください。有効な場合、Mattermostは電子メールによる通知を送信します。開発者は無効に設定することで、電子メールによる通知を省略し、開発をより早く進められるようにできます。<br/>また、有効に設定することで、プレビューモードのバナーが表示されなくなります(この設定を反映するには再度ログインし直してください)。",
"admin.email.notificationsTitle": "通知電子メールを有効にする: ",
"admin.email.passwordSaltDescription": "パスワード初期化の電子メールの署名に32文字のソルトを付与します。これはインストールするたびにランダムに生成されます。新しいソルトを生成するには「再生成する」をクリックしてください。",
@@ -308,10 +310,20 @@
"admin.general.localization.serverLocaleTitle": "デフォルトのサーバー言語:",
"admin.general.log": "ログ",
"admin.general.policy": "ポリシー",
+ "admin.general.policy.allowEditPostAlways": "いつでも",
+ "admin.general.policy.allowEditPostDescription": "投稿した後にメッセージを編集できる期間についてのポリシーを設定してください。",
+ "admin.general.policy.allowEditPostNever": "できない",
+ "admin.general.policy.allowEditPostTimeLimit": "投稿後数秒",
+ "admin.general.policy.allowEditPostTitle": "メッセージの編集ができるユーザー:",
"admin.general.policy.permissionsAdmin": "チーム管理者とシステム管理者",
"admin.general.policy.permissionsAll": "全てのチームメンバー",
"admin.general.policy.permissionsAllChannel": "全てのチャンネルのメンバー",
+ "admin.general.policy.permissionsDeletePostAdmin": "チーム管理者とシステム管理者",
+ "admin.general.policy.permissionsDeletePostAll": "メッセージの作成者は自身のメッセージを削除できます。また、管理者はどんなメッセージも削除することができます。",
+ "admin.general.policy.permissionsDeletePostSystemAdmin": "システム管理者",
"admin.general.policy.permissionsSystemAdmin": "システム管理者",
+ "admin.general.policy.restrictPostDeleteDescription": "メッセージを削除できる権限を持つユーザーについてのポリシーを設定してください。",
+ "admin.general.policy.restrictPostDeleteTitle": "メッセージの削除ができるユーザー:",
"admin.general.policy.restrictPrivateChannelCreationDescription": "非公開グループの作成ができるユーザーについてのポリシーを設定してください。",
"admin.general.policy.restrictPrivateChannelCreationTitle": "非公開グループの作成を許可する:",
"admin.general.policy.restrictPrivateChannelDeletionCommandLineToolLink": "コマンドラインツール",
@@ -363,6 +375,7 @@
"admin.image.amazonS3BucketExample": "例: \"mattermost-media\"",
"admin.image.amazonS3BucketTitle": "Amazon S3バケット:",
"admin.image.amazonS3EndpointDescription": "S3と互換性のあるストレージプロバイダーのホスト名です。デフォルトは`s3.amazonaws.com`です。",
+ "admin.image.amazonS3EndpointExample": "例: \"s3.amazonaws.com\"",
"admin.image.amazonS3EndpointTitle": "Amazon S3 エンドポイント:",
"admin.image.amazonS3IdDescription": "Amazon EC2の管理者から認証情報を入手してください。",
"admin.image.amazonS3IdExample": "例: \"AKIADTOVBGERKLCBV\"",
@@ -564,7 +577,7 @@
"admin.rate.enableLimiterTitle": "投稿頻度制限を有効にする: ",
"admin.rate.httpHeaderDescription": "入力した場合、指定されたHTTPヘッダーフィールド(例えば、NGINXで設定する場合には\"X-Real-IP\"、AmazonELBの場合には\"X-Forwarded-For\")で投稿頻度制限を変更できます。",
"admin.rate.httpHeaderExample": "例: \"X-Real-IP\", \"X-Forwarded-For\"",
- "admin.rate.httpHeaderTitle": "HTTPヘッダーで投稿頻度制限を変更",
+ "admin.rate.httpHeaderTitle": "HTTPヘッダーで投稿頻度制限を変更:",
"admin.rate.maxBurst": "最大バーストサイズ:",
"admin.rate.maxBurstDescription": "1秒間当たりのクエリーの上限数を指定してください。",
"admin.rate.maxBurstExample": "例: \"100\"",
@@ -597,7 +610,7 @@
"admin.saml.assertionConsumerServiceURLDesc": "https://<your-mattermost-url>/login/sso/saml を入力してください。HTTPとHTTPSのどちらを使うか注意してください。この入力欄は、アサーションコンシューマーサービスURLとも呼ばれます。",
"admin.saml.assertionConsumerServiceURLEx": "例: \"https://<your-mattermost-url>/login/sso/saml\"",
"admin.saml.assertionConsumerServiceURLTitle": "サービスプロバイダーログインURL:",
- "admin.saml.bannerDesc": "User attributes in SAML server, including user deactivation or removal, are updated in Mattermost during user login. Learn more at: <a href=\"https://docs.mattermost.com/deployment/sso-saml.html\">https://docs.mattermost.com/deployment/sso-saml.html</a>",
+ "admin.saml.bannerDesc": "非活性もしくは削除されたユーザーを含めSAMLサーバーのユーザー属性がログイン中に更新されました。詳細について: <a href=\"https://docs.mattermost.com/deployment/sso-saml.html\">https://docs.mattermost.com/deployment/sso-saml.html</a>",
"admin.saml.emailAttrDesc": "Mattermostのユーザーの電子メールアドレスを設定するために使用されるSAMLアサーションの属性値です。",
"admin.saml.emailAttrEx": "例: \"Email\"または\"PrimaryEmail\"",
"admin.saml.emailAttrTitle": "電子メール属性値:",
@@ -831,10 +844,10 @@
"admin.team.dirDesc": "有効な場合、チーム一覧に表示されるチームが、新しいチーム作成の代わりに、メインページに表示されます。",
"admin.team.dirTitle": "チーム一覧を有効にする: ",
"admin.team.maxChannelsDescription": "チーム毎のチャンネル数合計の最大値です。アクティブなチャンネルと削除済みのチャンネルの両方が数えられます。",
- "admin.team.maxChannelsExample": "例 \"100\"",
+ "admin.team.maxChannelsExample": "例: \"100\"",
"admin.team.maxChannelsTitle": "チーム毎の最大チャンネル数:",
"admin.team.maxNotificationsPerChannelDescription": "パフォーマンスの問題によりユーザーがメッセージ、@all、@here、@channelを入力してもが通知を送信されなくなるチャンネル内のユーザーの最大数です。",
- "admin.team.maxNotificationsPerChannelExample": "例 \"1000\"",
+ "admin.team.maxNotificationsPerChannelExample": "例: \"10000\"",
"admin.team.maxNotificationsPerChannelTitle": "チャンネルごとの最大通知数:",
"admin.team.maxUsersDescription": "チーム毎のユーザー数合計の最大値です。有効なユーザーと無効なユーザーの両方が数えられます。",
"admin.team.maxUsersExample": "例: \"25\"",
@@ -844,7 +857,7 @@
"admin.team.openServerTitle": "オープンサーバーを有効にする: ",
"admin.team.restrictDescription": "特定のドメインからだけチームとユーザーアカウントの作成を可能にします。単数(例: \"mattermost.org\")でもカンマ区切りで複数(例: \"corp.mattermost.com, mattermost.org\")でも指定できます。",
"admin.team.restrictDirectMessage": "ダイレクトメッセージの対象範囲:",
- "admin.team.restrictDirectMessageDesc": "'Mattermostの全てのユーザー'はチームに属していないユーザーへのダイレクトメッセージのチャンネルを利用することが出来ます。'このチームのメンバー'では同じチームに属しているユーザーに制限されます。",
+ "admin.team.restrictDirectMessageDesc": "'Mattermostの全てのユーザー'はチームに属していないユーザーへのダイレクトメッセージのチャンネルを利用することが出来ます。'チームのメンバーのみ'では同じチームに属しているユーザーに制限されます。",
"admin.team.restrictExample": "例: \"corp.mattermost.com, mattermost.org\"",
"admin.team.restrictNameDesc": "有効な場合、www、admin、support、test、channelなど予約された名前を持つチームは作成できません。",
"admin.team.restrictNameTitle": "予約されたチーム名: ",
@@ -901,13 +914,13 @@
"admin.webrtc.gatewayWebsocketUrlTitle": "ゲートウェイウェブソケットURL:",
"admin.webrtc.stunUriDescription": "stun:<your-stun-url>:<port> のようにSTUN URIを入力してください。STUNは、デバイスがNATの背後に置かれていた場合に、パブリックなIPアドレスへのアクセスをエンドホストがアシストすることを許可するために使われる標準的なネットワークプロトコルです。",
"admin.webrtc.stunUriExample": "例: \"stun:webrtc.mattermost.com:5349\"",
- "admin.webrtc.stunUriTitle": "STUN URI",
+ "admin.webrtc.stunUriTitle": "STUN URI:",
"admin.webrtc.turnSharedKeyDescription": "TURNサーバー共通鍵を入力してください。これは接続を確立するための動的なパスワードを生成するために使用されます。それぞれのパスワードが有効なのは短期間です。",
"admin.webrtc.turnSharedKeyExample": "例: \"bXdkOWQxc3d0Ynk3emY5ZmsxZ3NtazRjaWg=\"",
"admin.webrtc.turnSharedKeyTitle": "TURN共通鍵:",
"admin.webrtc.turnUriDescription": "turn:<your-turn-url>:<port> のようにTURN URIを入力してください。TURNは、デバイスが対称型NATの背後に置かれていた場合に、リレー用パブリックIPアドレスを使用することによって接続を確立することを、エンドホストがアシストするための標準的なネットワークプロトコルです。",
"admin.webrtc.turnUriExample": "例: \"turn:webrtc.mattermost.com:5349\"",
- "admin.webrtc.turnUriTitle": "TURN URI",
+ "admin.webrtc.turnUriTitle": "TURN URI:",
"admin.webrtc.turnUsernameDescription": "TURNサーバーユーザー名を入力してください。",
"admin.webrtc.turnUsernameExample": "例: \"myusername\"",
"admin.webrtc.turnUsernameTitle": "TURNユーザー名:",
@@ -923,12 +936,14 @@
"analytics.chart.meaningful": "意味のある表示のための十分なデータがありません。",
"analytics.system.activeUsers": "投稿実績のあるアクティブユーザー",
"analytics.system.channelTypes": "チャンネル形式",
+ "analytics.system.dailyActiveUsers": "日次アクティブユーザー",
"analytics.system.expiredBanner": "エンタープライズライセンスは{date}に期限が切れました。この日から15日以内にライセンスを更新してください。問い合わせは<a href='mailto:commercial@mattermost.com'>commercial@mattermost.com</a>までお願いします。",
"analytics.system.expiringBanner": "エンタープライズライセンスは{date}に期限が切れます。ライセンスを更新するには、<a href='mailto:commercial@mattermost.com'>commercial@mattermost.com</a>に問い合わせてください。",
+ "analytics.system.monthlyActiveUsers": "月次アクティブユーザー",
"analytics.system.postTypes": "投稿、ファイル、ハッシュタグ",
"analytics.system.privateGroups": "非公開グループ",
"analytics.system.publicChannels": "公開チャンネル",
- "analytics.system.skippedIntensiveQueries": "To maximize performance, some statistics are disabled. You can re-enable them in config.json. See: <a href='https://docs.mattermost.com/administration/statistics.html' target='_blank'>https://docs.mattermost.com/administration/statistics.html</a>",
+ "analytics.system.skippedIntensiveQueries": "パフォーマンスを最大にするために無効化された統計情報があります。config.jsonで再び有効化にすることができます。<a href='https://docs.mattermost.com/administration/statistics.html' target='_blank'>https://docs.mattermost.com/administration/statistics.html</a>を参照してください。",
"analytics.system.textPosts": "テキストのみの投稿数",
"analytics.system.title": "システムの使用統計",
"analytics.system.totalChannels": "総チャンネル数",
@@ -1022,7 +1037,6 @@
"backstage_sidebar.integrations.outgoing_webhooks": "外向きのウェブフック",
"calling_screen": "呼び出し中",
"center_panel.recent": "ここをクリックして最近のメッセージへ移動します。 ",
- "chanel_header.addMembers": "メンバーを追加する",
"change_url.close": "閉じる",
"change_url.endWithLetter": "英数字で終わらせてください",
"change_url.invalidUrl": "不正なURL",
@@ -1040,6 +1054,7 @@
"channel_flow.handleTooShort": "チャンネルURLは2文字以上の小文字の英数字にしてください",
"channel_flow.invalidName": "不正なチャンネル名です",
"channel_flow.set_url_title": "{term}のURLを設定する",
+ "channel_header.addMembers": "メンバーを追加する",
"channel_header.addToFavorites": "お気に入りに追加する",
"channel_header.channel": "チャンネル",
"channel_header.channelHeader": "チャンネルヘッダーを編集する",
@@ -1079,10 +1094,14 @@
"channel_loader.uploadedFile": " がファイルをアップロードしました",
"channel_loader.uploadedImage": " が画像をアップロードしました",
"channel_loader.wrote": " が書きました: ",
+ "channel_members_dropdown.channel_admin": "チャンネル管理者",
+ "channel_members_dropdown.channel_member": "チャンネルのメンバー",
+ "channel_members_dropdown.make_channel_admin": "チャンネル管理者を作成する",
+ "channel_members_dropdown.make_channel_member": "チャンネルメンバーを作成する",
+ "channel_members_dropdown.remove_from_channel": "チャンネルから削除する",
+ "channel_members_dropdown.remove_member": "メンバーを削除メンバーを削除する",
"channel_members_modal.addNew": " 新しいメンバーを追加する",
- "channel_members_modal.close": "閉じる",
- "channel_members_modal.remove": "削除する",
- "channel_memebers_modal.members": " メンバー",
+ "channel_members_modal.members": " メンバー",
"channel_modal.cancel": "キャンセル",
"channel_modal.channel": "チャンネル",
"channel_modal.createNew": "新規 ",
@@ -1091,6 +1110,7 @@
"channel_modal.edit": "編集する",
"channel_modal.group": "グループ",
"channel_modal.header": "ヘッダー",
+ "channel_modal.headerEx": "例: \"[Link Title](http://example.com)\"",
"channel_modal.headerHelp": "{term}名の近くの{term}のヘッダー部分に表示されるテキストを設定してください。例えば、よく入力されるリンク [リンクのタイトル](http://example.com) などを含めてください。",
"channel_modal.modalTitle": "新規 ",
"channel_modal.name": "名前",
@@ -1101,6 +1121,7 @@
"channel_modal.publicChannel1": "公開チャンネルを作成する",
"channel_modal.publicChannel2": "新しい誰でも参加できる公開チャンネルを作成します。 ",
"channel_modal.purpose": "目的",
+ "channel_modal.purposeEx": "例: \"バグや改善を取りまとめるチャンネル\"",
"channel_notifications.allActivity": "全てのアクティビティーについて",
"channel_notifications.allUnread": "全ての未読のメッセージについて",
"channel_notifications.globalDefault": "システム全体のデフォルト({notifyLevel})",
@@ -1113,6 +1134,7 @@
"channel_notifications.unreadInfo": "未読のメッセージがある場合、チャンネル名がサイドバーに太字で表示されます。「あなたについての投稿のみ」を選択することで、あなたについての投稿がある場合のみ太字で表示されます。",
"channel_select.placeholder": "--- チャンネルを選択してください ---",
"channel_switch_modal.dm": "(ダイレクトメッセージ)",
+ "channel_switch_modal.failed_to_open": "チャンネルを開けませんでした。",
"channel_switch_modal.help": "チャンネル名を入力してください。↑↓ で閲覧、TAB で選択、↵ で切り替え、ESC でキャンセル",
"channel_switch_modal.not_found": "一致するものは見つかりませんでした。",
"channel_switch_modal.submit": "切り替える",
@@ -1285,15 +1307,14 @@
"find_team.submitError": "有効な電子メールアドレスを入力してください",
"flag_post.flag": "追跡フラグ",
"flag_post.unflag": "フラグを消す",
+ "general_tab.chooseDescription": "あなたのチームの新しい説明を選択してください",
"general_tab.chooseName": "あなたのチームの新しい名称を選択してください",
"general_tab.codeDesc": "招待コードを再生成するには「編集」をクリックしてください。",
- "general_tab.codeLongDesc": "招待コードは、<strong>チーム招待リンクを入手</strong>で作成されたチーム招待リンクのURLの一部として使われます。再生成することで新しいチーム招待リンクが作成され、古いリンクは無効化されます。",
+ "general_tab.codeLongDesc": "招待コードは、メインメニューの {getTeamInviteLink} で作成されたチーム招待リンクのURLの一部として使われます。再生成することで新しいチーム招待リンクが作成され、古いリンクは無効化されます。",
"general_tab.codeTitle": "招待コード",
- "general_tab.dirDisabled": "チーム一覧が無効になっています。システム管理者にシステムコンソールのチームの設定でチーム一覧を有効化するように依頼してください。",
- "general_tab.dirOff": "このシステムでは、チーム一覧は無効になっています。",
"general_tab.emptyDescription": "チームの説明を追加するには「編集する」をクリックしてください。",
+ "general_tab.getTeamInviteLink": "チーム招待リンクを入手",
"general_tab.includeDirDesc": "ホームページのチーム一覧にチーム名が表示され、サインインページへのリンクが提供されます。",
- "general_tab.includeDirTitle": "チーム一覧に追加する",
"general_tab.no": "いいえ",
"general_tab.openInviteDesc": "許可された場合、このチームへのリンクは、このチームに誰もが参加できるようにランディングページに含まれます。",
"general_tab.openInviteTitle": "このサーバーにアカウントを持つ全てのユーザーが、このチームに参加できるようにする",
@@ -1550,6 +1571,7 @@
"login.passwordChanged": " パスワードは正常に更新されました",
"login.session_expired": " あなたのセッションは有効期限が切れました。再度ログインしてください。",
"login.signIn": "サインイン",
+ "login.signInLoading": "サインイン中です...",
"login.signInWith": "サインイン方法:",
"login.userNotFound": "あなたのログイン情報に合致するアカウントはありません。",
"login.username": "ユーザー名",
@@ -1561,8 +1583,10 @@
"member_item.makeAdmin": "管理者にする",
"member_item.member": "メンバー",
"member_list.noUsersAdd": "ユーザーは追加されません。",
+ "members_popover.manageMembers": "メンバーを管理する",
"members_popover.msg": "メッセージ",
"members_popover.title": "メンバー",
+ "members_popover.viewMembers": "メンバーを見る",
"mfa.confirm.complete": "<strong>セットアップが完了しました!</strong>",
"mfa.confirm.okay": "OK",
"mfa.confirm.secure": "あなたのアカウントは安全です。次にサインインするとき、あなたはGoogle Authenticatorからのコードの入力を求められるでしょう。",
@@ -1665,6 +1689,7 @@
"post_info.mobile.unflag": "フラグを消す",
"post_info.permalink": "パーマリンク",
"post_info.reply": "返信する",
+ "post_message_view.edited": "(編集済)",
"posts_view.loadMore": "もっとメッセージを読み込む",
"posts_view.newMsg": "新しいメッセージ",
"posts_view.newMsgBelow": "以下に新しい {count, plural, one {message} other {messages}}があります",
@@ -1899,7 +1924,7 @@
"update_command.question": "あなたの変更により既存のコマンドが動作しなくなる恐れがあります。本当に更新しますか?",
"update_command.update": "更新",
"upload_overlay.info": "ファイルをアップロードするためにドラッグアンドドロップします。",
- "user.settings.advance.embed_preview": "利用可能な場合、リンク先の内容を試験的にプレビューする",
+ "user.settings.advance.embed_preview": "メッセージ内の最初のWebのリンクについて、可能ならばそのメッセージの下にWebサイトの内容のプレビューを表示します",
"user.settings.advance.embed_toggle": "全ての埋め込まれたプレビューの表示非表示を切り替える",
"user.settings.advance.enabledFeatures": "{count, number} {count, plural, one {Feature} other {Features}}が有効化されました",
"user.settings.advance.formattingDesc": "オンにした場合、投稿は、リンクを作成したり、絵文字を表示したり、テキストに書式を設定したり、改行したりされます。デフォルトではオンに設定されています。この設定を変更した場合には、ページを再読み込みしてください。",
@@ -1944,10 +1969,10 @@
"user.settings.display.channelDisplayTitle": "チャンネル表示",
"user.settings.display.channeldisplaymode": "中央のチャンネルの幅を選択してください。",
"user.settings.display.clockDisplay": "時計表示",
- "user.settings.display.collapseDesc": "利用可能な場合、内容のプレビューを表示するようにリンクを拡張します。",
- "user.settings.display.collapseDisplay": "リンクのプレビュー",
- "user.settings.display.collapseOff": "オフ",
- "user.settings.display.collapseOn": "オン",
+ "user.settings.display.collapseDesc": "画像リンクのプレビューが展開して表示されるか折り畳んで表示されるかを設定してください。この設定はスラッシュコマンドの /expand と /collapse を使用して制御することもできます。",
+ "user.settings.display.collapseDisplay": "画像リンクプレビューのデフォルト表示",
+ "user.settings.display.collapseOff": "折り畳まれる",
+ "user.settings.display.collapseOn": "展開される",
"user.settings.display.fixedWidthCentered": "固定幅、中央寄せ",
"user.settings.display.fontDesc": "Mattermostユーザーインターフェイスで使うフォントを選択してください。",
"user.settings.display.fontTitle": "表示フォント",
@@ -2130,7 +2155,9 @@
"user.settings.security.lastUpdated": "最終更新: {date} {time}",
"user.settings.security.ldap": "AD/LDAP",
"user.settings.security.loginGitlab": "GitLabでログインしました",
+ "user.settings.security.loginGoogle": "Google Appsでログインしました",
"user.settings.security.loginLdap": "AD/LDAPでログインする",
+ "user.settings.security.loginOffice365": "Office 365でログインしました",
"user.settings.security.loginSaml": "SAMLでログインしました",
"user.settings.security.logoutActiveSessions": "アクティブなセッションを見てログアウトする",
"user.settings.security.method": "サインイン方法",
@@ -2159,9 +2186,11 @@
"user.settings.security.passwordErrorUppercaseNumberSymbol": "パスワードは少なくとも{min}文字以上にしてください。少なくとも1つの英大文字と数字、記号 (例: \"~!@#$%^&*()\") も必要です。",
"user.settings.security.passwordErrorUppercaseSymbol": "パスワードは少なくとも{min}文字以上にしてください。少なくとも1つの英大文字と記号 (例: \"~!@#$%^&*()\") も必要です。",
"user.settings.security.passwordGitlabCantUpdate": "GitLabでログインしています。パスワードは更新できません。",
+ "user.settings.security.passwordGoogleCantUpdate": "GitLabでログインしています。パスワードは更新できません。",
"user.settings.security.passwordLdapCantUpdate": "AD/LDAPでログインしています。パスワードは更新できません。",
"user.settings.security.passwordMatchError": "あなたの入力した新しいパスワードが一致しません。",
"user.settings.security.passwordMinLength": "最小の長さが不正です。プレビューを表示できません。",
+ "user.settings.security.passwordOffice365CantUpdate": "GitLabでログインしています。パスワードは更新できません。",
"user.settings.security.passwordSamlCantUpdate": "この欄はあなたのログインプロバイダーで使用されます。変更したい場合、ログインプロバイダーを通じて変更してください。",
"user.settings.security.retypePassword": "新しいパスワードをもう一度入力してください",
"user.settings.security.saml": "SAML",
diff --git a/webapp/i18n/ko.json b/webapp/i18n/ko.json
index 50b3c387f..bb73f0583 100644
--- a/webapp/i18n/ko.json
+++ b/webapp/i18n/ko.json
@@ -28,6 +28,7 @@
"activity_log.sessionsDescription": "세션은 기기의 새 브라우저로 로그인할때 생성됩니다. 세션을 사용하면 시스템에서 정한 시간동안은 다시 로그인할 필요가 없습니다. '로그아웃' 버튼을 사용해서 세션을 종료할 수 있습니다.",
"activity_log_modal.android": "안드로이드",
"activity_log_modal.androidNativeApp": "안드로이드 앱",
+ "activity_log_modal.desktop": "Native Desktop App",
"activity_log_modal.iphoneNativeApp": "아이폰 앱",
"add_command.autocomplete": "자동완성",
"add_command.autocomplete.help": "(선택사항) 명령어가 자동완성 목록에서 보이게 합니다.",
@@ -260,6 +261,7 @@
"admin.email.notificationEmailTitle": "알림 이메일 주소:",
"admin.email.notificationOrganization": "Notification Footer Mailing Address:",
"admin.email.notificationOrganizationDescription": "Organization name and address displayed on email notifications from Mattermost, such as \"© ABC Corporation, 565 Knight Way, Palo Alto, California, 94305, USA\". If the field is left empty, the organization name and address will not be displayed.",
+ "admin.email.notificationOrganizationExample": "예시 \"© ABC Corporation, 565 Knight Way, Palo Alto, California, 94305, USA\"",
"admin.email.notificationsDescription": "Typically set to true in production. When true, Mattermost attempts to send email notifications. Developers may set this field to false to skip email setup for faster development.<br />Setting this to true removes the Preview Mode banner (requires logging out and logging back in after setting is changed).",
"admin.email.notificationsTitle": "Enable Email Notifications: ",
"admin.email.passwordSaltDescription": "32-character salt added to signing of password reset emails. Randomly generated on install. Click \"Regenerate\" to create new salt.",
@@ -281,7 +283,7 @@
"admin.email.smtpPasswordExample": "예시 \"yourpassword\", \"jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY\"",
"admin.email.smtpPasswordTitle": "SMTP 서버 패스워드:",
"admin.email.smtpPortDescription": "SMTP 이메일 서버의 포트를 입력하세요.",
- "admin.email.smtpPortExample": "Ex: \"25\", \"465\", \"587\"",
+ "admin.email.smtpPortExample": "E.g.: \"25\", \"465\", \"587\"",
"admin.email.smtpPortTitle": "SMTP 서버 포트:",
"admin.email.smtpServerDescription": "Location of SMTP email server.",
"admin.email.smtpServerExample": "예시 \"smtp.yourcompany.com\", \"email-smtp.us-east-1.amazonaws.com\"",
@@ -308,10 +310,20 @@
"admin.general.localization.serverLocaleTitle": "서버 기본 언어:",
"admin.general.log": "로그",
"admin.general.policy": "정책",
+ "admin.general.policy.allowEditPostAlways": "Any time",
+ "admin.general.policy.allowEditPostDescription": "Set policy on the length of time authors have to edit their messages after posting.",
+ "admin.general.policy.allowEditPostNever": "알림 사용 안함",
+ "admin.general.policy.allowEditPostTimeLimit": "seconds after posting",
+ "admin.general.policy.allowEditPostTitle": "Allow users to edit their messages:",
"admin.general.policy.permissionsAdmin": "Team and System Admins",
"admin.general.policy.permissionsAll": "모든 팀 회원",
"admin.general.policy.permissionsAllChannel": "All channel members",
+ "admin.general.policy.permissionsDeletePostAdmin": "Team and System Admins",
+ "admin.general.policy.permissionsDeletePostAll": "Message authors can delete their own messages, and Administrators can delete any message",
+ "admin.general.policy.permissionsDeletePostSystemAdmin": "시스템 관리자",
"admin.general.policy.permissionsSystemAdmin": "시스템 관리자",
+ "admin.general.policy.restrictPostDeleteDescription": "Set policy on who has permission to delete messages.",
+ "admin.general.policy.restrictPostDeleteTitle": "Allow which users to delete messages:",
"admin.general.policy.restrictPrivateChannelCreationDescription": "Set policy on who can create private groups.",
"admin.general.policy.restrictPrivateChannelCreationTitle": "Enable private group creation for:",
"admin.general.policy.restrictPrivateChannelDeletionCommandLineToolLink": "command line tool",
@@ -363,6 +375,7 @@
"admin.image.amazonS3BucketExample": "예시 \"mattermost-media\"",
"admin.image.amazonS3BucketTitle": "Amazon S3 Bucket:",
"admin.image.amazonS3EndpointDescription": "Hostname of your S3 Compatible Storage provider. Defaults to `s3.amazonaws.com`.",
+ "admin.image.amazonS3EndpointExample": "E.g.: \"s3.amazonaws.com\"",
"admin.image.amazonS3EndpointTitle": "Amazon S3 Endpoint:",
"admin.image.amazonS3IdDescription": "Obtain this credential from your Amazon EC2 administrator.",
"admin.image.amazonS3IdExample": "예시 \"AKIADTOVBGERKLCBV\"",
@@ -513,7 +526,7 @@
"admin.metrics.enableDescription": "When true, Mattermost will enable performance monitoring collection and profiling. Please see <a href=\"http://docs.mattermost.com/deployment/metrics.html\" target='_blank'>documentation</a> to learn more about configuring performance monitoring for Mattermost.",
"admin.metrics.enableTitle": "Enable Performance Monitoring:",
"admin.metrics.listenAddressDesc": "The address the server will listen on to expose performance metrics.",
- "admin.metrics.listenAddressEx": "Ex \":8065\"",
+ "admin.metrics.listenAddressEx": "예시 \":8065\"",
"admin.metrics.listenAddressTitle": "Listen Address:",
"admin.mfa.bannerDesc": "Multi-factor authentication is only available for accounts with LDAP and email login methods. If there are users on your system with other login methods, it is recommended you set up multi-factor authentication directly with the SSO or SAML provider.",
"admin.mfa.cluster": "High",
@@ -834,7 +847,7 @@
"admin.team.maxChannelsExample": "예시 \"100\"",
"admin.team.maxChannelsTitle": "팀 당 최대 채널: ",
"admin.team.maxNotificationsPerChannelDescription": "Maximum total number of users in a channel before users typing messages, @all, @here, and @channel no longer send notifications because of performance.",
- "admin.team.maxNotificationsPerChannelExample": "Ex \"10000\"",
+ "admin.team.maxNotificationsPerChannelExample": "예시 \"10000\"",
"admin.team.maxNotificationsPerChannelTitle": "Max Notifications Per Channel:",
"admin.team.maxUsersDescription": "Maximum total number of users per team, including both active and inactive users.",
"admin.team.maxUsersExample": "예시 \"25\"",
@@ -923,8 +936,10 @@
"analytics.chart.meaningful": "Not enough data for a meaningful representation.",
"analytics.system.activeUsers": "활성 사용자 (글 작성 기준)",
"analytics.system.channelTypes": "Channel Types",
+ "analytics.system.dailyActiveUsers": "Daily Active Users",
"analytics.system.expiredBanner": "The Enterprise license expired on {date}. You have 15 days from this date to renew the license, please contact <a href='mailto:commercial@mattermost.com'>commercial@mattermost.com</a>.",
"analytics.system.expiringBanner": "The Enterprise license is expiring on {date}. To renew your license, please contact <a href='mailto:commercial@mattermost.com'>commercial@mattermost.com</a>.",
+ "analytics.system.monthlyActiveUsers": "Monthly Active Users",
"analytics.system.postTypes": "글, 파일, 해시태그",
"analytics.system.privateGroups": "비공개 그룹",
"analytics.system.publicChannels": "공개 채널",
@@ -1022,7 +1037,6 @@
"backstage_sidebar.integrations.outgoing_webhooks": "Outgoing Webhook",
"calling_screen": "Calling",
"center_panel.recent": "Click here to jump to recent messages. ",
- "chanel_header.addMembers": "회원 추가",
"change_url.close": "닫기",
"change_url.endWithLetter": "Must end with a letter or number",
"change_url.invalidUrl": "잘못된 URL",
@@ -1040,6 +1054,7 @@
"channel_flow.handleTooShort": "Channel URL must be 2 or more lowercase alphanumeric characters",
"channel_flow.invalidName": "잘못된 채널 이름",
"channel_flow.set_url_title": "{term} URL 설정",
+ "channel_header.addMembers": "회원 추가",
"channel_header.addToFavorites": "즐겨찾기에 추가",
"channel_header.channel": "채널",
"channel_header.channelHeader": "채널 헤더 설정",
@@ -1079,10 +1094,14 @@
"channel_loader.uploadedFile": " uploaded a file",
"channel_loader.uploadedImage": " uploaded an image",
"channel_loader.wrote": " wrote: ",
+ "channel_members_dropdown.channel_admin": "Channel Admin",
+ "channel_members_dropdown.channel_member": "채널 회원",
+ "channel_members_dropdown.make_channel_admin": "Make Channel Admin",
+ "channel_members_dropdown.make_channel_member": "Make Channel Member",
+ "channel_members_dropdown.remove_from_channel": "Remove From Channel",
+ "channel_members_dropdown.remove_member": "Remove Member",
"channel_members_modal.addNew": " 새로운 회원 추가",
- "channel_members_modal.close": "닫기",
- "channel_members_modal.remove": "제거하기",
- "channel_memebers_modal.members": " 회원",
+ "channel_members_modal.members": " 회원",
"channel_modal.cancel": "취소",
"channel_modal.channel": "채널",
"channel_modal.createNew": "새로운 ",
@@ -1091,6 +1110,7 @@
"channel_modal.edit": "편집",
"channel_modal.group": "그룹",
"channel_modal.header": "헤더",
+ "channel_modal.headerEx": "E.g.: \"[Link Title](http://example.com)\"",
"channel_modal.headerHelp": "{term} 상단 이름 옆에 표시될 텍스트를 입력하세요. 예를 들면, 다음과 같이 자주 사용되는 링크를 등록할 수 있습니다. [링크](http://example.com).",
"channel_modal.modalTitle": "새로운 ",
"channel_modal.name": "이름",
@@ -1101,6 +1121,7 @@
"channel_modal.publicChannel1": "공개 채널 만들기",
"channel_modal.publicChannel2": "누구나 참여할 수 있는 새 공개 채널을 만듭니다. ",
"channel_modal.purpose": "설명",
+ "channel_modal.purposeEx": "E.g.: \"A channel to file bugs and improvements\"",
"channel_notifications.allActivity": "모든 활동",
"channel_notifications.allUnread": "모든 읽지않은 메시지",
"channel_notifications.globalDefault": "전역 기본 설정 ({notifyLevel})",
@@ -1113,6 +1134,7 @@
"channel_notifications.unreadInfo": "읽지 않은 채널은 사이드바에서 굵은 글씨로 표시됩니다. \"멘션만\"을 선택하면 내가 멘션된 채널만 굵게 표시됩니다.",
"channel_select.placeholder": "--- 채널을 선택하세요 ---",
"channel_switch_modal.dm": "(개인 메시지)",
+ "channel_switch_modal.failed_to_open": "Failed to open channel.",
"channel_switch_modal.help": "채널 이름을 입력하세요. ↑↓ 방향키와 TAB키를 이용하여 선택하세요, ↵ 엔터키를 누르면 확인, ESC키를 누르면 취소됩니다.",
"channel_switch_modal.not_found": "일치하는 채널이 없습니다.",
"channel_switch_modal.submit": "변경",
@@ -1285,15 +1307,14 @@
"find_team.submitError": "유효한 이메일 주소를 입력하세요.",
"flag_post.flag": "중요 메시지로 지정",
"flag_post.unflag": "중요 메시지 해제",
+ "general_tab.chooseDescription": "사용자명을 선택하세요.",
"general_tab.chooseName": "사용자명을 선택하세요.",
"general_tab.codeDesc": "가입 링크를 변경하려면 '편집'을 클릭하세요.",
"general_tab.codeLongDesc": "The Invite Code is used as part of the URL in the team invitation link created by <strong>Get Team Invite Link</strong> in the main menu. Regenerating creates a new team invitation link and invalidates the previous link.",
"general_tab.codeTitle": "가입 링크",
- "general_tab.dirDisabled": "Team Directory has been disabled. Please ask a System Admin to enable the Team Directory in the System Console team settings.",
- "general_tab.dirOff": "Team directory is turned off for this system.",
"general_tab.emptyDescription": "Click 'Edit' to add a team description.",
+ "general_tab.getTeamInviteLink": "가입 링크",
"general_tab.includeDirDesc": "Including this team will display the team name from the Team Directory section of the Home Page, and provide a link to the sign-in page.",
- "general_tab.includeDirTitle": "Include this team in the Team Directory",
"general_tab.no": "아니요",
"general_tab.openInviteDesc": "When allowed, a link to this team will be included on the landing page allowing anyone with an account to join this team.",
"general_tab.openInviteTitle": "계정이 있는 사용자가 이 팀에 가입하는 것을 허용합니까?",
@@ -1550,6 +1571,7 @@
"login.passwordChanged": " 패스워드가 성공적으로 변경되었습니다.",
"login.session_expired": " 세션이 만료되었습니다. 다시 로그인 하세요.",
"login.signIn": "로그인",
+ "login.signInLoading": "Signing in...",
"login.signInWith": "다음으로 로그인하기:",
"login.userNotFound": "입력된 계정과 일치하는 사용자 정보를 찾을 수 없습니다.",
"login.username": "사용자명",
@@ -1561,8 +1583,10 @@
"member_item.makeAdmin": "관리자로 설정하기",
"member_item.member": "회원",
"member_list.noUsersAdd": "추가할 유저가 없습니다.",
+ "members_popover.manageMembers": "회원 관리",
"members_popover.msg": "메시지",
"members_popover.title": "회원",
+ "members_popover.viewMembers": "회원 보기",
"mfa.confirm.complete": "<strong>Set up complete!</strong>",
"mfa.confirm.okay": "확인",
"mfa.confirm.secure": "Your account is now secure. Next time you sign in, you will be asked to enter a code from the Google Authenticator app on your phone.",
@@ -1665,6 +1689,7 @@
"post_info.mobile.unflag": "중요 해제",
"post_info.permalink": "링크",
"post_info.reply": "답글",
+ "post_message_view.edited": "(edited)",
"posts_view.loadMore": "메시지 더 보기",
"posts_view.newMsg": "새로운 메시지",
"posts_view.newMsgBelow": "New {count, plural, one {message} other {messages}} below",
@@ -1899,7 +1924,7 @@
"update_command.question": "Your changes may break the existing slash command. Are you sure you would like to update it?",
"update_command.update": "Update",
"upload_overlay.info": "이 곳에 파일을 끌어 업로드하세요.",
- "user.settings.advance.embed_preview": "가능한 경우, 링크 내용 미리보기를 표시",
+ "user.settings.advance.embed_preview": "For the first web link in a message, display a preview of website content below the message, if available",
"user.settings.advance.embed_toggle": "미리보기 토글 버튼 보여주기",
"user.settings.advance.enabledFeatures": "{count, number}개 기능 활성화",
"user.settings.advance.formattingDesc": "활성화 하면 링크, 이모티콘, 글자 스타일 등을 사용할 수 있습니다. 기본적으로 활성화 되있습니다. 설정을 변경하려면 페이지 새로고침이 필요합니다.",
@@ -1944,10 +1969,10 @@
"user.settings.display.channelDisplayTitle": "채널 화면 모드",
"user.settings.display.channeldisplaymode": "채널 영역의 너비를 선택하세요.",
"user.settings.display.clockDisplay": "시간 표시",
- "user.settings.display.collapseDesc": "미리보기가 가능한 콘텐츠의 미리보기를 펼친 상태로 표시합니다.",
- "user.settings.display.collapseDisplay": "링크 미리보기",
- "user.settings.display.collapseOff": "끄기",
- "user.settings.display.collapseOn": "켜기",
+ "user.settings.display.collapseDesc": "Set whether previews of image links show as expanded or collapsed by default. This setting can also be controlled using the slash commands /expand and /collapse.",
+ "user.settings.display.collapseDisplay": "Default appearance of image link previews",
+ "user.settings.display.collapseOff": "Collapsed",
+ "user.settings.display.collapseOn": "Expanded",
"user.settings.display.fixedWidthCentered": "고정 너비, 가운데",
"user.settings.display.fontDesc": "Mattermost 화면에서 보여질 폰트를 선택하세요.",
"user.settings.display.fontTitle": "폰트",
@@ -2130,7 +2155,9 @@
"user.settings.security.lastUpdated": "{date} {time} 에 마지막으로 변경됨",
"user.settings.security.ldap": "AD/LDAP",
"user.settings.security.loginGitlab": "Gitlab을 통해 로그인 되었습니다.",
+ "user.settings.security.loginGoogle": "Login done through Google Apps",
"user.settings.security.loginLdap": "LDAP을 통해 로그인 되었습니다.",
+ "user.settings.security.loginOffice365": "Login done through Office 365",
"user.settings.security.loginSaml": "Gitlab을 통해 로그인 되었습니다.",
"user.settings.security.logoutActiveSessions": "활성화 된 세션 보기/로그아웃 하기",
"user.settings.security.method": "접속 방식",
@@ -2159,9 +2186,11 @@
"user.settings.security.passwordErrorUppercaseNumberSymbol": "Your password must contain at least {min} characters made up of at least one uppercase letter, at least one number, and at least one symbol (e.g. \"~!@#$%^&*()\").",
"user.settings.security.passwordErrorUppercaseSymbol": "Your password must contain at least {min} characters made up of at least one uppercase letter and at least one symbol (e.g. \"~!@#$%^&*()\").",
"user.settings.security.passwordGitlabCantUpdate": "Login occurs through GitLab. Password cannot be updated.",
+ "user.settings.security.passwordGoogleCantUpdate": "Login occurs through GitLab. Password cannot be updated.",
"user.settings.security.passwordLdapCantUpdate": "Login occurs through LDAP. Password cannot be updated.",
"user.settings.security.passwordMatchError": "다시 입력한 새로운 패스워드가 일치하지 않습니다.",
"user.settings.security.passwordMinLength": "Invalid minimum length, cannot show preview.",
+ "user.settings.security.passwordOffice365CantUpdate": "Login occurs through GitLab. Password cannot be updated.",
"user.settings.security.passwordSamlCantUpdate": "This field is handled through your login provider. If you want to change it, you need to do so through your login provider.",
"user.settings.security.retypePassword": "새로운 패스워드 확인",
"user.settings.security.saml": "SAML",
diff --git a/webapp/i18n/nl.json b/webapp/i18n/nl.json
index 0cdb10289..664ccb14f 100644
--- a/webapp/i18n/nl.json
+++ b/webapp/i18n/nl.json
@@ -28,6 +28,7 @@
"activity_log.sessionsDescription": "Sessies worden gemaakt wanneer je inlogt met een nieuwe webbrowser. Met een sessie kan Mattermost je gebruiken zonder dat je opnieuw hoeft in te loggen tijdens de ingestelde periode. Om eerder uit te loggen, gebruik je de 'Afmelden'-knop hieronder.",
"activity_log_modal.android": "Android",
"activity_log_modal.androidNativeApp": "Android-applicatie",
+ "activity_log_modal.desktop": "Native Desktop App",
"activity_log_modal.iphoneNativeApp": "iPhone-app",
"add_command.autocomplete": "Automatisch aanvullen",
"add_command.autocomplete.help": "(Optioneel) Toon slash-commando's bij het automatisch aanvullen.",
@@ -260,6 +261,7 @@
"admin.email.notificationEmailTitle": "Melding afzender E-mailadres",
"admin.email.notificationOrganization": "Email Adres in voettekst van melding",
"admin.email.notificationOrganizationDescription": "Organisatie naam en adres weergegeven op e-mailberichten van Mattermost, zoals \"© ABC Corporation, 565 Knight Way, Palo Alto, Californië, 94305, USA\". Als het veld leeg wordt gelaten, wordt de naam en het adres van de organisatie niet weergegeven.",
+ "admin.email.notificationOrganizationExample": "Bijvoorbeeld \"© ABC Corporation, 565 Knight Way, Palo Alto, California, 94305, USA\"",
"admin.email.notificationsDescription": "Meestal ingesteld op ingeschakeld in productie. Wanneer dit ingeschakeld is, zal Mattermost e-mailberichten verzenden. Ontwikkelaars kunnen dit veld op uitgeschakeld zetten om e-mail instellen over te slaan voor een snellere ontwikkeling.<br />Wanneer ingeschakeld wordt de preview mode banner niet meer getoond (dit vereist uitloggen en opnieuw inloggen nadat deze instelling wordt gewijzigd).",
"admin.email.notificationsTitle": "Inschakelen van E-mail Meldingen: ",
"admin.email.passwordSaltDescription": "32-karakter 'salt' wordt gebruikt om wachtwoord reset mails te beveiligen. Deze wordt tijdens installatie aangemaakt. Klik op \"Opnieuw Genereren\" om een nieuwe 'salt' te maken.",
@@ -281,7 +283,7 @@
"admin.email.smtpPasswordExample": "Bijv. \"uwwachtwoord\", \"jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY\"",
"admin.email.smtpPasswordTitle": "SMTP server wachtwoord:",
"admin.email.smtpPortDescription": "De poort van de SMTP (mailserver).",
- "admin.email.smtpPortExample": "Ex: \"25\", \"465\", \"587\"",
+ "admin.email.smtpPortExample": "E.g.: \"25\", \"465\", \"587\"",
"admin.email.smtpPortTitle": "SMTP server poort :",
"admin.email.smtpServerDescription": "Naam of ip adres van de SMTP (mail)server.",
"admin.email.smtpServerExample": "Bijv. \"smtp.uwbedrijf.nl\", \"email-smtp.us-east-1.amazonaws.com\"",
@@ -308,10 +310,20 @@
"admin.general.localization.serverLocaleTitle": "Standaard server taal:",
"admin.general.log": "Loggen",
"admin.general.policy": "Beleid",
+ "admin.general.policy.allowEditPostAlways": "Any time",
+ "admin.general.policy.allowEditPostDescription": "Set policy on the length of time authors have to edit their messages after posting.",
+ "admin.general.policy.allowEditPostNever": "Nooit",
+ "admin.general.policy.allowEditPostTimeLimit": "seconds after posting",
+ "admin.general.policy.allowEditPostTitle": "Allow users to edit their messages:",
"admin.general.policy.permissionsAdmin": "Team en Systeem Admins",
"admin.general.policy.permissionsAll": "Alle teamleden",
"admin.general.policy.permissionsAllChannel": "All channel members",
+ "admin.general.policy.permissionsDeletePostAdmin": "Team en Systeem Admins",
+ "admin.general.policy.permissionsDeletePostAll": "Message authors can delete their own messages, and Administrators can delete any message",
+ "admin.general.policy.permissionsDeletePostSystemAdmin": "Systeem beheerders",
"admin.general.policy.permissionsSystemAdmin": "Systeem beheerders",
+ "admin.general.policy.restrictPostDeleteDescription": "Set policy on who has permission to delete messages.",
+ "admin.general.policy.restrictPostDeleteTitle": "Allow which users to delete messages:",
"admin.general.policy.restrictPrivateChannelCreationDescription": "Set policy on who can create private groups.",
"admin.general.policy.restrictPrivateChannelCreationTitle": "Enable private group creation for:",
"admin.general.policy.restrictPrivateChannelDeletionCommandLineToolLink": "command line tool",
@@ -363,6 +375,7 @@
"admin.image.amazonS3BucketExample": "Bijv.: \"mattermost-media\"",
"admin.image.amazonS3BucketTitle": "Amazon S3 Bucket:",
"admin.image.amazonS3EndpointDescription": "Hostname of your S3 Compatible Storage provider. Defaults to `s3.amazonaws.com`.",
+ "admin.image.amazonS3EndpointExample": "E.g.: \"s3.amazonaws.com\"",
"admin.image.amazonS3EndpointTitle": "Amazon S3 Endpoint:",
"admin.image.amazonS3IdDescription": "Verkrijg de credentials van uw Amazon EC2 beheerder.",
"admin.image.amazonS3IdExample": "Bijv.: \"AKIADTOVBGERKLCBV\"",
@@ -513,7 +526,7 @@
"admin.metrics.enableDescription": "When true, Mattermost will enable performance monitoring collection and profiling. Please see <a href=\"http://docs.mattermost.com/deployment/metrics.html\" target='_blank'>documentation</a> to learn more about configuring performance monitoring for Mattermost.",
"admin.metrics.enableTitle": "Enable Performance Monitoring:",
"admin.metrics.listenAddressDesc": "The address the server will listen on to expose performance metrics.",
- "admin.metrics.listenAddressEx": "Bijv. \":8065\"",
+ "admin.metrics.listenAddressEx": "Bijv.: \":8065\"",
"admin.metrics.listenAddressTitle": "Luister Adres:",
"admin.mfa.bannerDesc": "Multi-factor authentication is only available for accounts with LDAP and email login methods. If there are users on your system with other login methods, it is recommended you set up multi-factor authentication directly with the SSO or SAML provider.",
"admin.mfa.cluster": "High",
@@ -831,10 +844,10 @@
"admin.team.dirDesc": "Wanneer dit aanstaat, teams die geconfigureerd zijn om zichtbaar te zijn in de team directory zullen zichtbaar zijn op de hoofdpagina in plaats van het maken van een nieuw team.",
"admin.team.dirTitle": "Inschakelen Team Directory: ",
"admin.team.maxChannelsDescription": "Maximum aantal gebruikers per team, inclusief actieve en niet-actieve gebruikers.",
- "admin.team.maxChannelsExample": "Bijv. \"100\"",
+ "admin.team.maxChannelsExample": "Bijv.: \"100\"",
"admin.team.maxChannelsTitle": "Max Channels Per Team:",
"admin.team.maxNotificationsPerChannelDescription": "Maximum total number of users in a channel before users typing messages, @all, @here, and @channel no longer send notifications because of performance.",
- "admin.team.maxNotificationsPerChannelExample": "Bijv. \"10000\"",
+ "admin.team.maxNotificationsPerChannelExample": "Bijv.: \"10000\"",
"admin.team.maxNotificationsPerChannelTitle": "Max Notifications Per Channel:",
"admin.team.maxUsersDescription": "Maximum aantal gebruikers per team, inclusief actieve en niet-actieve gebruikers.",
"admin.team.maxUsersExample": "Bijv.: \"25\"",
@@ -923,8 +936,10 @@
"analytics.chart.meaningful": "Niet genoeg gegevens voor een zinvolle weergave.",
"analytics.system.activeUsers": "Actieve gebruikers met berichten",
"analytics.system.channelTypes": "Kanaal types",
+ "analytics.system.dailyActiveUsers": "Daily Active Users",
"analytics.system.expiredBanner": "De Enterprise licentie is verlopen op {date}. U heeft 15 dagen vanaf deze datum om de licentie te vernieuwen. Neem contact op met <a href='mailto:commercial@mattermost.com'>commercial@mattermost.com</a>.",
"analytics.system.expiringBanner": "De Enterprise licentie verloop op {date}. Om uw licentie te vernieuwen, neem contact op met <a href='mailto:commercial@mattermost.com'>commercial@mattermost.com</a>.",
+ "analytics.system.monthlyActiveUsers": "Monthly Active Users",
"analytics.system.postTypes": "Berichten, bestanden en hashtags",
"analytics.system.privateGroups": "Privé groepen",
"analytics.system.publicChannels": "Publieke kanalen",
@@ -1022,7 +1037,6 @@
"backstage_sidebar.integrations.outgoing_webhooks": "Uitgaande webhooks",
"calling_screen": "Oproep",
"center_panel.recent": "Klik hier om naar uw recente berichten te gaan. ",
- "chanel_header.addMembers": "Leden toevoegen",
"change_url.close": "Afsluiten",
"change_url.endWithLetter": "Moet eindigen met een letter of een cijfer",
"change_url.invalidUrl": "Ongeldige URL",
@@ -1040,6 +1054,7 @@
"channel_flow.handleTooShort": "Kanaal URL moet 2 of meer kleine alfanumerieke karakters bevatten",
"channel_flow.invalidName": "Ongeldige kanaal naam",
"channel_flow.set_url_title": "Instelling {term} URL",
+ "channel_header.addMembers": "Leden toevoegen",
"channel_header.addToFavorites": "Add to Favorites",
"channel_header.channel": "Kanaal",
"channel_header.channelHeader": "Edit Channel Header",
@@ -1079,10 +1094,14 @@
"channel_loader.uploadedFile": " heeft een bestand geüpload",
"channel_loader.uploadedImage": " heeft een afbeelding geüpload",
"channel_loader.wrote": " schreef: ",
+ "channel_members_dropdown.channel_admin": "Channel Admin",
+ "channel_members_dropdown.channel_member": "Kanaal Leden",
+ "channel_members_dropdown.make_channel_admin": "Make Channel Admin",
+ "channel_members_dropdown.make_channel_member": "Make Channel Member",
+ "channel_members_dropdown.remove_from_channel": "Remove From Channel",
+ "channel_members_dropdown.remove_member": "Remove Member",
"channel_members_modal.addNew": "Leden toevoegen",
- "channel_members_modal.close": "Afsluiten",
- "channel_members_modal.remove": "Verwijderen",
- "channel_memebers_modal.members": " Leden",
+ "channel_members_modal.members": " Leden",
"channel_modal.cancel": "Annuleren",
"channel_modal.channel": "Kanaal",
"channel_modal.createNew": "Maak nieuw ",
@@ -1091,6 +1110,7 @@
"channel_modal.edit": "Bewerken",
"channel_modal.group": "Groep",
"channel_modal.header": "Kop",
+ "channel_modal.headerEx": "E.g.: \"[Link Title](http://example.com)\"",
"channel_modal.headerHelp": "Geef de tekst die zal verschijnen in het hoofd van de {term} naast de {term} naam. Bijvoorbeeld, veelgebruikte links door het opgeven van [Link Titel](http://example.com).",
"channel_modal.modalTitle": "Nieuw ",
"channel_modal.name": "Naam",
@@ -1101,6 +1121,7 @@
"channel_modal.publicChannel1": "Maak een publiek kanaal",
"channel_modal.publicChannel2": "Maak een publiek kanaal waar iedereen lid van kan worden. ",
"channel_modal.purpose": "Doel",
+ "channel_modal.purposeEx": "E.g.: \"A channel to file bugs and improvements\"",
"channel_notifications.allActivity": "Voor alle activiteiten",
"channel_notifications.allUnread": "Voor alle ongelezen berichten",
"channel_notifications.globalDefault": "Globale standaard ({notifyLevel})",
@@ -1113,6 +1134,7 @@
"channel_notifications.unreadInfo": "De kanaal-naam is vet in de navigatiekolom wanneer er ongelezen berichten zijn. Wanneer \"Enkel bij vermeldingen\" is geselecteerd, zal het kanaal enkel vet zijn wanneer uw naam vermeld is.",
"channel_select.placeholder": "--- Selecteer een kanaal ---",
"channel_switch_modal.dm": "(Privé bericht)",
+ "channel_switch_modal.failed_to_open": "Failed to open channel.",
"channel_switch_modal.help": "Tik kanaal naam. Gebruik ↑↓ voor bladeren, TAB om te selecteren, ↵ voor bevestiging, ESC voor annuleren",
"channel_switch_modal.not_found": "Geen overeenkomsten gevonden.",
"channel_switch_modal.submit": "Wisselen",
@@ -1285,15 +1307,14 @@
"find_team.submitError": "Vul een geldig e-mail adres in",
"flag_post.flag": "Markeren voor vervolgactie",
"flag_post.unflag": "Demarkeer",
+ "general_tab.chooseDescription": "Kies een nieuwe naam voor uw team",
"general_tab.chooseName": "Kies een nieuwe naam voor uw team",
"general_tab.codeDesc": "Klik op 'Bewerken' om de uitnodigings-code opnieuw te genereren.",
"general_tab.codeLongDesc": "De Uitnodigings Code is onderdeel van de URL in de team uitnodigings link gemaakt door de <strong>Krijg Team Uitnodigings Link\"</strong> in het hoofdmenu. Hergenereren maakt een nieuwe team uitnodiging en zal de vorige link onbruikbaar maken.",
"general_tab.codeTitle": "Uitnodigings-code",
- "general_tab.dirDisabled": "Team Directory is uitgeschakeld. Vraag je systeembeheerder om de Team Directory aan te zetten in de Systeem Console team instellingen.",
- "general_tab.dirOff": "Team-adresboek staat uit voor dit systeem",
"general_tab.emptyDescription": "Click 'Edit' to add a team description.",
+ "general_tab.getTeamInviteLink": "Verkrijg de team uitnodigings-link",
"general_tab.includeDirDesc": "Invoegen van dit team zal de team naam zichtbaar maken in de Team Directory sectie op de hoofdpaginam en zal een link naar de login pagina maken.",
- "general_tab.includeDirTitle": "Voeg dit team toe aan aan de Team Gids",
"general_tab.no": "Nee",
"general_tab.openInviteDesc": "Wanneer dit is toegestaan, zal een link naar dit team worden opgenomen op de landings-pagina, zodat iedereen met een account mee kan doen met dit team.",
"general_tab.openInviteTitle": "Sta iedere gebruiker met een account op deze server om lid te worden van dit team",
@@ -1550,6 +1571,7 @@
"login.passwordChanged": " Wachtwoord succesvol bijgewerkt",
"login.session_expired": " Uw sessie is verlopen. Log opnieuw in.",
"login.signIn": "Log in",
+ "login.signInLoading": "Signing in...",
"login.signInWith": "Log in met:",
"login.userNotFound": "We konden geen account vinden met jouw inlog gegevens.",
"login.username": "Gebruikersnaam",
@@ -1561,8 +1583,10 @@
"member_item.makeAdmin": "Maak Admin",
"member_item.member": "Lid",
"member_list.noUsersAdd": "Geen gebruikers om toe te voegen.",
+ "members_popover.manageMembers": "Leden beheren",
"members_popover.msg": "Bericht",
"members_popover.title": " Leden",
+ "members_popover.viewMembers": "Bekijk Leden",
"mfa.confirm.complete": "<strong>Set up complete!</strong>",
"mfa.confirm.okay": "OK",
"mfa.confirm.secure": "Your account is now secure. Next time you sign in, you will be asked to enter a code from the Google Authenticator app on your phone.",
@@ -1665,6 +1689,7 @@
"post_info.mobile.unflag": "Demarkeer ",
"post_info.permalink": "Permanente koppeling",
"post_info.reply": "Antwoord",
+ "post_message_view.edited": "(edited)",
"posts_view.loadMore": "Laad meer berichten",
"posts_view.newMsg": "Nieuwe berichten",
"posts_view.newMsgBelow": "New {count, plural, one {message} other {messages}} below",
@@ -1899,7 +1924,7 @@
"update_command.question": "Your changes may break the existing slash command. Are you sure you would like to update it?",
"update_command.update": "Update",
"upload_overlay.info": "Sleep hier een bestand om te uploaden.",
- "user.settings.advance.embed_preview": "Toon experimentele previews van de link inhoud wanneer deze beschikbaar is.",
+ "user.settings.advance.embed_preview": "For the first web link in a message, display a preview of website content below the message, if available",
"user.settings.advance.embed_toggle": "Toon schakel optie voor alle ingesloten voorbeelden",
"user.settings.advance.enabledFeatures": "{count, number} {count, plural, one {Feature} other {Features}} Ingeschakeld",
"user.settings.advance.formattingDesc": "Indien ingeschakeld, worden berichten opgemaakt met links, emoji, stijl van de tekst, en regeleinden toevoegen. Standaard is deze instelling ingeschakeld. Het wijzigen van deze instelling vereist dat de pagina vernieuwd wordt.",
@@ -1944,10 +1969,10 @@
"user.settings.display.channelDisplayTitle": "Kanaal Schermmodus",
"user.settings.display.channeldisplaymode": "Kies de breedte van het center kanaal.",
"user.settings.display.clockDisplay": "Klok weergave",
- "user.settings.display.collapseDesc": "Vouw links uit om een voorvertoning van de inhoud te zien wanneer deze beschikbaar is.",
- "user.settings.display.collapseDisplay": "Link voorvertoningen",
- "user.settings.display.collapseOff": "Uit",
- "user.settings.display.collapseOn": "Aan",
+ "user.settings.display.collapseDesc": "Set whether previews of image links show as expanded or collapsed by default. This setting can also be controlled using the slash commands /expand and /collapse.",
+ "user.settings.display.collapseDisplay": "Default appearance of image link previews",
+ "user.settings.display.collapseOff": "Collapsed",
+ "user.settings.display.collapseOn": "Expanded",
"user.settings.display.fixedWidthCentered": "Vaste breedte, gecentreerd",
"user.settings.display.fontDesc": "Selecteer het lettertype dat door de Mattermost user interface wordt gebruikt.",
"user.settings.display.fontTitle": "Lettertype",
@@ -2130,7 +2155,9 @@
"user.settings.security.lastUpdated": "Laatst bijgewerkt op {date} {time}",
"user.settings.security.ldap": "AD/LDAP",
"user.settings.security.loginGitlab": "Inloggen via Gitlab",
+ "user.settings.security.loginGoogle": "Login done through Google Apps",
"user.settings.security.loginLdap": "Aanmelden via AD/LDAP",
+ "user.settings.security.loginOffice365": "Login done through Office 365",
"user.settings.security.loginSaml": "Inloggen via Gitlab",
"user.settings.security.logoutActiveSessions": "Bekijk en uitloggen huidige sessies",
"user.settings.security.method": "Inlog methode",
@@ -2159,9 +2186,11 @@
"user.settings.security.passwordErrorUppercaseNumberSymbol": "Jouw wachtwoord moet minimaal {min} karakters bevatten met minimaal een hoofdetter, een cijfer en een karakter (bv. \"~!@#$%^&*()\").",
"user.settings.security.passwordErrorUppercaseSymbol": "Jouw wachtwoord moet minimaal {min} karakters bevatten met minimaal een hoofdletter en een karakter (bv. \"~!@#$%^&*()\").",
"user.settings.security.passwordGitlabCantUpdate": "Aanmelden gebeurt via Gitlab. Uw wachtwoord kan niet bijgewerkt worden.",
+ "user.settings.security.passwordGoogleCantUpdate": "Aanmelden gebeurt via Gitlab. Uw wachtwoord kan niet bijgewerkt worden.",
"user.settings.security.passwordLdapCantUpdate": "Aanmelden gebeurt via AD/LDAP. Uw wachtwoord kan niet bijgewerkt worden.",
"user.settings.security.passwordMatchError": "De wachtwoorden die U ingaf zijn niet identiek",
"user.settings.security.passwordMinLength": "Verkeerde minimale lengte, kan de voorvertoning niet tonen.",
+ "user.settings.security.passwordOffice365CantUpdate": "Aanmelden gebeurt via Gitlab. Uw wachtwoord kan niet bijgewerkt worden.",
"user.settings.security.passwordSamlCantUpdate": "Dit veld word gebruikt door jouw login provider. Als je het wilt veranderen, moet je dat via de login provider doen. ",
"user.settings.security.retypePassword": "Nieuw wachtwoord herhalen",
"user.settings.security.saml": "SAML",
diff --git a/webapp/i18n/pt-BR.json b/webapp/i18n/pt-BR.json
index bbd12f671..a194ff246 100644
--- a/webapp/i18n/pt-BR.json
+++ b/webapp/i18n/pt-BR.json
@@ -28,6 +28,7 @@
"activity_log.sessionsDescription": "Sessões são criadas quando você efetuar login em um novo navegador em um dispositivo. Sessões permitem que você use Mattermost sem ter que logar novamente por um período de tempo especificado pelo administrador do sistema. Se você deseja sair mais cedo, use o botão 'Logout' abaixo para terminar uma sessão.",
"activity_log_modal.android": "Android",
"activity_log_modal.androidNativeApp": "App Nativo para Android",
+ "activity_log_modal.desktop": "Aplicativo Nativo Desktop",
"activity_log_modal.iphoneNativeApp": "App Nativo para iPhone",
"add_command.autocomplete": "Autocompletar",
"add_command.autocomplete.help": "(Opcional) Exibir comandos slash na lista de auto preenchimento.",
@@ -219,10 +220,10 @@
"admin.customization.iosAppDownloadLinkDesc": "Adiciona um link para download do app iOS. Usuários que acessarem o site em um navegador móvel serão perguntados com uma página dando a opção para download do app. Deixe este campo em branco para evitar que a página apareça.",
"admin.customization.iosAppDownloadLinkTitle": "App iOS Link para Download:",
"admin.customization.nativeAppLinks": "Links Aplicativo Mattermost",
- "admin.customization.restrictCustomEmojiCreationAdmin": "Habilitar criação de emoji personalizados para Administradores de Sistemas e de Times ",
+ "admin.customization.restrictCustomEmojiCreationAdmin": "Habilitar criação de emoji personalizados para Administradores de Sistema e de Times ",
"admin.customization.restrictCustomEmojiCreationAll": "Permitir que todos possam criar emoji personalizados",
"admin.customization.restrictCustomEmojiCreationDesc": "Restringir a criação de emoji personalizado para determinados usuários.",
- "admin.customization.restrictCustomEmojiCreationSystemAdmin": "Permitir que apenas administradores de sistema possam criar emoji personalizados",
+ "admin.customization.restrictCustomEmojiCreationSystemAdmin": "Permitir que apenas administradores do sistema possam criar emoji personalizados",
"admin.customization.restrictCustomEmojiCreationTitle": "Restringir a Criação Emoji Personalizado:",
"admin.customization.support": "Legal e Suporte",
"admin.database.title": "Configurações do Banco de dados",
@@ -256,10 +257,11 @@
"admin.email.notificationDisplayExample": "Ex: \"Mattermost Notificação\", \"Sistema\", \"Não-Responda\"",
"admin.email.notificationDisplayTitle": "Notificação Nome de Exibição:",
"admin.email.notificationEmailDescription": "Endereço de email mostrado na conta de email quando envia notificações do Mattermost.",
- "admin.email.notificationEmailExample": "Ex: \"mattermost@yourcompany.com\", \"admin@yourcompany.com\"",
+ "admin.email.notificationEmailExample": "Ex: \"mattermost@suaempresa.com\", \"admin@suaempresa.com\"",
"admin.email.notificationEmailTitle": "Notificação a Partir do Endereço:",
"admin.email.notificationOrganization": "Notificação Endereço Rodapé:",
"admin.email.notificationOrganizationDescription": "Nome da empresa e endereço de email mostrado nas notificações do Mattermost, como \"® Empresa ABC, Av. Paulista, 1000, São Paulo, SP, 12345-150, BRA\". Se este campo for deixado em branco, o nome da organização e endereço não será mostrado.",
+ "admin.email.notificationOrganizationExample": "Ex. \"® Empresa ABC, Av. Paulista, 1000, São Paulo, SP, 12345-150, BRA\"",
"admin.email.notificationsDescription": "Normalmente definido como verdadeiro em produção. Quando verdadeiro, Mattermost tenta enviar notificações por e-mail. Os desenvolvedores podem definir este campo como falso para ignorar configuração de e-mail para o desenvolvimento mais rápido.<br /> A definição deste como verdadeiro remove a bandeira modo de visualização (requer sair e entrar novamente após a alteração).",
"admin.email.notificationsTitle": "Habilitar Notificações por E-mail: ",
"admin.email.passwordSaltDescription": "Salt de 32-caracteres adicionado para assinar o redefinição de senha por e-mail. Gerada aleatoriamente na instalação. Clique em \"Re-Gerar\" para criar um novo salt.",
@@ -287,7 +289,7 @@
"admin.email.smtpServerExample": "Ex: \"smtp.suaempresa.com\", \"email-smtp.us-east-1.amazonaws.com\"",
"admin.email.smtpServerTitle": "Servidor SMTP:",
"admin.email.smtpUsernameDescription": " Obter essa credencial do administrador das configurações do servidor de email.",
- "admin.email.smtpUsernameExample": "Ex: \"admin@yourcompany.com\", \"AKIADTOVBGERKLCBV\"",
+ "admin.email.smtpUsernameExample": "Ex: \"admin@suaempresa.com\", \"AKIADTOVBGERKLCBV\"",
"admin.email.smtpUsernameTitle": "Nome do Usuário do Servidor de SMTP:",
"admin.email.testing": "Testando...",
"admin.false": "falso",
@@ -308,10 +310,20 @@
"admin.general.localization.serverLocaleTitle": "Idioma Padrão do Servidor:",
"admin.general.log": "Carregando",
"admin.general.policy": "Política",
+ "admin.general.policy.allowEditPostAlways": "A qualquer momento",
+ "admin.general.policy.allowEditPostDescription": "Definir a política sobre o período de tempo que os autores têm de editar suas mensagens após a publicação.",
+ "admin.general.policy.allowEditPostNever": "Nunca",
+ "admin.general.policy.allowEditPostTimeLimit": "segundos após a postagem",
+ "admin.general.policy.allowEditPostTitle": "Permitir que os usuários editem suas mensagens:",
"admin.general.policy.permissionsAdmin": "Administradores de Time e Sistema",
"admin.general.policy.permissionsAll": "Todos os membros da equipe",
"admin.general.policy.permissionsAllChannel": "Todos os membros do canal",
+ "admin.general.policy.permissionsDeletePostAdmin": "Administradores de Equipe e Sistema",
+ "admin.general.policy.permissionsDeletePostAll": "Autor da mensagem pode deletar sua própria mensagem, e Administradores podem deletar qualquer mensagem",
+ "admin.general.policy.permissionsDeletePostSystemAdmin": "Administrador de Sistema",
"admin.general.policy.permissionsSystemAdmin": "Administrador de Sistema",
+ "admin.general.policy.restrictPostDeleteDescription": "Define a política de quem tem permissão para deletar mensagens.",
+ "admin.general.policy.restrictPostDeleteTitle": "Permitir quais usuários a deletar mensagens:",
"admin.general.policy.restrictPrivateChannelCreationDescription": "Define a política sobre quem pode criar grupos privados.",
"admin.general.policy.restrictPrivateChannelCreationTitle": "Ativar a criação de grupos privados para:",
"admin.general.policy.restrictPrivateChannelDeletionCommandLineToolLink": "ferramenta de linha de comando",
@@ -363,6 +375,7 @@
"admin.image.amazonS3BucketExample": "Ex.: \"mattermost-media\"",
"admin.image.amazonS3BucketTitle": "Amazon S3 Bucket:",
"admin.image.amazonS3EndpointDescription": "Nome do host provedor de armazenamento compatível com S3. O padrão é `s3.amazonaws.com`.",
+ "admin.image.amazonS3EndpointExample": "Ex.: \"s3.amazonaws.com\"",
"admin.image.amazonS3EndpointTitle": "Amazon S3 Endpoint:",
"admin.image.amazonS3IdDescription": "Obter essa credencial do seu administrador Amazon EC2.",
"admin.image.amazonS3IdExample": "Ex.: \"AKIADTOVBGERKLCBV\"",
@@ -513,7 +526,7 @@
"admin.metrics.enableDescription": "Quando verdadeiro, Mattermost irá habilitar a coleta do monitoramento de performance e profiling. Por favor verifique <a href=\"http://docs.mattermost.com/deployment/metrics.html\" target='_blank'>documentação</a> para ler mais sobre como configurar o monitoramento de performance para Mattermost.",
"admin.metrics.enableTitle": "Habilitar Monitoramento de Performance:",
"admin.metrics.listenAddressDesc": "O endereço que o servidor irá escutar para expor as métricas de performance.",
- "admin.metrics.listenAddressEx": "Ex \":8067\"",
+ "admin.metrics.listenAddressEx": "Ex.: \":8067\"",
"admin.metrics.listenAddressTitle": "Endereço à escutar:",
"admin.mfa.bannerDesc": "Autenticação por Multi-fator está apenas disponível para contas com LDAP e login por email. Se existir algum usuário no sistema com outro tipo de login, é recomendado que você configure a autenticação por multi-fator diretamente com o provedor de SSO ou SAML.",
"admin.mfa.cluster": "Alta",
@@ -564,7 +577,7 @@
"admin.rate.enableLimiterTitle": "Ativar Limitador de Velocidade: ",
"admin.rate.httpHeaderDescription": "Quando preenchido, a limitação de velocidade varia pelo campo do cabeçalho HTTP especificado (ex. quando configurado NGINX ajustado para \"X-Real-IP\", quando configurado AmazonELB ajustado para \"X-Forwarded-For\").",
"admin.rate.httpHeaderExample": "Ex.: \"X-Real-IP\", \"X-Forwarded-For\"",
- "admin.rate.httpHeaderTitle": "Variar o limite de velocidade pelo cabeçalho HTTP",
+ "admin.rate.httpHeaderTitle": "Variar o limite de velocidade pelo cabeçalho HTTP:",
"admin.rate.maxBurst": "Máximo Tamanho Burst:",
"admin.rate.maxBurstDescription": "Máximo número de pedidos permitidos além do limite de consultas por segundo.",
"admin.rate.maxBurstExample": "Ex.: \"100\"",
@@ -597,7 +610,7 @@
"admin.saml.assertionConsumerServiceURLDesc": "Digite https://<sua-url-mattermost>/login/sso/saml. Certifique-se de usar HTTP ou HTTPS em sua URL, dependendo da sua configuração do servidor. Este campo também é conhecido como Assertion Consumer Service URL.",
"admin.saml.assertionConsumerServiceURLEx": "Ex \"https://<sua-url-mattermost>/login/sso/saml\"",
"admin.saml.assertionConsumerServiceURLTitle": "URL Provedor de Serviço de Login:",
- "admin.saml.bannerDesc": "User attributes in SAML server, including user deactivation or removal, are updated in Mattermost during user login. Learn more at: <a href=\"https://docs.mattermost.com/deployment/sso-saml.html\">https://docs.mattermost.com/deployment/sso-saml.html</a>",
+ "admin.saml.bannerDesc": "Os atributos de usuário no servidor SAML, incluindo a desativação ou remoção do usuário, são atualizados no Mattermost durante o login do usuário. Leia mais em <a href=\"https://docs.mattermost.com/deployment/sso-saml.html\">https://docs.mattermost.com/deployment/sso-saml.html</a>",
"admin.saml.emailAttrDesc": "O atributo na SAML Assertion que será usado para preencher os endereços de email dos usuários em Mattermost.",
"admin.saml.emailAttrEx": "Ex.: \"Email\" ou \"PrimaryEmail\"",
"admin.saml.emailAttrTitle": "Atributo de E-mail:",
@@ -754,7 +767,7 @@
"admin.sidebar.legalAndSupport": "Legal e Suporte",
"admin.sidebar.license": "Edição e Licença",
"admin.sidebar.localization": "Regionalização",
- "admin.sidebar.logging": "Acesso",
+ "admin.sidebar.logging": "Logs",
"admin.sidebar.login": "Login",
"admin.sidebar.logs": "Logs",
"admin.sidebar.metrics": "Monitoramento de Performance (Beta)",
@@ -831,10 +844,10 @@
"admin.team.dirDesc": "Quando verdadeiro, as equipes que estão configuradas para mostrar o diretório de equipe irá mostrar na página principal, em lugar de criar uma nova equipe.",
"admin.team.dirTitle": "Ativar Diretório de Equipe: ",
"admin.team.maxChannelsDescription": "Número máximo total de canais por equipe, incluindo ambos canais ativos e inativos.",
- "admin.team.maxChannelsExample": "Ex \"100\"",
+ "admin.team.maxChannelsExample": "Ex.: \"100\"",
"admin.team.maxChannelsTitle": "Máximo Canais Por Equipe:",
"admin.team.maxNotificationsPerChannelDescription": "Número total máximo de usuários em um canal antes de os usuários digitarem as mensagens, @all, @here e @channel não será enviado notificações por motivos de performance.",
- "admin.team.maxNotificationsPerChannelExample": "Ex \"1000\"",
+ "admin.team.maxNotificationsPerChannelExample": "Ex.: \"1000\"",
"admin.team.maxNotificationsPerChannelTitle": "Máximo de Notificações por Canal",
"admin.team.maxUsersDescription": "Número máximo total de usuários por equipe, incluindo ambos usuários ativos e inativos.",
"admin.team.maxUsersExample": "Ex.: \"25\"",
@@ -901,13 +914,13 @@
"admin.webrtc.gatewayWebsocketUrlTitle": "Gateway Websocket URL:",
"admin.webrtc.stunUriDescription": "Digite sua STUN URI como stun:<your-stun-url>:<port>. STUN é um protocolo de rede padronizado que permite que um host de destino auxilie dispositivos para estabelecer uma conexão usando um endereço IP público se este está localizado atrás de um NAT.",
"admin.webrtc.stunUriExample": "Ex.: \"stun:webrtc.mattermost.com:5349\"",
- "admin.webrtc.stunUriTitle": "STUN URI",
+ "admin.webrtc.stunUriTitle": "STUN URI:",
"admin.webrtc.turnSharedKeyDescription": "Digite sua Chave Compartilhada TURN. Isto é usado para criar senhas dinâmicas para estabelecer a conexão. Cada senha é válida para um período de tempo curto.",
"admin.webrtc.turnSharedKeyExample": "Ex.: \"bXdkOWQxc3d0Ynk3emY5ZmsxZ3NtazRjaWg=\"",
"admin.webrtc.turnSharedKeyTitle": "Chave Compartilhada TURN:",
"admin.webrtc.turnUriDescription": "Digite sua TURN URI como turn:<your-turn-url>:<port>. TURN é um protocolo de rede padronizado que permite que um host de destino auxilie dispositivos para estabelecer uma conexão usando um endereço IP público se este está localizado atrás de um NAT simétrico.",
"admin.webrtc.turnUriExample": "Ex.: \"turn:webrtc.mattermost.com:5349\"",
- "admin.webrtc.turnUriTitle": "TURN URI",
+ "admin.webrtc.turnUriTitle": "TURN URI:",
"admin.webrtc.turnUsernameDescription": "Digite seu Usuário para o Servidor TURN.",
"admin.webrtc.turnUsernameExample": "Ex.: \"meuusuario\"",
"admin.webrtc.turnUsernameTitle": "Usuário TURN:",
@@ -923,8 +936,10 @@
"analytics.chart.meaningful": "Não há dados suficientes para uma representação significativa.",
"analytics.system.activeUsers": "Usuários Ativos Com Posts",
"analytics.system.channelTypes": "Tipos de Canal",
+ "analytics.system.dailyActiveUsers": "Daily Active Users",
"analytics.system.expiredBanner": "A licença Enterprise expirou em {date}. Você tem 15 dias a partir desta data para renovar a licença, por favor contate <a href='mailto:commercial@mattermost.com'>commercial@mattermost.com</a>.",
"analytics.system.expiringBanner": "A licença Enterprise termina em {date}. Para renovar sua licença, por favor contate <a href='mailto:commercial@mattermost.com'>commercial@mattermost.com</a>.",
+ "analytics.system.monthlyActiveUsers": "Monthly Active Users",
"analytics.system.postTypes": "Posts, Arquivos e Hashtags",
"analytics.system.privateGroups": "Grupos Privados",
"analytics.system.publicChannels": "Canais Públicos",
@@ -1022,7 +1037,6 @@
"backstage_sidebar.integrations.outgoing_webhooks": "Webhooks Saída",
"calling_screen": "Chamando",
"center_panel.recent": "Clique aqui para pular para mensagens recentes. ",
- "chanel_header.addMembers": "Adicionar Membros",
"change_url.close": "Fechar",
"change_url.endWithLetter": "Deve teminar com uma letra ou número",
"change_url.invalidUrl": "URL inválida",
@@ -1040,6 +1054,7 @@
"channel_flow.handleTooShort": "URL do canal precisa ter 2 ou mais caracteres minúsculos alfanuméricos",
"channel_flow.invalidName": "Nome do Canal Inválido",
"channel_flow.set_url_title": "Ajustar URL {term}",
+ "channel_header.addMembers": "Adicionar Membros",
"channel_header.addToFavorites": "Adicionar aos Favoritos",
"channel_header.channel": "Canal",
"channel_header.channelHeader": "Editar Cabeçalho do Canal",
@@ -1079,10 +1094,14 @@
"channel_loader.uploadedFile": " enviado um arquivo",
"channel_loader.uploadedImage": " enviado uma imagem",
"channel_loader.wrote": " escreveu: ",
+ "channel_members_dropdown.channel_admin": "Administrador de Canal",
+ "channel_members_dropdown.channel_member": "Membro do Canal",
+ "channel_members_dropdown.make_channel_admin": "Tornar Administrador de Canal",
+ "channel_members_dropdown.make_channel_member": "Tornar Membro de Canal",
+ "channel_members_dropdown.remove_from_channel": "Remover do Canal",
+ "channel_members_dropdown.remove_member": "Remover Membro",
"channel_members_modal.addNew": " Adicionar Novos Membros",
- "channel_members_modal.close": "Fechar",
- "channel_members_modal.remove": "Remover",
- "channel_memebers_modal.members": " Membros",
+ "channel_members_modal.members": " Membros",
"channel_modal.cancel": "Cancelar",
"channel_modal.channel": "Canal",
"channel_modal.createNew": "Criar Novo ",
@@ -1091,6 +1110,7 @@
"channel_modal.edit": "Editar",
"channel_modal.group": "Grupo",
"channel_modal.header": "Cabeçalho",
+ "channel_modal.headerEx": "Ex.: \"[Título do Link](http://example.com)\"",
"channel_modal.headerHelp": "Configure o texto que irá aparecer no topo do {term} ao lado do nome {term}. Por exemplo, inclua os links utilizados frequentemente digitando [Link Title](http://example.com).",
"channel_modal.modalTitle": "Novo ",
"channel_modal.name": "Nome",
@@ -1101,6 +1121,7 @@
"channel_modal.publicChannel1": "Criar um canal público",
"channel_modal.publicChannel2": "Criar um novo canal público para qualquer um participar. ",
"channel_modal.purpose": "Propósito",
+ "channel_modal.purposeEx": "Ex.: \"Um canal para arquivar bugs e melhorias\"",
"channel_notifications.allActivity": "Para todas as atividades",
"channel_notifications.allUnread": "Para todas as mensagens não lidas",
"channel_notifications.globalDefault": "Global padrão ({notifyLevel})",
@@ -1113,6 +1134,7 @@
"channel_notifications.unreadInfo": "O nome do canal fica em negrito na barra lateral quando houver mensagens não lidas. Selecionando \"Apenas menções\" o canal vai ficar em negrito apenas quando você for mencionado.",
"channel_select.placeholder": "--- Selecione um canal ---",
"channel_switch_modal.dm": "(Mensagem Direta)",
+ "channel_switch_modal.failed_to_open": "Falha ao abrir o canal.",
"channel_switch_modal.help": "Digite o nome do canal. Use ↑↓ para navegar, TAB para selecionar, ↵ para confirmar, ESC para cancelar",
"channel_switch_modal.not_found": "Nenhum resultado encontrado.",
"channel_switch_modal.submit": "Alternar",
@@ -1197,7 +1219,7 @@
"delete_channel.confirm": "Confirmar EXCLUSÃO do Canal",
"delete_channel.del": "Deletar",
"delete_channel.group": "grupo",
- "delete_channel.question": "Isto irá apagar o canal do equipe e todo o conteúdo não vai estar disponível para os usuários. Você está certo que deseja apagar {display_name} {term}?",
+ "delete_channel.question": "Isto irá apagar o canal da equipe e todo o conteúdo não vai estar mais disponível para os usuários. Você tem certeza de que deseja apagar o {term} {display_name}?",
"delete_post.cancel": "Cancelar",
"delete_post.comment": "Comentário",
"delete_post.confirm": "Confirmar Delete {term}",
@@ -1285,15 +1307,14 @@
"find_team.submitError": "Por favor entre um endereço de e-mail válido",
"flag_post.flag": "Marcar para seguir",
"flag_post.unflag": "Desmarcar",
+ "general_tab.chooseDescription": "Por favor escolha uma nova descrição para sua equipe",
"general_tab.chooseName": "Por favor escolha um novo nome para sua equipe",
"general_tab.codeDesc": "Clique 'Edit' para re-gerar o Código de Convite.",
- "general_tab.codeLongDesc": "O Código de convite é usado como parte da URL no link de convite da equipe criado por <strong>Obter Link de Convite de Equipe</strong> no menu principal. Re-gerar cria um novo link de convite de equipe e invalida os link anteriores.",
+ "general_tab.codeLongDesc": "O Código de convite é usado como parte da URL no link de convite da equipe criado por {getTeamInviteLink} no menu principal. Re-gerar cria um novo link de convite de equipe e invalida os link anteriores.",
"general_tab.codeTitle": "Código de Convite",
- "general_tab.dirDisabled": "Diretório de equipe foi desativado. Por favor peça a um Administrador de Sistema para ativar o Diretório de Equipe nas configurações do Console do Sistema.",
- "general_tab.dirOff": "Diretório de equipe está desativado para este sistema.",
"general_tab.emptyDescription": "Clique 'Editar' para adicionar uma descrição da equipe",
+ "general_tab.getTeamInviteLink": "Obter Link de Convite de Equipe",
"general_tab.includeDirDesc": "Incluindo esta equipe irá exibir o nome da equipe da seção Diretório Equipe da página inicial, e fornecer um link para a página de login.",
- "general_tab.includeDirTitle": "Incluir esta equipe no Diretório de Equipe",
"general_tab.no": "Não",
"general_tab.openInviteDesc": "Quando permitido, um link para esta equipe vai ser incluído na página de destino permitindo que qualquer pessoa com uma conta possa participar desta equipe.",
"general_tab.openInviteTitle": "Permitir que qualquer usuário com uma conta neste servidor possa se juntar a esta equipe",
@@ -1321,7 +1342,7 @@
"get_post_link_modal.title": "Copiar Permalink",
"get_public_link_modal.help": "O link abaixo permite que qualquer pessoa veja este arquivo sem ser registrado neste servidor.",
"get_public_link_modal.title": "Obter Link Público",
- "get_team_invite_link_modal.help": "Enviar o link abaixo para sua equipe de trabalho para que eles se inscrevam no site da sua equipe. O Link de Convite de Equipe como ele não muda pode ser compartilhado com várias pessoas ao menos que seja re-gerado em Configurações de Equipe pelo Administrador de Equipe.",
+ "get_team_invite_link_modal.help": "Envie o link abaixo para sua equipe de trabalho para que eles se inscrevam nessa equipe. O Link de Convite de Equipe pode ser compartilhado com vários colegas de equipe, pois ele não muda a menos que seja re-gerado nas Configurações de Equipe por um Administrador da mesma.",
"get_team_invite_link_modal.helpDisabled": "Criação de usuários está desabilitada para sua equipe. Por favor peça ao administrador de equipe por detalhes.",
"get_team_invite_link_modal.title": "Link para Convite de Equipe",
"help.attaching.downloading": "#### Download Arquivos\nFazer download de um arquivo anexado clicando no ícone de download ao lado da miniatura de arquivos ou abrindo o visualizador de arquivos e clicando em **Download**.",
@@ -1344,7 +1365,7 @@
"help.commands.intro": "Comandos slash executam operações no Mattermost digitando na caixa de entrada de texto. Digite `/` seguido por um comando e alguns argumentos para executar ações.\n\nComandos slash nativos vêm com todas as instalações Mattermost e comandos slash personalizado são configurados para interagir com aplicações externas. Saiba mais sobre a configuração de comandos slash personalizados em [developer slash command documentation page](http://docs.mattermost.com/developer/slash-commands.html).",
"help.commands.title": "# Executando Comandos\n___",
"help.composing.deleting": "## Deletando uma mensagem\nDelete uma mensagem clicando no ícone **[...]** ao lado do texto da mensagem que você escreveu, em seguida, clique em **Deletar**. Administrador de Sistema e de Equipe podem excluir qualquer mensagem em seu sistema ou equipe.",
- "help.composing.editing": "## Edição de Mensagem\nEditar uma mensagem clicando no ícone **[...]** ao lado o texto da mensagem que você compôs, em seguida, clique em **Editar**. Depois de fazer modificações no texto da mensagem, pressione **ENTER** para salvar as modificações. Edições mensagem não desencadeiam novas notificações de @menção, notificações de área de trabalho ou sons de notificação.",
+ "help.composing.editing": "## Edição de Mensagem\nEdite uma mensagem clicando no ícone **[...]** ao lado do texto de qualquer mensagem que você compôs, em seguida, clique em **Editar**. Após fazer modificações no texto da mensagem, pressione **ENTER** para salvá-las. Edições em mensagens não geram novas notificações de @menção, notificações da área de trabalho ou sons de notificação.",
"help.composing.linking": "## Link para uma mensagem\nO recurso **Permalink** cria um link para qualquer mensagem. Compartilhar este link com outros usuários no canal lhes permite visualizar a mensagem lincada no Arquivos de Mensagem. Os usuários que não são membros do canal onde a mensagem foi postada não podem ver o permalink. Obter o permalink de qualquer mensagem clicando no ícone **[...]** ao lado do texto da mensagem > **Permalink** > **Copiar Link**.",
"help.composing.posting": "## Postando uma Mensagem\nEscreva uma mensagem digitando na caixa de entrada de texto, em seguida, pressione **ENTER** para enviá-la. Use **Shift + ENTER** para criar uma nova linha sem enviar uma mensagem. Para enviar mensagens pressionando **CTRL + ENTER** vá para **Menu Principal > Configurações de Conta > Enviar mensagens com CTRL + ENTER**.",
"help.composing.posts": "#### Posts\nPosts podem ser consideradas as mensagens principais. Eles são as mensagens que, muitas vezes iniciam uma discussão com respostas. Posts são criados e enviados a partir da caixa de entrada de texto na parte inferior do painel central.",
@@ -1550,6 +1571,7 @@
"login.passwordChanged": " Senha atualizada com sucesso",
"login.session_expired": " Sua sessão expirou. Por favor faça login novamente.",
"login.signIn": "Login",
+ "login.signInLoading": "Iniciando a sessão...",
"login.signInWith": "Login com:",
"login.userNotFound": "Não foi possível encontrar uma conta que corresponda com as suas credenciais de login.",
"login.username": "Usuário",
@@ -1561,8 +1583,10 @@
"member_item.makeAdmin": "Tornar Admin",
"member_item.member": "Membro",
"member_list.noUsersAdd": "Nenhum usuário para adicionar.",
+ "members_popover.manageMembers": "Gerenciar Membros",
"members_popover.msg": "Mensagem",
"members_popover.title": "Membros",
+ "members_popover.viewMembers": "Ver Membros",
"mfa.confirm.complete": "<strong>Configuração completa!</strong>",
"mfa.confirm.okay": "Ok",
"mfa.confirm.secure": "Sua conta agora está segura. Na próxima vez que você fizer login, será pedido a você um código do aplicativo Google Authenticator",
@@ -1626,7 +1650,7 @@
"navbar_dropdown.report": "Relatar um Problema",
"navbar_dropdown.switchTeam": "Alternar para {team}",
"navbar_dropdown.switchTo": "Alternar para ",
- "navbar_dropdown.teamLink": "Obter Link Convite de Equipe",
+ "navbar_dropdown.teamLink": "Obter Link de Convite de Equipe",
"navbar_dropdown.teamSettings": "Configurações da Equipe",
"navbar_dropdown.viewMembers": "Ver Membros",
"notification.dm": "Mensagem Direta",
@@ -1651,7 +1675,7 @@
"permalink.error.access": "O permalink pertence a uma mensagem deletada ou a um canal o qual você não tem acesso.",
"post_attachment.collapse": "Mostrar menos...",
"post_attachment.more": "Mostrar mais...",
- "post_body.commentedOn": "Comentado da mensagem {name}{apostrophe}: ",
+ "post_body.commentedOn": "Comentado da mensagem de {name}: ",
"post_body.deleted": "(mensagem deletada)",
"post_body.plusMore": " mais {count} outros arquivos",
"post_body.plusOne": " mais 1 outro arquivo",
@@ -1665,6 +1689,7 @@
"post_info.mobile.unflag": "Desmarcar",
"post_info.permalink": "Permalink",
"post_info.reply": "Responder",
+ "post_message_view.edited": "(editado)",
"posts_view.loadMore": "Carregar mais mensagens",
"posts_view.newMsg": "Novas Mensagens",
"posts_view.newMsgBelow": "{count} {count, plural, one {nova mensagem} other {novas mensagens}} abaixo",
@@ -1767,7 +1792,7 @@
"sidebar_right_menu.nativeApps": "Download Aplicativos",
"sidebar_right_menu.recentMentions": "Menções Recentes",
"sidebar_right_menu.report": "Relatar um Problema",
- "sidebar_right_menu.teamLink": "Obter Link Convite de Equipe",
+ "sidebar_right_menu.teamLink": "Obter Link de Convite de Equipe",
"sidebar_right_menu.teamSettings": "Configurações da Equipe",
"sidebar_right_menu.viewMembers": "Ver Membros",
"signup.email": "Email e Senha",
@@ -1899,12 +1924,12 @@
"update_command.question": "Suas alterações podem fazer parar de funcionar o comando slash existente. Tem a certeza de que pretende atualizá-lo?",
"update_command.update": "Atualizar",
"upload_overlay.info": "Soltar um arquivo para enviá-lo.",
- "user.settings.advance.embed_preview": "Mostrar pré-visualização experimental de conteúdo de link, quando disponível",
+ "user.settings.advance.embed_preview": "For the first web link in a message, display a preview of website content below the message, if available",
"user.settings.advance.embed_toggle": "Exibir mostrar/esconder para todas as pre-visualizações",
"user.settings.advance.enabledFeatures": "{count, number} {count, plural, one {Recurso} other {Recursos}} Ativado",
"user.settings.advance.formattingDesc": "Se ativado, posts serão formatados para criar links, exibir emoji, estilo de texto e adicionar quebra de linhas. Por padrão é definido como ativado. Mudando está configuração será necessário recarregar a página.",
"user.settings.advance.formattingTitle": "Ativar Formatação de Post",
- "user.settings.advance.joinLeaveDesc": "Quando \"Ligado\", Mensagens do Sistema dizendo que um usuário entrou ou saiu de um canal será visível. Quando \"Off\", as Mensagens do Sistema sobre entrar ou sair de um canal serão ocultados. A mensagem ainda vai aparecer quando são adicionados a um canal, para que você possa receber uma notificação.",
+ "user.settings.advance.joinLeaveDesc": "Quando \"Ligado\", Mensagens do Sistema dizendo que um usuário entrou ou saiu de um canal serão visíveis. Quando \"Off\", as Mensagens do Sistema sobre entrada e saída de um canal serão ocultadas. Uma mensagem ainda vai aparecer quando você é adicionado a um canal para que você possa receber uma notificação.",
"user.settings.advance.joinLeaveTitle": "Ativar Mensagens Juntar/Deixar",
"user.settings.advance.markdown_preview": "Mostrar opção pré-visualização markdown na caixa de entrada de mensagens",
"user.settings.advance.off": "Desligado",
@@ -1944,10 +1969,10 @@
"user.settings.display.channelDisplayTitle": "Modo de Exibição do Canal",
"user.settings.display.channeldisplaymode": "Selecione a largura do centro do canal.",
"user.settings.display.clockDisplay": "Exibição do Relógio",
- "user.settings.display.collapseDesc": "Expandir links para mostrar pré-visualização de conteúdo, quando disponível.",
- "user.settings.display.collapseDisplay": "Visualização de link",
- "user.settings.display.collapseOff": "Desligado",
- "user.settings.display.collapseOn": "Ligado",
+ "user.settings.display.collapseDesc": "Set whether previews of image links show as expanded or collapsed by default. This setting can also be controlled using the slash commands /expand and /collapse.",
+ "user.settings.display.collapseDisplay": "Default appearance of image link previews",
+ "user.settings.display.collapseOff": "Collapsed",
+ "user.settings.display.collapseOn": "Expanded",
"user.settings.display.fixedWidthCentered": "Largura fixa, centralizada",
"user.settings.display.fontDesc": "Selecione a fonte mostrada na interface do usuário no Mattermost.",
"user.settings.display.fontTitle": "Fonte Exibição",
@@ -2023,7 +2048,7 @@
"user.settings.general.validEmail": "Por favor entre um endereço de e-mail válido",
"user.settings.general.validImage": "Somente imagens em JPG ou PNG podem ser usadas como imagem do perfil",
"user.settings.import_theme.cancel": "Cancelar",
- "user.settings.import_theme.importBody": "Para importar um tema, vá para uma equipe no Slack e olhe para “Preferences -> Sidebar Theme”. Abra a opção de tema personalizado, copie os valores das cores do tema e cole eles aqui:",
+ "user.settings.import_theme.importBody": "Para importar um tema, vá para uma equipe do Slack e procure por “Preferences -> Sidebar Theme”. Abra a opção de tema personalizado, copie os valores das cores do tema e cole-os aqui:",
"user.settings.import_theme.importHeader": "Importar Tema Slack",
"user.settings.import_theme.submit": "Enviar",
"user.settings.import_theme.submitError": "Formato inválido, por favor tente copiar e colar novamente.",
@@ -2130,7 +2155,9 @@
"user.settings.security.lastUpdated": "Última atualização {date} {time}",
"user.settings.security.ldap": "AD/LDAP",
"user.settings.security.loginGitlab": "Login feito através do GitLab",
+ "user.settings.security.loginGoogle": "Login feito através do Google Apps",
"user.settings.security.loginLdap": "Login feito através de AD/LDAP",
+ "user.settings.security.loginOffice365": "Login feito através do Office 365",
"user.settings.security.loginSaml": "Login feito através do SAML",
"user.settings.security.logoutActiveSessions": "Ver e fazer Logout das Sessões Ativas",
"user.settings.security.method": "Método de Login",
@@ -2159,9 +2186,11 @@
"user.settings.security.passwordErrorUppercaseNumberSymbol": "Sua senha deve conter pelo menos {min} caracteres constituídos por pelo menos uma letra maiúscula, pelo menos um número, e pelo menos um símbolo (ex. \"~!@#$%^&*()\").",
"user.settings.security.passwordErrorUppercaseSymbol": "Sua senha deve conter pelo menos {min} caracteres constituídos por pelo menos uma letra maiúscula e pelo menos um símbolo (ex. \"~!@#$%^&*()\").",
"user.settings.security.passwordGitlabCantUpdate": "Login ocorreu através do GitLab. Senha não pode ser atualizada.",
+ "user.settings.security.passwordGoogleCantUpdate": "Login ocorreu através do Google Apps. Senha não pode ser atualizada.",
"user.settings.security.passwordLdapCantUpdate": "Login ocorreu através de AD/LDAP. Senha não pode ser atualizada.",
"user.settings.security.passwordMatchError": "As novas senhas que você inseriu não correspondem.",
"user.settings.security.passwordMinLength": "Comprimento mínimo inválido, não é possível mostrar pré-visualização.",
+ "user.settings.security.passwordOffice365CantUpdate": "Login ocorreu através do Office 365. Senha não pode ser atualizada.",
"user.settings.security.passwordSamlCantUpdate": "Este campo é tratada pelo seu provedor de login. Se você quiser mudá-lo, você precisa fazê-lo através de seu provedor de login.",
"user.settings.security.retypePassword": "Digite Novamente a nova Senha",
"user.settings.security.saml": "SAML",
diff --git a/webapp/i18n/ru.json b/webapp/i18n/ru.json
index 0100cd97e..1036e365b 100644
--- a/webapp/i18n/ru.json
+++ b/webapp/i18n/ru.json
@@ -28,6 +28,7 @@
"activity_log.sessionsDescription": "Сессии создаются когда вы входите через новый браузер на устройстве. Они позволяют использовать Mattermost без необходимости повторного входа на протяжении времени установленого Системным Администратором. Если вы хотите выйти раньше, используйте кнопку 'Выход' ниже, чтобы завершить сессию.",
"activity_log_modal.android": "Android",
"activity_log_modal.androidNativeApp": "Приложение Android",
+ "activity_log_modal.desktop": "Native Desktop App",
"activity_log_modal.iphoneNativeApp": "Приложение для iPhone",
"add_command.autocomplete": "Автодополнение",
"add_command.autocomplete.help": "(Необязательно) Показывать слэш-команду в списке автодополнения.",
@@ -244,7 +245,7 @@
"admin.email.enableEmailBatchingTitle": "Включить почтовые объединения:",
"admin.email.fullPushNotification": "Послать полный фрагмент сообщения",
"admin.email.genericPushNotification": "Отправить общее описание с именами пользователей и каналов",
- "admin.email.inviteSaltDescription": "32-символа \"соли\" добавлено в приглашении по эл. почте. Случайно сгенерированных при установке.Кликните \"Перегенерировать\" чтобы создать новую \"соль\".",
+ "admin.email.inviteSaltDescription": "32-символа \"соли\" для подписи приглашений по эл. почте. При установке создается случайная \"соль\". Нажмите \"Перегенерировать\" чтобы создать новую.",
"admin.email.inviteSaltExample": "Например: \"bjlSR4QqkXFBr7TP4oDzlfZmcNuH9Yo\"",
"admin.email.inviteSaltTitle": "\"Соль\" для почтового приглашения:",
"admin.email.mhpns": "Используйте шифрованное, качественное HPNS соединение с iOS и Android приложениями",
@@ -260,6 +261,7 @@
"admin.email.notificationEmailTitle": "Адрес электронной почты для уведомлений:",
"admin.email.notificationOrganization": "Адрес эл. почты в нижнем колонтитуле:",
"admin.email.notificationOrganizationDescription": "Имя и адрес организации отображаемые в уведомлении по эл. почте от Mattermost, например \"© ABC Corporation, 565 Knight Way, Palo Alto, California, 94305, USA\". Если поле оставить пустым имя и адрес организации отображаться не будут.",
+ "admin.email.notificationOrganizationExample": "Например: \"© ABC Corporation, 565 Knight Way, Palo Alto, California, 94305, USA\"",
"admin.email.notificationsDescription": "Обычно включают в продуктивной системе. Если включено, Mattermost пытается отправить уведомление по эл. почте. Разработчики могут выключить чтобы уведомления не отправлялись.<br />Включение этой настройки удаляет баннер режима предпросмотра (требуется перезайти в систему для применения настроек).",
"admin.email.notificationsTitle": "Включить уведомления по электронной почте: ",
"admin.email.passwordSaltDescription": "32-символа \"соли\" добавлено в письмо сброса пароля. Они случайно сгенерированны при установке. Кликните \"Перегенерировать\" чтобы создать новую \"соль\".",
@@ -274,7 +276,7 @@
"admin.email.pushServerEx": "Например: \"http://push-test.mattermost.com\"",
"admin.email.pushServerTitle": "Сервер push уведомлений:",
"admin.email.pushTitle": "Включить push-уведомления: ",
- "admin.email.requireVerificationDescription": "Typically set to true in production. When true, Mattermost requires email verification after account creation prior to allowing login. Developers may set this field to false so skip sending verification emails for faster development.",
+ "admin.email.requireVerificationDescription": "Если истина, для разрешения входа Mattermost требует подтверждения адреса эл. почты после создания учетной записи. Обычно включается в production-системе. Разработчики могут отключить подтверждение адреса эл. почты для упрощения работы.",
"admin.email.requireVerificationTitle": "Требовать подтверждение адреса электронной почты: ",
"admin.email.selfPush": "Введите адрес сервиса отправки push-уведомлений вручную",
"admin.email.smtpPasswordDescription": " Получите эти данные от администратора, обслуживающего ваш сервер электронной почты.",
@@ -308,10 +310,20 @@
"admin.general.localization.serverLocaleTitle": "Язык сервера по умолчанию:",
"admin.general.log": "Ведение журнала",
"admin.general.policy": "Policy",
+ "admin.general.policy.allowEditPostAlways": "Any time",
+ "admin.general.policy.allowEditPostDescription": "Set policy on the length of time authors have to edit their messages after posting.",
+ "admin.general.policy.allowEditPostNever": "Никогда",
+ "admin.general.policy.allowEditPostTimeLimit": "seconds after posting",
+ "admin.general.policy.allowEditPostTitle": "Allow users to edit their messages:",
"admin.general.policy.permissionsAdmin": "Группа и Администраторы системы",
"admin.general.policy.permissionsAll": "Все участники команды",
"admin.general.policy.permissionsAllChannel": "Все участники канала",
+ "admin.general.policy.permissionsDeletePostAdmin": "Группа и Администраторы системы",
+ "admin.general.policy.permissionsDeletePostAll": "Message authors can delete their own messages, and Administrators can delete any message",
+ "admin.general.policy.permissionsDeletePostSystemAdmin": "Администраторы Системы",
"admin.general.policy.permissionsSystemAdmin": "Администраторы Системы",
+ "admin.general.policy.restrictPostDeleteDescription": "Set policy on who has permission to delete messages.",
+ "admin.general.policy.restrictPostDeleteTitle": "Allow which users to delete messages:",
"admin.general.policy.restrictPrivateChannelCreationDescription": "Установите политики того, кто может создавать приватные группы.",
"admin.general.policy.restrictPrivateChannelCreationTitle": "Включить возможность создания приватных групп для:",
"admin.general.policy.restrictPrivateChannelDeletionCommandLineToolLink": "командная строка",
@@ -363,6 +375,7 @@
"admin.image.amazonS3BucketExample": "Например: \"mattermost-media\"",
"admin.image.amazonS3BucketTitle": "Amazon S3 Корзина:",
"admin.image.amazonS3EndpointDescription": "Адрес вашего совместимого хранилища S3 . По умолчанию `s3.amazonaws.com`.",
+ "admin.image.amazonS3EndpointExample": "E.g.: \"s3.amazonaws.com\"",
"admin.image.amazonS3EndpointTitle": "Конечная точка Amazon S3:",
"admin.image.amazonS3IdDescription": "Получите эти учетные данные от своего администратора Amazon EC2.",
"admin.image.amazonS3IdExample": "Например: \"AKIADTOVBGERKLCBV\"",
@@ -510,12 +523,12 @@
"admin.log.logSettings": "Настройки журнала",
"admin.logs.reload": "Перезагрузить",
"admin.logs.title": "Серверные логи",
- "admin.metrics.enableDescription": "When true, Mattermost will enable performance monitoring collection and profiling. Please see <a href=\"http://docs.mattermost.com/deployment/metrics.html\" target='_blank'>documentation</a> to learn more about configuring performance monitoring for Mattermost.",
+ "admin.metrics.enableDescription": "Если включено, в Mattermost будет включен сбор данных о производительности и профилирование. Дополнительную информацию по конфигурации мониторинга производительности смотрите в <a href=\"http://docs.mattermost.com/deployment/metrics.html\" target='_blank'>документации</a>.",
"admin.metrics.enableTitle": "Включить мониторинг производительности",
- "admin.metrics.listenAddressDesc": "The address the server will listen on to expose performance metrics.",
- "admin.metrics.listenAddressEx": "Например: \":8067\"",
+ "admin.metrics.listenAddressDesc": "Адрес, прослушиваемый сервером для предоставления метрик производительности",
+ "admin.metrics.listenAddressEx": "Например: \":8065\"",
"admin.metrics.listenAddressTitle": "Прослушиваемый адрес:",
- "admin.mfa.bannerDesc": "Multi-factor authentication is only available for accounts with LDAP and email login methods. If there are users on your system with other login methods, it is recommended you set up multi-factor authentication directly with the SSO or SAML provider.",
+ "admin.mfa.bannerDesc": "Многофакторная проверка подлинности доступна только для учетных записей со входом через адрес эл. почты или LDAP. Если на вашей системе есть пользователи с другими методами входа, рекомендуем настроить многофакторную проверку подлинности напрямую через провайдер SSO или SAML.",
"admin.mfa.cluster": "Высокий",
"admin.mfa.title": "Включить многофакторную аутентификацию",
"admin.nav.help": "Помощь",
@@ -834,7 +847,7 @@
"admin.team.maxChannelsExample": "Например: \"100\"",
"admin.team.maxChannelsTitle": "Максимальное количество каналов на команду:",
"admin.team.maxNotificationsPerChannelDescription": "Maximum total number of users in a channel before users typing messages, @all, @here, and @channel no longer send notifications because of performance.",
- "admin.team.maxNotificationsPerChannelExample": "Например: \"1000\"",
+ "admin.team.maxNotificationsPerChannelExample": "Например: \"10000\"",
"admin.team.maxNotificationsPerChannelTitle": "Максимальное количество уведомлений на канал:",
"admin.team.maxUsersDescription": "Максимальное количество пользователей на команду.",
"admin.team.maxUsersExample": "Например: \"25\"",
@@ -866,7 +879,7 @@
"admin.team_analytics.totalPosts": "Всего сообщений",
"admin.true": "да",
"admin.userList.title": "Пользователи {team}",
- "admin.userList.title2": "Users for {team} ({count})",
+ "admin.userList.title2": "Пользователи для команды {team} ({count})",
"admin.user_item.authServiceEmail": ", <strong>Метод входа:</strong> Email",
"admin.user_item.authServiceNotEmail": ", <strong>Метод входа:</strong> {service}",
"admin.user_item.confirmDemoteDescription": "If you demote yourself from the System Admin role and there is not another user with System Admin privileges, you'll need to re-assign a System Admin by accessing the Mattermost server through a terminal and running the following command.",
@@ -923,8 +936,10 @@
"analytics.chart.meaningful": "Недостаточно данных для представления.",
"analytics.system.activeUsers": "Активные пользователи с сообщениями",
"analytics.system.channelTypes": "Типы канала",
+ "analytics.system.dailyActiveUsers": "Daily Active Users",
"analytics.system.expiredBanner": "Лицензия Enterprise истекает {date}. Свяжитесь с <a href='mailto:commercial@mattermost.com'>commercial@mattermost.com</a> и обновите лицензию в течение 15 дней.",
"analytics.system.expiringBanner": "Лицензия Enterprise истекает {date}. Для обновления лицензии свяжитесь с <a href='mailto:commercial@mattermost.com'>commercial@mattermost.com</a>.",
+ "analytics.system.monthlyActiveUsers": "Monthly Active Users",
"analytics.system.postTypes": "Сообщения, файлы и хештэги",
"analytics.system.privateGroups": "Приватные Группы",
"analytics.system.publicChannels": "Публичные Каналы",
@@ -1022,7 +1037,6 @@
"backstage_sidebar.integrations.outgoing_webhooks": "Исходящие Webhook'и",
"calling_screen": "Вызов",
"center_panel.recent": "Нажмите здесь, чтобы перейти к последнему сообщению. ",
- "chanel_header.addMembers": "Добавить участников",
"change_url.close": "Закрыть",
"change_url.endWithLetter": "Должен заканчиваться буквой или цифрой",
"change_url.invalidUrl": "Некорректный URL",
@@ -1040,6 +1054,7 @@
"channel_flow.handleTooShort": "Ссылка на канал должна быть не короче 2-х буквенных символов",
"channel_flow.invalidName": "Недопустимое имя канала",
"channel_flow.set_url_title": "Установить адрес {term}",
+ "channel_header.addMembers": "Добавить участников",
"channel_header.addToFavorites": "Добавить в избранное",
"channel_header.channel": "Канал",
"channel_header.channelHeader": "Изменить заголовок канала",
@@ -1079,10 +1094,14 @@
"channel_loader.uploadedFile": " загрузил файл",
"channel_loader.uploadedImage": " загрузил изображение",
"channel_loader.wrote": " написал: ",
+ "channel_members_dropdown.channel_admin": "Channel Admin",
+ "channel_members_dropdown.channel_member": "Участники канала",
+ "channel_members_dropdown.make_channel_admin": "Make Channel Admin",
+ "channel_members_dropdown.make_channel_member": "Make Channel Member",
+ "channel_members_dropdown.remove_from_channel": "Remove From Channel",
+ "channel_members_dropdown.remove_member": "Remove Member",
"channel_members_modal.addNew": " Добавить участников",
- "channel_members_modal.close": "Закрыть",
- "channel_members_modal.remove": "Удалить",
- "channel_memebers_modal.members": " Участники",
+ "channel_members_modal.members": " Участники",
"channel_modal.cancel": "Отмена",
"channel_modal.channel": "Канал",
"channel_modal.createNew": "Создать ",
@@ -1091,6 +1110,7 @@
"channel_modal.edit": "Редактировать",
"channel_modal.group": "Группа",
"channel_modal.header": "Заголовок",
+ "channel_modal.headerEx": "E.g.: \"[Link Title](http://example.com)\"",
"channel_modal.headerHelp": "Задайте текст, который появится в заголовке {term} рядом с названием {term}. К примеру, вы можете включить часто используемые ссылки, введя [Текст ссылки](http://example.com).",
"channel_modal.modalTitle": "Новый ",
"channel_modal.name": "Имя",
@@ -1101,6 +1121,7 @@
"channel_modal.publicChannel1": "Создать общедоступный канал",
"channel_modal.publicChannel2": "Создать новый публичный канал. ",
"channel_modal.purpose": "Назначение",
+ "channel_modal.purposeEx": "E.g.: \"A channel to file bugs and improvements\"",
"channel_notifications.allActivity": "При любой активности",
"channel_notifications.allUnread": "При любых непрочитанных сообщениях",
"channel_notifications.globalDefault": "По умолчанию глобально ({notifyLevel})",
@@ -1113,6 +1134,7 @@
"channel_notifications.unreadInfo": "Название канала в боковом меню выделяется жирным, когда есть непрочитанные сообщения. Если выбрано \"Только при упоминаниях\" название канала будет выделяться только если Вас упомянут.",
"channel_select.placeholder": "--- Выбрать канал ---",
"channel_switch_modal.dm": "(Личное сообщение)",
+ "channel_switch_modal.failed_to_open": "Failed to open channel.",
"channel_switch_modal.help": "Напишите название канала. Используйте ↑↓ для перемещения, TAB для выбора, ↵ для подтверждения и ESC для отказа",
"channel_switch_modal.not_found": "Совпадений не найдено.",
"channel_switch_modal.submit": "Переключить",
@@ -1265,7 +1287,7 @@
"file_upload.limited": "Загрузка ограничена максимум {count} файлами(ом). Пожалуйста используйте дополнительные сообщения для отправки большего количества.more files.",
"file_upload.pasted": "Изображение вставлено в ",
"filtered_channels_list.count": "{count} {count, plural, =0 {0 channels} one {channel} other {channels}}",
- "filtered_channels_list.countTotal": "{count} {count, plural, =0 {0 channels} one {channel} other {channels}} of {total} Total",
+ "filtered_channels_list.countTotal": "{count} {count, plural, =0 {0 channels} one {channel} other {channels}} of {total} total",
"filtered_channels_list.search": "Поиск каналов",
"filtered_user_list.any_team": "Все пользователи",
"filtered_user_list.count": "{count} {count, plural, =0 {0 members} one {member} other {members}}",
@@ -1285,15 +1307,14 @@
"find_team.submitError": "Введите корректный email",
"flag_post.flag": "Отметить для отслеживания",
"flag_post.unflag": "Не помечено",
+ "general_tab.chooseDescription": "Введите новое имя для вашей команды",
"general_tab.chooseName": "Введите новое имя для вашей команды",
"general_tab.codeDesc": "Нажмите 'Редактировать' для перегенерации кода приглашения.",
- "general_tab.codeLongDesc": "Код приглашения используется как часть URL в ссылке прилашения в команду, созданная в разделе <strong>Получить ссылку для прилашения в команду</strong> в главном меню. Перегенерация создаст новую ссылку для приглашения и сделает предыдущую недействительной.",
+ "general_tab.codeLongDesc": "Код приглашения используется как часть URL в ссылке приглашения в команду, созданная в разделе {getTeamInviteLink} в главном меню. Пересоздание создаст новую ссылку для приглашения и сделает предыдущую недействительной.",
"general_tab.codeTitle": "Код приглашения",
- "general_tab.dirDisabled": "Team Directory has been disabled. Please ask a System Admin to enable the Team Directory in the System Console team settings.",
- "general_tab.dirOff": "Team Directory выключена для этой системы.",
"general_tab.emptyDescription": "Нажмите 'Редактировать' для добавления описания.",
+ "general_tab.getTeamInviteLink": "Ссылка для приглашения",
"general_tab.includeDirDesc": "Including this team will display the team name from the Team Directory section of the Home Page, and provide a link to the sign-in page.",
- "general_tab.includeDirTitle": "Добавить команду в список команд",
"general_tab.no": "Нет",
"general_tab.openInviteDesc": "Если разрешено, ссылка на эту команду будет отображаться на странице входа, позволяя любому пользователю войти в команду.",
"general_tab.openInviteTitle": "Разрешить вход любому пользователю с учетной записью на этом сервере",
@@ -1550,6 +1571,7 @@
"login.passwordChanged": " Пароль успешно обновлён",
"login.session_expired": " Сессия истекла. Пожалуйста, войдите заново",
"login.signIn": "Войти",
+ "login.signInLoading": "Signing in...",
"login.signInWith": "Войти с помощью:",
"login.userNotFound": "Мы не обнаружили аккаунт по вашим данным для входа.",
"login.username": "Имя пользователя",
@@ -1561,8 +1583,10 @@
"member_item.makeAdmin": "Сделать администратором",
"member_item.member": "Участник",
"member_list.noUsersAdd": "Нет пользователей для добавления.",
+ "members_popover.manageMembers": "Управление участниками",
"members_popover.msg": "Сообщение",
"members_popover.title": "Участники",
+ "members_popover.viewMembers": "Просмотреть список участников",
"mfa.confirm.complete": "<strong>Настройка завершена!</strong>",
"mfa.confirm.okay": "Понятно",
"mfa.confirm.secure": "Теперь ваш аккаунт защищён. В следующий раз будет запрошен ввод кода из Google Authentificator.",
@@ -1665,6 +1689,7 @@
"post_info.mobile.unflag": "Не помечено",
"post_info.permalink": "Постоянная ссылка",
"post_info.reply": "Ответить",
+ "post_message_view.edited": "(edited)",
"posts_view.loadMore": "Больше сообщений",
"posts_view.newMsg": "Новые сообщения",
"posts_view.newMsgBelow": "New {count, plural, one {message} other {messages}} below",
@@ -1677,7 +1702,7 @@
"reaction.reactionVerb.you": "reacted",
"reaction.reactionVerb.youAndUsers": "reacted",
"reaction.usersAndOthersReacted": "{users} and {otherUsers, number} other {otherUsers, plural, one {user} other {users}}",
- "reaction.usersReacted": "{users} and {lastUser}",
+ "reaction.usersReacted": "{users} и {lastUser}",
"reaction.you": "Вы",
"removed_channel.channelName": "канал",
"removed_channel.from": "Удалено из ",
@@ -1809,8 +1834,8 @@
"signup_user_completed.required": "Обязательное поле",
"signup_user_completed.reserved": "Имя зарезервировано, выберите другое.",
"signup_user_completed.signIn": "Щёлкните здесь для входа.",
- "signup_user_completed.userHelp": "Username must begin with a letter, and contain between {min} to {max} lowercase characters made up of numbers, letters, and the symbols '.', '-' and '_'",
- "signup_user_completed.usernameLength": "Username must begin with a letter, and contain between {min} to {max} lowercase characters made up of numbers, letters, and the symbols '.', '-' and '_'.",
+ "signup_user_completed.userHelp": "Имя пользователя должно начинаться с латинской буквы и содержать от {min} до {max} символов, включающих цифры, латинские буквы, а также символы '.', '-' и '_'.",
+ "signup_user_completed.usernameLength": "Имя пользователя должно начинаться с латинской буквы и содержать от {min} до {max} символов, включающих цифры, латинские буквы, а также символы '.', '-' и '_'.",
"signup_user_completed.validEmail": "Пожалуйста, введите корректный адрес электронной почты",
"signup_user_completed.welcome": "Добро пожаловать:",
"signup_user_completed.whatis": "Ваш адрес электронной почты?",
@@ -1868,7 +1893,7 @@
"team_settings_modal.generalTab": "Общие",
"team_settings_modal.importTab": "Импорт",
"team_settings_modal.title": "Настройки команды",
- "team_sidebar.join": "Other teams you can join.",
+ "team_sidebar.join": "Другие команды, к которым вы можете присоединиться.",
"textbox.bold": "**жирный**",
"textbox.edit": "Редактировать сообщение",
"textbox.help": "Помощь",
@@ -1877,7 +1902,7 @@
"textbox.preformatted": "```преформатированный```",
"textbox.preview": "Предпросмотр",
"textbox.quote": ">цитата",
- "textbox.strike": "strike",
+ "textbox.strike": "зачеркнутый",
"tutorial_intro.allSet": "Теперь всё готово!",
"tutorial_intro.end": "Нажмите “Далее” для входа в {channel}. Это первый канал, который пользователи команды видят после входа. Используйте его для общения всей команды.",
"tutorial_intro.invite": "Пригласить товарищей",
@@ -1899,7 +1924,7 @@
"update_command.question": "Your changes may break the existing slash command. Are you sure you would like to update it?",
"update_command.update": "Обновить",
"upload_overlay.info": "Бросьте сюда файл, чтобы загрузить его.",
- "user.settings.advance.embed_preview": "Показывать \"экспериментальный\" превью в ссылках, когда доступно",
+ "user.settings.advance.embed_preview": "For the first web link in a message, display a preview of website content below the message, if available",
"user.settings.advance.embed_toggle": "Показывать переключатель для всех встроенных превью",
"user.settings.advance.enabledFeatures": "{count, number} {count, plural, one {Feature} other {Features}} Enabled",
"user.settings.advance.formattingDesc": "If enabled, posts will be formatted to create links, show emoji, style the text, and add line breaks. By default, this setting is enabled. Changing this setting requires the page to be refreshed.",
@@ -1944,10 +1969,10 @@
"user.settings.display.channelDisplayTitle": "Режим отображения канала",
"user.settings.display.channeldisplaymode": "Выберите ширину центрального канала.",
"user.settings.display.clockDisplay": "Отображение времени",
- "user.settings.display.collapseDesc": "Раскрывать ссылки и показывать предпросмотр содержимого, если возможно.",
- "user.settings.display.collapseDisplay": "Предварительный просмотр ссылок",
- "user.settings.display.collapseOff": "Выкл",
- "user.settings.display.collapseOn": "Вкл",
+ "user.settings.display.collapseDesc": "Set whether previews of image links show as expanded or collapsed by default. This setting can also be controlled using the slash commands /expand and /collapse.",
+ "user.settings.display.collapseDisplay": "Default appearance of image link previews",
+ "user.settings.display.collapseOff": "Collapsed",
+ "user.settings.display.collapseOn": "Expanded",
"user.settings.display.fixedWidthCentered": "По центру, фиксировано по ширине",
"user.settings.display.fontDesc": "Выберите шрифт пользовательского интерфейса Mattermost.",
"user.settings.display.fontTitle": "Шрифт",
@@ -2130,7 +2155,9 @@
"user.settings.security.lastUpdated": "Последние изменения: {date} в {time}",
"user.settings.security.ldap": "AD/LDAP",
"user.settings.security.loginGitlab": "Вход выполнен с помощью GitLab",
+ "user.settings.security.loginGoogle": "Login done through Google Apps",
"user.settings.security.loginLdap": "Вход выполнен через AD/LDAP",
+ "user.settings.security.loginOffice365": "Login done through Office 365",
"user.settings.security.loginSaml": "Вход выполнен с помощью SAML",
"user.settings.security.logoutActiveSessions": "Просмотр и завершение активных сессий",
"user.settings.security.method": "Методы входа",
@@ -2159,9 +2186,11 @@
"user.settings.security.passwordErrorUppercaseNumberSymbol": "Пароль должен быть не короче {min} символов и содержать хотя бы один символ в верхнем регистре, хотя бы одну цифру и хотя бы один символ пунктуации (например, \"~!@#$%^&*()\").",
"user.settings.security.passwordErrorUppercaseSymbol": "Пароль должен быть не короче {min} символов и содержать хотя бы одну цифру и хотя бы один специальный символ (например, \"~!@#$%^&*()\").",
"user.settings.security.passwordGitlabCantUpdate": "Вход произошел через GitLab. Пароль не может быть изменен.",
+ "user.settings.security.passwordGoogleCantUpdate": "Вход произошел через GitLab. Пароль не может быть изменен.",
"user.settings.security.passwordLdapCantUpdate": "Вход произведен через AD/LDAP. Пароль не может быть обновлен.",
"user.settings.security.passwordMatchError": "Введенные пароли не совпадают.",
"user.settings.security.passwordMinLength": "Длина пароля меньше минимальной, предпросмотр невозможен.",
+ "user.settings.security.passwordOffice365CantUpdate": "Вход произошел через GitLab. Пароль не может быть изменен.",
"user.settings.security.passwordSamlCantUpdate": "This field is handled through your login provider. If you want to change it, you need to do so through your login provider.",
"user.settings.security.retypePassword": "Повторите новый пароль",
"user.settings.security.saml": "SAML",
diff --git a/webapp/i18n/zh_CN.json b/webapp/i18n/zh_CN.json
index 2599452f4..ed814e813 100644
--- a/webapp/i18n/zh_CN.json
+++ b/webapp/i18n/zh_CN.json
@@ -28,6 +28,7 @@
"activity_log.sessionsDescription": "当您在设备的新浏览器中登录时,将创建会话。会话让您使用Mattermost时无需在系统管理员限定的时间段内重新登录。如果您希望早些退出,点击下方的‘注销’按钮结束会话。",
"activity_log_modal.android": "安卓",
"activity_log_modal.androidNativeApp": "Android本地App",
+ "activity_log_modal.desktop": "电脑应用",
"activity_log_modal.iphoneNativeApp": "iPhone本地App",
"add_command.autocomplete": "自动完成",
"add_command.autocomplete.help": "(可选) 在自动完成列表显示斜杠命令。",
@@ -251,15 +252,16 @@
"admin.email.mhpnsHelp": "从 iTunes下载 <a href=\"https://itunes.apple.com/us/app/mattermost/id984966508?mt=8\" target='_blank'>Mattermost iOS app</a>。从 Google Play 下载 <a href=\"https://play.google.com/store/apps/details?id=com.mattermost.mattermost&hl=en\" target='_blank'>Mattermost Android app</a>。 了解更多 <a href=\"http://docs.mattermost.com/deployment/push.html#hosted-push-notifications-service-hpns\" target='_blank'>HPNS</a>。",
"admin.email.mtpns": "在iTunes和TPNS的谷歌Play使用iOS和Android应用程序",
"admin.email.mtpnsHelp": "从 iTunes下载 <a href=\"https://itunes.apple.com/us/app/mattermost/id984966508?mt=8\" target='_blank'>Mattermost iOS app</a>。从 Google Play 下载 <a href=\"https://play.google.com/store/apps/details?id=com.mattermost.mattermost&hl=en\" target='_blank'>Mattermost Android app</a>。 了解更多 <a href=\"http://docs.mattermost.com/deployment/push.html#test-push-notifications-service-tpns\" target='_blank'>TPNS</a>。",
- "admin.email.nofificationOrganizationExample": "例如 \"© ABC Corporation, 565 Knight Way, Palo Alto, California, 94305, USA\"",
+ "admin.email.nofificationOrganizationExample": "例如:\"© ABC Corporation, 565 Knight Way, Palo Alto, California, 94305, USA\"",
"admin.email.notificationDisplayDescription": "从 Mattermost 发送的电子邮件通知时显示的电子邮件帐号名。",
- "admin.email.notificationDisplayExample": "例如: \"Mattermost通知\", \"系统\", \"无答复\"",
+ "admin.email.notificationDisplayExample": "例如:\"Mattermost通知\", \"系统\", \"无答复\"",
"admin.email.notificationDisplayTitle": "通知显示名称:",
"admin.email.notificationEmailDescription": "从 Mattermost 发送的电子邮件通知时显示的电子邮件地址。",
- "admin.email.notificationEmailExample": "例如: \"mattermost@yourcompany.com\", \"admin@yourcompany.com\"",
+ "admin.email.notificationEmailExample": "例如:\"mattermost@yourcompany.com\", \"admin@yourcompany.com\"",
"admin.email.notificationEmailTitle": "通知邮件地址:",
"admin.email.notificationOrganization": "通知页脚地址:",
"admin.email.notificationOrganizationDescription": "从mattermost电子邮件通知显示组织机构名称和地址,如“©ABC Corporation, 565 Knight Way, Palo Alto, California, 94305, USA\"。如果字段为空,则将不显示该组织的名称和地址。",
+ "admin.email.notificationOrganizationExample": "例如:\"© ABC Corporation, 565 Knight Way, Palo Alto, California, 94305, USA\"",
"admin.email.notificationsDescription": "通常在正式环境中设置为是。当设为是时,Mattermost 将尝试发送电子邮件通知。开发人员可以设置为否以跳过电子邮件设置来加快开发速度。<br />置此为是时将删除预览模式横幅 (设置后需要注销后重新登录才生效)。",
"admin.email.notificationsTitle": "启用发送邮件通知:",
"admin.email.passwordSaltDescription": "32字盐值用来签署重置密码邮件。由安装时随机生成。点击 \"重新生成\" 生成新的盐。",
@@ -278,7 +280,7 @@
"admin.email.requireVerificationTitle": "要求电子邮件验证:",
"admin.email.selfPush": "手动输入推送通知服务位置",
"admin.email.smtpPasswordDescription": "从邮件服务器管理员获得此凭据。",
- "admin.email.smtpPasswordExample": "例如: \"yourpassword\", \"jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY\"",
+ "admin.email.smtpPasswordExample": "例如:\"yourpassword\", \"jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY\"",
"admin.email.smtpPasswordTitle": "SMTP服务器密码:",
"admin.email.smtpPortDescription": "SMTP邮件服务器端口。",
"admin.email.smtpPortExample": "例如:\"25\", \"465\", \"587\"",
@@ -287,7 +289,7 @@
"admin.email.smtpServerExample": "例如:\"smtp.yourcompany.com\", \"email-smtp.us-east-1.amazonaws.com\"",
"admin.email.smtpServerTitle": "SMTP服务器:",
"admin.email.smtpUsernameDescription": "从邮件服务器管理员获得此凭据。",
- "admin.email.smtpUsernameExample": "例如: \"admin@yourcompany.com\", \"AKIADTOVBGERKLCBV\"",
+ "admin.email.smtpUsernameExample": "例如:\"admin@yourcompany.com\", \"AKIADTOVBGERKLCBV\"",
"admin.email.smtpUsernameTitle": "SMTP服务器用户名:",
"admin.email.testing": "测试中...",
"admin.false": "否",
@@ -308,10 +310,20 @@
"admin.general.localization.serverLocaleTitle": "默认服务器语言:",
"admin.general.log": "日志",
"admin.general.policy": "策略",
+ "admin.general.policy.allowEditPostAlways": "Any time",
+ "admin.general.policy.allowEditPostDescription": "Set policy on the length of time authors have to edit their messages after posting.",
+ "admin.general.policy.allowEditPostNever": "从不",
+ "admin.general.policy.allowEditPostTimeLimit": "seconds after posting",
+ "admin.general.policy.allowEditPostTitle": "Allow users to edit their messages:",
"admin.general.policy.permissionsAdmin": "团队和系统管理员",
"admin.general.policy.permissionsAll": "所有团队成员",
"admin.general.policy.permissionsAllChannel": "所有频道成员",
+ "admin.general.policy.permissionsDeletePostAdmin": "团队和系统管理员",
+ "admin.general.policy.permissionsDeletePostAll": "Message authors can delete their own messages, and Administrators can delete any message",
+ "admin.general.policy.permissionsDeletePostSystemAdmin": "系统管理员",
"admin.general.policy.permissionsSystemAdmin": "系统管理员",
+ "admin.general.policy.restrictPostDeleteDescription": "Set policy on who has permission to delete messages.",
+ "admin.general.policy.restrictPostDeleteTitle": "Allow which users to delete messages:",
"admin.general.policy.restrictPrivateChannelCreationDescription": "设置谁可以创建私有组的策略。",
"admin.general.policy.restrictPrivateChannelCreationTitle": "开启创建私有组:",
"admin.general.policy.restrictPrivateChannelDeletionCommandLineToolLink": "命令符工具",
@@ -363,6 +375,7 @@
"admin.image.amazonS3BucketExample": "例如 \"mattermost-media\"",
"admin.image.amazonS3BucketTitle": "Amazon S3 存储桶:",
"admin.image.amazonS3EndpointDescription": "您的 S3 兼容储存提供商的主机名称。默认为 `s3.amazonaws.com`。",
+ "admin.image.amazonS3EndpointExample": "例如:\"s3.amazonaws.com\"",
"admin.image.amazonS3EndpointTitle": "亚马逊 S3 连接点:",
"admin.image.amazonS3IdDescription": "从您的Amazon EC2管理员获得此证书。",
"admin.image.amazonS3IdExample": "例如 \"AKIADTOVBGERKLCBV\"",
@@ -467,7 +480,7 @@
"admin.ldap.testSuccess": "AD/LDAP 测试成功",
"admin.ldap.uernameAttrDesc": "AD/LDAP 服务器中属性用于填充 Mattermost 用户名属性。这可以和 ID 属性一致。",
"admin.ldap.userFilterDisc": "(可选) 输入个AD/LDAP筛选器用在搜索用户对象。只有被查询条件选中的用户才能访问 Mattermost。对于活动目录,过滤禁用用户的查询是(&(objectCategory=Person)(!(UserAccountControl:1.2.840.113556.1.4.803:=2)))。",
- "admin.ldap.userFilterEx": "例如 \"(objectClass=user)\"",
+ "admin.ldap.userFilterEx": "例如:\"(objectClass=user)\"",
"admin.ldap.userFilterTitle": "用户筛选器:",
"admin.ldap.usernameAttrEx": "例如 \"sAMAccountName\"",
"admin.ldap.usernameAttrTitle": "用户名属性:",
@@ -513,7 +526,7 @@
"admin.metrics.enableDescription": "当设置为是时,Mattermost 会启用性能监控收集和分析。请查看<a href=\"http://docs.mattermost.com/deployment/metrics.html\" target='_blank'>文档</a>了解更多Mattermost 性能监控配置信息。",
"admin.metrics.enableTitle": "开启性能监视:",
"admin.metrics.listenAddressDesc": "服务端监听的地址以公开性能指标数据。",
- "admin.metrics.listenAddressEx": "例如 \":8067\"",
+ "admin.metrics.listenAddressEx": "例如:\":8065\"",
"admin.metrics.listenAddressTitle": "监听地址:",
"admin.mfa.bannerDesc": "多重验证只能用在使用 LDAP 或邮箱地址登入方式的帐号。如果您的系统有用户使用其他登入方式,我们推荐您在 SSO 或 SAML 提供商直接设置多重验证。",
"admin.mfa.cluster": "高",
@@ -564,7 +577,7 @@
"admin.rate.enableLimiterTitle": "启用频率限制:",
"admin.rate.httpHeaderDescription": "填充时,变化率限制通过HTTP头字段指定(例如当配置NGINX\"X-Real-IP\",当配置AmazonELB为\"X-Forwarded-For\").",
"admin.rate.httpHeaderExample": "例如 \"X-Real-IP\", \"X-Forwarded-For\"",
- "admin.rate.httpHeaderTitle": "通过HTTP头变化频率限制",
+ "admin.rate.httpHeaderTitle": "通过HTTP头变化频率限制:",
"admin.rate.maxBurst": "最大过载大小:",
"admin.rate.maxBurstDescription": "超过每秒查询限制的最大请求数。",
"admin.rate.maxBurstExample": "例如 \"100\"",
@@ -831,10 +844,10 @@
"admin.team.dirDesc": "当设置为是时,设置为显示在团队目录里的团队会在主页显示并替代创建新的团队的位置。",
"admin.team.dirTitle": "启用团队目录:",
"admin.team.maxChannelsDescription": "每个团队最多频道数,包括活动的和已删除的频道。",
- "admin.team.maxChannelsExample": "例如 \"100\"",
+ "admin.team.maxChannelsExample": "例如:\"100\"",
"admin.team.maxChannelsTitle": "每团队最多频道数:",
"admin.team.maxNotificationsPerChannelDescription": "因性能限制输入消息、@all、@here 以及 @channel 发通知的最大频道总用户数 。",
- "admin.team.maxNotificationsPerChannelExample": "例如 \"1000\"",
+ "admin.team.maxNotificationsPerChannelExample": "例如:\"10000\"",
"admin.team.maxNotificationsPerChannelTitle": "每频道最大通知数:",
"admin.team.maxUsersDescription": "每个团队最多用户数,包括启用的和停用的用户。",
"admin.team.maxUsersExample": "例如 \"25\"",
@@ -901,13 +914,13 @@
"admin.webrtc.gatewayWebsocketUrlTitle": "网关 Websocket 网址:",
"admin.webrtc.stunUriDescription": "输入您的 STUN 网址 stun:<your-stun-url>:<port>。STUN 是一个网络协议标准用于让一个主机帮助双方都在 NAT 背后的用户连接到公网 IP。",
"admin.webrtc.stunUriExample": "例如 \"stun:webrtc.mattermost.com:5349\"",
- "admin.webrtc.stunUriTitle": "STUN URI",
+ "admin.webrtc.stunUriTitle": "STUN URI:",
"admin.webrtc.turnSharedKeyDescription": "输入您的 TURN 服务器共享密钥。此用来生成动态密码来创建连接。每个密码只有短暂有效期。",
"admin.webrtc.turnSharedKeyExample": "例如 \"bXdkOWQxc3d0Ynk3emY5ZmsxZ3NtazRjaWg=\"",
"admin.webrtc.turnSharedKeyTitle": "TURN 共享密钥:",
"admin.webrtc.turnUriDescription": "输入您的 TURN 网址以格式 turn:<your-turn-url>:<port>。TURN 是个标准的网络协议能协助双方都在NAT后时创建连接。",
"admin.webrtc.turnUriExample": "例如 \"turn:webrtc.mattermost.com:5349\"",
- "admin.webrtc.turnUriTitle": "TURN URI",
+ "admin.webrtc.turnUriTitle": "TURN URI:",
"admin.webrtc.turnUsernameDescription": "输入您的 TURN 服务器用户名。",
"admin.webrtc.turnUsernameExample": "例如 \"myusername\"",
"admin.webrtc.turnUsernameTitle": "TURN 用户名:",
@@ -923,8 +936,10 @@
"analytics.chart.meaningful": "没有足够的数据进行有意义的表示。",
"analytics.system.activeUsers": "有发信息的的正常用户",
"analytics.system.channelTypes": "频道类型",
+ "analytics.system.dailyActiveUsers": "Daily Active Users",
"analytics.system.expiredBanner": "企业授权已在 {date} 过期。您在即日起有15天时间更新授权,请联系 <a href='mailto:commercial@mattermost.com'>commercial@mattermost.com</a>。",
"analytics.system.expiringBanner": "企业授权已在 {date} 过期。请更新授权,详情请联系 <a href='mailto:commercial@mattermost.com'>commercial@mattermost.com</a>。",
+ "analytics.system.monthlyActiveUsers": "Monthly Active Users",
"analytics.system.postTypes": "发文,文件和标签",
"analytics.system.privateGroups": "私有组",
"analytics.system.publicChannels": "公共频道",
@@ -1022,7 +1037,6 @@
"backstage_sidebar.integrations.outgoing_webhooks": "对外Webhooks",
"calling_screen": "呼叫中",
"center_panel.recent": "点击这里跳转到最近的消息。",
- "chanel_header.addMembers": "添加成员",
"change_url.close": "关闭",
"change_url.endWithLetter": "必须以字母或数字结尾",
"change_url.invalidUrl": "无效的网址",
@@ -1040,6 +1054,7 @@
"channel_flow.handleTooShort": "频道网址必须为至少2个小写英文数字字符",
"channel_flow.invalidName": "无效的频道名称",
"channel_flow.set_url_title": "设置{term}URL",
+ "channel_header.addMembers": "添加成员",
"channel_header.addToFavorites": "添加到收藏",
"channel_header.channel": "频道",
"channel_header.channelHeader": "编辑频道标题",
@@ -1079,10 +1094,14 @@
"channel_loader.uploadedFile": "上传文件",
"channel_loader.uploadedImage": "上传图片",
"channel_loader.wrote": "写到:",
+ "channel_members_dropdown.channel_admin": "频道管理员",
+ "channel_members_dropdown.channel_member": "频道成员",
+ "channel_members_dropdown.make_channel_admin": "Make Channel Admin",
+ "channel_members_dropdown.make_channel_member": "Make Channel Member",
+ "channel_members_dropdown.remove_from_channel": "从频道移除",
+ "channel_members_dropdown.remove_member": "移除成员",
"channel_members_modal.addNew": "添加新成员",
- "channel_members_modal.close": "关闭",
- "channel_members_modal.remove": "移除",
- "channel_memebers_modal.members": "成员",
+ "channel_members_modal.members": " 位成员",
"channel_modal.cancel": "取消",
"channel_modal.channel": "频道",
"channel_modal.createNew": "创建新",
@@ -1091,6 +1110,7 @@
"channel_modal.edit": "编辑",
"channel_modal.group": "群组",
"channel_modal.header": "标题",
+ "channel_modal.headerEx": "例如:\"[链接标题](http://example.com)\"",
"channel_modal.headerHelp": "设定在 {term} 标题里在 {term} 旁边的文字。举例,输入常见链接 [链接标题](http://example.com)。",
"channel_modal.modalTitle": "新建",
"channel_modal.name": "名称",
@@ -1101,6 +1121,7 @@
"channel_modal.publicChannel1": "创建一个公共频道",
"channel_modal.publicChannel2": "创建一个任何人都能加入的新公共频道。",
"channel_modal.purpose": "用途",
+ "channel_modal.purposeEx": "例如:\"用于提交问题和建议的频道\"",
"channel_notifications.allActivity": "所有操作",
"channel_notifications.allUnread": "所有未读消息",
"channel_notifications.globalDefault": "默认全局({notifyLevel})",
@@ -1113,6 +1134,7 @@
"channel_notifications.unreadInfo": "有未读消息时,侧边栏的频道名称粗体显示。只有当您被提及时选择“仅对提及”会加粗频道名称。",
"channel_select.placeholder": "--- 选择一个频道 ---",
"channel_switch_modal.dm": "(私信)",
+ "channel_switch_modal.failed_to_open": "Failed to open channel.",
"channel_switch_modal.help": "输入频道名。↑↓浏览,TAB选择,↵确认,ESC取消",
"channel_switch_modal.not_found": "无匹配项。",
"channel_switch_modal.submit": "切换",
@@ -1285,15 +1307,14 @@
"find_team.submitError": "请输入一个有效的电子邮件地址",
"flag_post.flag": "标记以跟进",
"flag_post.unflag": "取消标记",
+ "general_tab.chooseDescription": "请为您的团队选个新的描述",
"general_tab.chooseName": "请选择一个新的名称为你的团队",
"general_tab.codeDesc": "点击 \"编辑\" 重新生成邀请码。",
- "general_tab.codeLongDesc": "邀请码用来在主菜单里使用<strong>获取团队邀请链接</strong>所产生的团队邀请链接中的一部分。重新生成一个新的团队邀请链接将使之前的链接无效。",
+ "general_tab.codeLongDesc": "作为团队邀请链接中URL的一部分,邀请码在主菜单中由 {getTeamInviteLink} 创建。重新生成创建一个新的团队邀请链接将使之前的链接无效。",
"general_tab.codeTitle": "邀请码",
- "general_tab.dirDisabled": "团队目录已被禁用。请联系系统管理员到系统控制台中启用团队目录。",
- "general_tab.dirOff": "此系统的团队目录已关闭。",
"general_tab.emptyDescription": "点击 '修改' 添加团队描述。",
+ "general_tab.getTeamInviteLink": "获取团队邀请链接",
"general_tab.includeDirDesc": "包含此团队将在首页的团队目录里显示该团队名,并提供一个链接到登陆页面。",
- "general_tab.includeDirTitle": "团队目录中已包括这个团队",
"general_tab.no": "否",
"general_tab.openInviteDesc": "允许时,此团队的链接将会显示在首页让任何有帐号的用户可以加入此团队。",
"general_tab.openInviteTitle": "允许任何在本服务器上的用户加入此团队",
@@ -1550,6 +1571,7 @@
"login.passwordChanged": "成功更新密码",
"login.session_expired": "您的会话已过期,请重新登录。",
"login.signIn": "登录",
+ "login.signInLoading": "登入中...",
"login.signInWith": "登录使用:",
"login.userNotFound": "我们找不到现有的帐户匹配您的凭证。",
"login.username": "用户名",
@@ -1561,8 +1583,10 @@
"member_item.makeAdmin": "Admin",
"member_item.member": "成员",
"member_list.noUsersAdd": "没有用户可添加。",
+ "members_popover.manageMembers": "成员管理",
"members_popover.msg": "消息",
"members_popover.title": "成员",
+ "members_popover.viewMembers": "查看成员",
"mfa.confirm.complete": "<strong>设置完成!</strong>",
"mfa.confirm.okay": "确定",
"mfa.confirm.secure": "您的帐号现在安全了。下次登入时,您将要求输入 Google Authenticator 应用提供的令牌。",
@@ -1665,6 +1689,7 @@
"post_info.mobile.unflag": "取消标记",
"post_info.permalink": "永久链接",
"post_info.reply": "回复",
+ "post_message_view.edited": "(已编辑)",
"posts_view.loadMore": "载入更多消息",
"posts_view.newMsg": "新消息",
"posts_view.newMsgBelow": "以下有 {count} 个新消息",
@@ -1899,7 +1924,7 @@
"update_command.question": "您的修改可能破坏现有的斜杠命令。您确定要更新吗?",
"update_command.update": "更新",
"upload_overlay.info": "拖动文件上传。",
- "user.settings.advance.embed_preview": "可用时,显示实验性链接内容预览",
+ "user.settings.advance.embed_preview": "For the first web link in a message, display a preview of website content below the message, if available",
"user.settings.advance.embed_toggle": "显示切换所有嵌入预览",
"user.settings.advance.enabledFeatures": "已启用 {count, number} 项功能",
"user.settings.advance.formattingDesc": "开启时,文章会显示链接,表情符,格式,以及添加断行。默认下,此选项时开启的。修改此设定需要刷新页面。",
@@ -1944,10 +1969,10 @@
"user.settings.display.channelDisplayTitle": "频道显示模式",
"user.settings.display.channeldisplaymode": "选择中间栏的宽度。",
"user.settings.display.clockDisplay": "时钟显示",
- "user.settings.display.collapseDesc": "可用时,展开链接显示内容预览。",
- "user.settings.display.collapseDisplay": "链接预览",
- "user.settings.display.collapseOff": "关闭",
- "user.settings.display.collapseOn": "开启",
+ "user.settings.display.collapseDesc": "Set whether previews of image links show as expanded or collapsed by default. This setting can also be controlled using the slash commands /expand and /collapse.",
+ "user.settings.display.collapseDisplay": "Default appearance of image link previews",
+ "user.settings.display.collapseOff": "Collapsed",
+ "user.settings.display.collapseOn": "Expanded",
"user.settings.display.fixedWidthCentered": "固定宽度,居中",
"user.settings.display.fontDesc": "选择在Mattermost用户界面显示的字体。",
"user.settings.display.fontTitle": "显示字体",
@@ -2130,7 +2155,9 @@
"user.settings.security.lastUpdated": "上次更新时间{date}{time}",
"user.settings.security.ldap": "AD/LDAP",
"user.settings.security.loginGitlab": "用 GitLab 登录",
+ "user.settings.security.loginGoogle": "Login done through Google Apps",
"user.settings.security.loginLdap": "用 AD/LDAP 登录",
+ "user.settings.security.loginOffice365": "Login done through Office 365",
"user.settings.security.loginSaml": "用 SAML 登录",
"user.settings.security.logoutActiveSessions": "查看并退出正在执行的会话",
"user.settings.security.method": "登录方式",
@@ -2159,9 +2186,11 @@
"user.settings.security.passwordErrorUppercaseNumberSymbol": "您的密码必须包含至少 {min} 个字符且至少有一个大写字母,一个数字,以及一个符号(如\"~!@#$%^&*()\")。",
"user.settings.security.passwordErrorUppercaseSymbol": "您的密码必须包含至少 {min} 个字符且至少有一个大写字母以及一个符号(如\"~!@#$%^&*()\")。",
"user.settings.security.passwordGitlabCantUpdate": "通过GitLab登录。电子邮件不能被更新。",
+ "user.settings.security.passwordGoogleCantUpdate": "通过 Google Apps 登录。密码不能被更新。",
"user.settings.security.passwordLdapCantUpdate": "通过 AD/LDAP 登录。密码不能被更新。",
"user.settings.security.passwordMatchError": "您输入的新密码不一致。",
"user.settings.security.passwordMinLength": "无效最小长度,无法显示预览。",
+ "user.settings.security.passwordOffice365CantUpdate": "通过 Office 365 登录。密码不能被更新。",
"user.settings.security.passwordSamlCantUpdate": "此栏由您的登入提供商决定。如果您想更改,您需要到您的登入提供者改动。",
"user.settings.security.retypePassword": "再次输入新密码",
"user.settings.security.saml": "SAML",
diff --git a/webapp/i18n/zh_TW.json b/webapp/i18n/zh_TW.json
index 63078772f..d7b8d6f86 100644
--- a/webapp/i18n/zh_TW.json
+++ b/webapp/i18n/zh_TW.json
@@ -28,6 +28,7 @@
"activity_log.sessionsDescription": "當使用新的瀏覽器登入時工作階段會被建立。工作階段讓您可以在系統管理員設定的時間內不用重複登入。如果想先行登出,請使用下方的 '登出' 按鈕以結束工作階段。",
"activity_log_modal.android": "Android",
"activity_log_modal.androidNativeApp": "Android 原生應用程式",
+ "activity_log_modal.desktop": "Native Desktop App",
"activity_log_modal.iphoneNativeApp": "iPhone 原生應用程式",
"add_command.autocomplete": "自動完成",
"add_command.autocomplete.help": "在自動完成列表上顯示斜線命令(非必須)。",
@@ -260,6 +261,7 @@
"admin.email.notificationEmailTitle": "通知信寄件人地址:",
"admin.email.notificationOrganization": "通知信信尾地址:",
"admin.email.notificationOrganizationDescription": "顯示於來自 Mattermost 通知信的組織名稱跟地址。如:\"© ABC Corporation, 565 Knight Way, Palo Alto, California, 94305, USA\"。如果這欄為空,信將不會顯示組織名稱跟地址。",
+ "admin.email.notificationOrganizationExample": "如:\"© ABC Corporation, 565 Knight Way, Palo Alto, California, 94305, USA\"",
"admin.email.notificationsDescription": "正式環境通常設為啟用。啟用時 Mattermost 會傳送電子郵件通知。開發者可以設為停用以跳過設定電子郵件加速開發。<br/>啟用時移除預覽模式橫幅(變更設定後須重新登入以生效)。",
"admin.email.notificationsTitle": "啟用電子郵件通知:",
"admin.email.passwordSaltDescription": "32字元的 Salt 用來簽署重設密碼之電子郵件。於安裝時隨機產生。按\"重新產生\"建立新的 salt。",
@@ -308,10 +310,20 @@
"admin.general.localization.serverLocaleTitle": "預設的伺服器語言:",
"admin.general.log": "記錄",
"admin.general.policy": "政策",
+ "admin.general.policy.allowEditPostAlways": "Any time",
+ "admin.general.policy.allowEditPostDescription": "Set policy on the length of time authors have to edit their messages after posting.",
+ "admin.general.policy.allowEditPostNever": "永不",
+ "admin.general.policy.allowEditPostTimeLimit": "seconds after posting",
+ "admin.general.policy.allowEditPostTitle": "Allow users to edit their messages:",
"admin.general.policy.permissionsAdmin": "團隊跟系統管理員",
"admin.general.policy.permissionsAll": "團隊全員",
"admin.general.policy.permissionsAllChannel": "所有頻道成員",
+ "admin.general.policy.permissionsDeletePostAdmin": "團隊跟系統管理員",
+ "admin.general.policy.permissionsDeletePostAll": "Message authors can delete their own messages, and Administrators can delete any message",
+ "admin.general.policy.permissionsDeletePostSystemAdmin": "系統管理員",
"admin.general.policy.permissionsSystemAdmin": "系統管理員",
+ "admin.general.policy.restrictPostDeleteDescription": "Set policy on who has permission to delete messages.",
+ "admin.general.policy.restrictPostDeleteTitle": "Allow which users to delete messages:",
"admin.general.policy.restrictPrivateChannelCreationDescription": "設定誰能建立私人群組的政策。",
"admin.general.policy.restrictPrivateChannelCreationTitle": "允許建立私人群組:",
"admin.general.policy.restrictPrivateChannelDeletionCommandLineToolLink": "命令列工具",
@@ -363,6 +375,7 @@
"admin.image.amazonS3BucketExample": "如:\"mattermost-media\"",
"admin.image.amazonS3BucketTitle": "Amazon S3 儲存貯體:",
"admin.image.amazonS3EndpointDescription": "相容於 S3 的儲存提供者的主機名稱。預設為`s3.amazonaws.com`。",
+ "admin.image.amazonS3EndpointExample": "E.g.: \"s3.amazonaws.com\"",
"admin.image.amazonS3EndpointTitle": "Amazon S3 端點:",
"admin.image.amazonS3IdDescription": "從 Amazon EC2 管理員取得認證。",
"admin.image.amazonS3IdExample": "如:\"AKIADTOVBGERKLCBV\"",
@@ -513,7 +526,7 @@
"admin.metrics.enableDescription": "啟用時,Mattermost 會啟用效能監視的收集與分析。詳細如何設定 Mattermost 的效能監視,請參閱<a href=\"http://docs.mattermost.com/deployment/metrics.html\" target='_blank'>文件</a>。",
"admin.metrics.enableTitle": "啟用效能監視:",
"admin.metrics.listenAddressDesc": "伺服器將監聽以公開效能計量值的位址。",
- "admin.metrics.listenAddressEx": "例如:\":8067\"",
+ "admin.metrics.listenAddressEx": "如:\":8065\"",
"admin.metrics.listenAddressTitle": "監聽位址:",
"admin.mfa.bannerDesc": "多重要素驗證僅供以 LDAP 或是電子郵件登入的帳號使用。如果系統中有使用者使用其他的登入方式,我們建議直接對 SSO 或 SAML 提供者設定多重要素驗證。",
"admin.mfa.cluster": "高",
@@ -834,7 +847,7 @@
"admin.team.maxChannelsExample": "如:\"100\"",
"admin.team.maxChannelsTitle": "團隊最大頻道數:",
"admin.team.maxNotificationsPerChannelDescription": "頻道使用者人數在超過此數量後,由於效能考量,使用者輸入訊息、@all、@here 以及 @channel 將不再發送通知。",
- "admin.team.maxNotificationsPerChannelExample": "例如 \"1000\"",
+ "admin.team.maxNotificationsPerChannelExample": "如:\"10000\"",
"admin.team.maxNotificationsPerChannelTitle": "單一頻道最大通知數:",
"admin.team.maxUsersDescription": "每個團隊最大人數,包含活躍與不活躍的使用者。",
"admin.team.maxUsersExample": "如:\"25\"",
@@ -923,8 +936,10 @@
"analytics.chart.meaningful": "沒有足夠有意義的資料可顯示。",
"analytics.system.activeUsers": "有發文的活躍使用者",
"analytics.system.channelTypes": "頻道類型",
+ "analytics.system.dailyActiveUsers": "Daily Active Users",
"analytics.system.expiredBanner": "企業版授權將在{date}過期。請在該日起15天內更新授權。詳情請洽<a href='mailto:commercial@mattermost.com'>commercial@mattermost.com</a>。",
"analytics.system.expiringBanner": "企業版授權將在{date}過期。請洽<a href='mailto:commercial@mattermost.com'>commercial@mattermost.com</a>以更新授權。",
+ "analytics.system.monthlyActiveUsers": "Monthly Active Users",
"analytics.system.postTypes": "發文,檔案與#標籤",
"analytics.system.privateGroups": "私人群組",
"analytics.system.publicChannels": "公開頻道",
@@ -1022,7 +1037,6 @@
"backstage_sidebar.integrations.outgoing_webhooks": "傳出的 Webhook",
"calling_screen": "撥打中",
"center_panel.recent": "按這裡跳到最新的訊息。",
- "chanel_header.addMembers": "新增成員",
"change_url.close": "關閉",
"change_url.endWithLetter": "必須以字元或數字做結尾",
"change_url.invalidUrl": "錯誤的網址",
@@ -1040,6 +1054,7 @@
"channel_flow.handleTooShort": "頻道網址必須為小寫英數字、至少兩個字元",
"channel_flow.invalidName": "無效的頻道名稱",
"channel_flow.set_url_title": "設定 {term} 網址",
+ "channel_header.addMembers": "新增成員",
"channel_header.addToFavorites": "新增至我的最愛",
"channel_header.channel": "頻道",
"channel_header.channelHeader": "編輯頻道標題",
@@ -1079,10 +1094,14 @@
"channel_loader.uploadedFile": " 已上傳一個檔案",
"channel_loader.uploadedImage": " 已上傳一張圖片",
"channel_loader.wrote": " 寫下: ",
+ "channel_members_dropdown.channel_admin": "Channel Admin",
+ "channel_members_dropdown.channel_member": "頻道成員",
+ "channel_members_dropdown.make_channel_admin": "Make Channel Admin",
+ "channel_members_dropdown.make_channel_member": "Make Channel Member",
+ "channel_members_dropdown.remove_from_channel": "Remove From Channel",
+ "channel_members_dropdown.remove_member": "Remove Member",
"channel_members_modal.addNew": " 增加新成員",
- "channel_members_modal.close": "關閉",
- "channel_members_modal.remove": "移除",
- "channel_memebers_modal.members": " 成員",
+ "channel_members_modal.members": " 成員",
"channel_modal.cancel": "取消",
"channel_modal.channel": "頻道",
"channel_modal.createNew": "建立新的",
@@ -1091,6 +1110,7 @@
"channel_modal.edit": "編輯",
"channel_modal.group": "群組",
"channel_modal.header": "標題",
+ "channel_modal.headerEx": "E.g.: \"[Link Title](http://example.com)\"",
"channel_modal.headerHelp": "設定除了{term}名字以外還會顯示在{term}標題的文字。舉例來說,可以輸入 [Link Title](http://example.com) 以顯示常用連結。",
"channel_modal.modalTitle": "新增",
"channel_modal.name": "名字",
@@ -1101,6 +1121,7 @@
"channel_modal.publicChannel1": "建立公開頻道",
"channel_modal.publicChannel2": "建立一個人人可加入的頻道。 ",
"channel_modal.purpose": "用途",
+ "channel_modal.purposeEx": "E.g.: \"A channel to file bugs and improvements\"",
"channel_notifications.allActivity": "所有的活動所有的活動",
"channel_notifications.allUnread": "全部的未讀訊息",
"channel_notifications.globalDefault": "系統預設({notifyLevel})",
@@ -1113,6 +1134,7 @@
"channel_notifications.unreadInfo": "當有未讀訊息時,側邊欄的頻道名字會用粗體表示。選擇\"僅限於提及您的\"將只有您被提及的頻道會用粗體。",
"channel_select.placeholder": "--- 選擇頻道 ---",
"channel_switch_modal.dm": "(直接傳訊)",
+ "channel_switch_modal.failed_to_open": "Failed to open channel.",
"channel_switch_modal.help": "請輸入頻道名。↑↓可以瀏覽、TAB 選擇、↵ 確認、ESC 取消",
"channel_switch_modal.not_found": "找不到符合的。",
"channel_switch_modal.submit": "切換",
@@ -1285,15 +1307,14 @@
"find_team.submitError": "請輸入一個有效的電子郵件位址",
"flag_post.flag": "標記以追蹤",
"flag_post.unflag": "取消標記",
+ "general_tab.chooseDescription": "為團隊取名",
"general_tab.chooseName": "為團隊取名",
"general_tab.codeDesc": "按下'修改'來重新產生邀請碼。",
"general_tab.codeLongDesc": "邀請碼用來當作主選單中<strong>取得團隊邀請連結</strong>所產生的團隊邀請連結的一部分。重新產生會建立一個新的招待連結並且讓舊的連結失效。",
"general_tab.codeTitle": "邀請碼",
- "general_tab.dirDisabled": "團隊列表已被關閉。請向系統管理員要求開啟系統控制台中的團隊列表。",
- "general_tab.dirOff": "此系統的團隊列表已被關閉。",
"general_tab.emptyDescription": "按下'編輯'以增加團隊敘述。",
+ "general_tab.getTeamInviteLink": "取得團隊邀請連結",
"general_tab.includeDirDesc": "在首頁的團隊列表顯示此團隊名字、提供通往登入頁面的連結。",
- "general_tab.includeDirTitle": "加入團隊列表",
"general_tab.no": "否",
"general_tab.openInviteDesc": "允許時,往此團隊的連結將會出現在首頁,有帳號的人皆可以此連結加入此團隊。",
"general_tab.openInviteTitle": "允許任何在此服務器有帳號的使用者加入此團隊",
@@ -1550,6 +1571,7 @@
"login.passwordChanged": " 已成功更新密碼",
"login.session_expired": " 工作階段已逾期,請重新登入。",
"login.signIn": "登入",
+ "login.signInLoading": "Signing in...",
"login.signInWith": "登入方法:",
"login.userNotFound": "無法找到與您輸入的認證相符的帳號。",
"login.username": "使用者名稱",
@@ -1561,8 +1583,10 @@
"member_item.makeAdmin": "設為管理員",
"member_item.member": "成員",
"member_list.noUsersAdd": "沒有可增加的使用者。",
+ "members_popover.manageMembers": "成員管理",
"members_popover.msg": "訊息",
"members_popover.title": "成員",
+ "members_popover.viewMembers": "檢視成員",
"mfa.confirm.complete": "<strong>完成設定!</strong>",
"mfa.confirm.okay": "確定",
"mfa.confirm.secure": "您的帳號現在安全了。下一次登入時,將會被要求輸入手機上 Google Authenticator 所提供的代碼。",
@@ -1665,6 +1689,7 @@
"post_info.mobile.unflag": "取消標記",
"post_info.permalink": "永久網址",
"post_info.reply": "回覆",
+ "post_message_view.edited": "(edited)",
"posts_view.loadMore": "載入更多訊息",
"posts_view.newMsg": "新訊息",
"posts_view.newMsgBelow": "下面還有 {count} 個新訊息",
@@ -1899,7 +1924,7 @@
"update_command.question": "變更可能會導致現有的斜線命令無法使用。您確定要更新嘛?",
"update_command.update": "更新",
"upload_overlay.info": "將檔案拖曳到這裡上傳。",
- "user.settings.advance.embed_preview": "可以使用時,顯示實驗性的連結內容預覽",
+ "user.settings.advance.embed_preview": "For the first web link in a message, display a preview of website content below the message, if available",
"user.settings.advance.embed_toggle": "內嵌預覽顯示開關",
"user.settings.advance.enabledFeatures": "已啟用 {count, number} 項功能",
"user.settings.advance.formattingDesc": "啟用時,文章會顯示連結、顯示繪文字、套用樣式到文字上並自動斷行。此設定預設為開啟。修改此設定後需要重新讀取頁面以生效。",
@@ -1944,10 +1969,10 @@
"user.settings.display.channelDisplayTitle": "頻道顯示模式",
"user.settings.display.channeldisplaymode": "選擇中央頻道的寬度。",
"user.settings.display.clockDisplay": "顯示時間",
- "user.settings.display.collapseDesc": "可用時,展開連結以顯示內容預覽。",
- "user.settings.display.collapseDisplay": "連結預覽",
- "user.settings.display.collapseOff": "關閉",
- "user.settings.display.collapseOn": "啟用",
+ "user.settings.display.collapseDesc": "Set whether previews of image links show as expanded or collapsed by default. This setting can also be controlled using the slash commands /expand and /collapse.",
+ "user.settings.display.collapseDisplay": "Default appearance of image link previews",
+ "user.settings.display.collapseOff": "Collapsed",
+ "user.settings.display.collapseOn": "Expanded",
"user.settings.display.fixedWidthCentered": "固定寬度,置中對齊",
"user.settings.display.fontDesc": "選擇 Mattermost 使用者介面的字型。",
"user.settings.display.fontTitle": "顯示字型",
@@ -2130,7 +2155,9 @@
"user.settings.security.lastUpdated": "最後一次更新於 {date} {time}",
"user.settings.security.ldap": "AD/LDAP",
"user.settings.security.loginGitlab": "已經由 GitLab 登入",
+ "user.settings.security.loginGoogle": "Login done through Google Apps",
"user.settings.security.loginLdap": "已經由 AD/LDAP 登入",
+ "user.settings.security.loginOffice365": "Login done through Office 365",
"user.settings.security.loginSaml": "已經由 SAML 登入",
"user.settings.security.logoutActiveSessions": "觀看並登出使用中的工作階段",
"user.settings.security.method": "登入方式",
@@ -2159,9 +2186,11 @@
"user.settings.security.passwordErrorUppercaseNumberSymbol": "密碼最短必須有{min}個字元且至少有一個大寫英文字母、一個數字和一個符號(\"~!@#$%^&*()\")。",
"user.settings.security.passwordErrorUppercaseSymbol": "密碼最短必須有{min}個字元且至少有一個大寫英文字母和一個符號(\"~!@#$%^&*()\")。",
"user.settings.security.passwordGitlabCantUpdate": "經由 GitLab 登入。無法變更密碼。",
+ "user.settings.security.passwordGoogleCantUpdate": "經由 GitLab 登入。無法變更密碼。",
"user.settings.security.passwordLdapCantUpdate": "經由 AD/LDAP 登入。無法變更密碼。",
"user.settings.security.passwordMatchError": "輸入的新密碼不一致。",
"user.settings.security.passwordMinLength": "無效的最短長度,無法顯示預覽。",
+ "user.settings.security.passwordOffice365CantUpdate": "經由 GitLab 登入。無法變更密碼。",
"user.settings.security.passwordSamlCantUpdate": "此欄位由您的登入提供者決定。如果想變更它,得經由登入提供者變更。",
"user.settings.security.retypePassword": "再次輸入新密碼",
"user.settings.security.saml": "SAML",
diff --git a/webapp/package.json b/webapp/package.json
index 1086fea30..e9a174798 100644
--- a/webapp/package.json
+++ b/webapp/package.json
@@ -16,7 +16,7 @@
"intl": "1.2.5",
"jasny-bootstrap": "3.1.3",
"jquery": "3.1.1",
- "marked": "mattermost/marked#69736482dbad685c398a5eec33a59b5ab06057ac",
+ "marked": "mattermost/marked#1adb66d8df3d582f4434e8e6cd401715463643d9",
"match-at": "0.1.0",
"object-assign": "4.1.0",
"pdfjs-dist": "1.6.319",
diff --git a/webapp/root.html b/webapp/root.html
index 3fc9dfa59..70cf47cc3 100644
--- a/webapp/root.html
+++ b/webapp/root.html
@@ -34,10 +34,52 @@
<!-- CSS Should always go first -->
<link rel='stylesheet' class='code_theme'>
+ <style>
+ .error-screen {
+ font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
+ padding-top: 50px;
+ max-width: 750px;
+ font-size: 14px;
+ color: #333333;
+ margin: auto;
+ display: none;
+ line-height: 1.5;
+ }
+ .error-screen h2 {
+ font-size: 30px;
+ font-weight: normal;
+ line-height: 1.2;
+ }
+
+ .error-screen ul {
+ padding-left: 15px;
+ line-height: 1.7;
+ margin-top: 0;
+ margin-bottom: 10px;
+ }
+
+ .error-screen hr {
+ color: #ddd;
+ margin-top: 20px;
+ margin-bottom: 20px;
+ border: 0;
+ border-top: 1px solid #eee;
+ }
+
+ .error-screen-visible {
+ display: block;
+ }
+ </style>
</head>
<body>
<div id='root'>
+ <div class='error-screen'>
+ <h2>Cannot connect to Mattermost</h2>
+ <hr/>
+ <p>We’re having trouble connecting to Mattermost. If refreshing this page (Ctrl+R or Command+R) does not work, please verify that your computer is connected to the internet.</p>
+ <br/>
+ </div>
<div
class='loading-screen'
style='position: relative'
@@ -50,6 +92,9 @@
</div>
</div>
<script>
+ if (typeof window.setup_root !== 'function') {
+ document.querySelector('.error-screen').classList.add('error-screen-visible');
+ }
window.setup_root();
</script>
<noscript>
diff --git a/webapp/sass/base/_typography.scss b/webapp/sass/base/_typography.scss
index f595e0ed9..1d3f1d052 100644
--- a/webapp/sass/base/_typography.scss
+++ b/webapp/sass/base/_typography.scss
@@ -26,6 +26,11 @@ body {
word-break: break-all;
}
+.overflow--ellipsis {
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
.fa {
&.fa-margin--left {
margin-left: 2px;
diff --git a/webapp/sass/layout/_post.scss b/webapp/sass/layout/_post.scss
index cd32ba55e..9448ad767 100644
--- a/webapp/sass/layout/_post.scss
+++ b/webapp/sass/layout/_post.scss
@@ -437,6 +437,15 @@
@include opacity(.9);
box-shadow: none;
}
+
+ &.btn-file__disabled {
+ @include opacity(.1);
+
+ &:hover,
+ &:active {
+ @include opacity(.1);
+ }
+ }
}
textarea {
@@ -560,9 +569,9 @@
}
blockquote {
- display: inline-block;
font-size: 1em;
margin-left: 0;
+ margin-top: 1.3em;
padding: 3px 0 0 25px;
vertical-align: top;
@@ -572,6 +581,11 @@
top: 2px;
}
}
+ .search-item-snippet {
+ blockquote {
+ margin-top: 0;
+ }
+ }
.markdown__heading {
clear: both;
@@ -598,7 +612,15 @@
}
p + p {
- margin-top: 1em;
+ margin: 1em 0;
+ &:last-of-type {
+ margin-bottom: 0;
+ }
+ }
+ span {
+ > p:first-child {
+ margin-bottom: 1em;
+ }
}
ol,
@@ -978,12 +1000,24 @@
width: 100%;
word-wrap: break-word;
- p {
+ div {
margin: 0 0 .4em;
}
p + p {
- margin-top: 1.4em;
+ margin: 1.4em 0;
+ &:last-of-type {
+ margin-bottom: 0;
+ }
+ }
+
+ span {
+ > p:last-child {
+ display: inline;
+ }
+ > p:first-child {
+ margin-bottom: 1.4em;
+ }
}
li {
@@ -1063,6 +1097,12 @@
color: $white;
}
}
+
+ span.edited {
+ color: #A3A3A3;
+ font-size: 0.87em;
+ opacity: 0.6;
+ }
}
.post__link {
diff --git a/webapp/sass/layout/_webhooks.scss b/webapp/sass/layout/_webhooks.scss
index 99a82f00e..904c50ccc 100644
--- a/webapp/sass/layout/_webhooks.scss
+++ b/webapp/sass/layout/_webhooks.scss
@@ -68,6 +68,9 @@
&.attachment__container--danger {
border-left-color: #e40303;
}
+ .sitename {
+ color: #A3A3A3;
+ }
}
.attachment__body {
@@ -80,6 +83,14 @@
&.attachment__body--no_thumb {
width: 100%;
}
+ .attachment__image {
+ margin-bottom: 0;
+ max-height: 150px;
+ max-width: 150px;
+ &.loading {
+ height: 150px;
+ }
+ }
}
.attachment__text p:last-of-type {
@@ -103,6 +114,13 @@
line-height: 18px;
margin: 5px 0;
padding: 0;
+
+ &.has-link {
+ color: #2f81b7;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ white-space: nowrap;
+ }
}
.attachment-link-more {
@@ -144,4 +162,4 @@
}
}
}
-} \ No newline at end of file
+}
diff --git a/webapp/sass/responsive/_mobile.scss b/webapp/sass/responsive/_mobile.scss
index 11b6312c9..5ade6046e 100644
--- a/webapp/sass/responsive/_mobile.scss
+++ b/webapp/sass/responsive/_mobile.scss
@@ -219,6 +219,9 @@
}
}
}
+ blockquote {
+ margin-top: 0;
+ }
}
&.same--root {
@@ -1077,7 +1080,7 @@
}
.post-create-footer {
- padding: 1em 0;
+ padding: 0 45px 0 45px;
.control-label {
margin: .5em 0;
diff --git a/webapp/sass/responsive/_tablet.scss b/webapp/sass/responsive/_tablet.scss
index c9bc1b06b..ac8b50961 100644
--- a/webapp/sass/responsive/_tablet.scss
+++ b/webapp/sass/responsive/_tablet.scss
@@ -26,7 +26,7 @@
}
.post-create-footer {
- padding: 0 1em;
+ padding: 0 45px 0 45px;
.msg-typing {
display: none;
diff --git a/webapp/stores/channel_store.jsx b/webapp/stores/channel_store.jsx
index c93edf7f4..30f395cc3 100644
--- a/webapp/stores/channel_store.jsx
+++ b/webapp/stores/channel_store.jsx
@@ -5,6 +5,7 @@ import AppDispatcher from '../dispatcher/app_dispatcher.jsx';
import EventEmitter from 'events';
import TeamStore from 'stores/team_store.jsx';
+import UserStore from 'stores/user_store.jsx';
var Utils;
import {ActionTypes, Constants} from 'utils/constants.jsx';
@@ -25,6 +26,7 @@ class ChannelStoreClass extends EventEmitter {
this.currentId = null;
this.postMode = this.POST_MODE_CHANNEL;
this.channels = [];
+ this.members_in_channel = {};
this.myChannelMembers = {};
this.moreChannels = {};
this.stats = {};
@@ -241,6 +243,29 @@ class ChannelStoreClass extends EventEmitter {
return this.myChannelMembers;
}
+ saveMembersInChannel(channelId = this.getCurrentId(), members) {
+ const oldMembers = this.members_in_channel[channelId] || {};
+ this.members_in_channel[channelId] = Object.assign({}, oldMembers, members);
+ }
+
+ removeMemberInChannel(channelId = this.getCurrentId(), userId) {
+ if (this.members_in_channel[channelId]) {
+ Reflect.deleteProperty(this.members_in_channel[channelId], userId);
+ }
+ }
+
+ getMembersInChannel(channelId = this.getCurrentId()) {
+ return Object.assign({}, this.members_in_channel[channelId]) || {};
+ }
+
+ hasActiveMemberInChannel(channelId = this.getCurrentId(), userId) {
+ if (this.members_in_channel[channelId] && this.members_in_channel[channelId][userId]) {
+ return true;
+ }
+
+ return false;
+ }
+
storeMoreChannels(channels, teamId = TeamStore.getCurrentId()) {
const newChannels = {};
for (let i = 0; i < channels.length; i++) {
@@ -259,7 +284,12 @@ class ChannelStoreClass extends EventEmitter {
getMoreChannelsList(teamId = TeamStore.getCurrentId()) {
const teamChannels = this.moreChannels[teamId] || {};
- return Object.keys(teamChannels).map((cid) => teamChannels[cid]);
+
+ if (!Utils) {
+ Utils = require('utils/utils.jsx'); //eslint-disable-line global-require
+ }
+
+ return Object.keys(teamChannels).map((cid) => teamChannels[cid]).sort(Utils.sortByDisplayName);
}
storeStats(stats) {
@@ -343,6 +373,25 @@ class ChannelStoreClass extends EventEmitter {
return channelNamesMap;
}
+
+ isChannelAdminForCurrentChannel() {
+ return this.isChannelAdmin(UserStore.getCurrentId(), this.getCurrentId());
+ }
+
+ isChannelAdmin(userId, channelId) {
+ if (!Utils) {
+ Utils = require('utils/utils.jsx'); //eslint-disable-line global-require
+ }
+
+ const channelMembers = this.getMembersInChannel(channelId);
+ const channelMember = channelMembers[userId];
+
+ if (channelMember) {
+ return Utils.isChannelAdmin(channelMember.roles);
+ }
+
+ return false;
+ }
}
var ChannelStore = new ChannelStoreClass();
@@ -409,7 +458,10 @@ ChannelStore.dispatchToken = AppDispatcher.register((payload) => {
ChannelStore.storeMoreChannels(action.channels);
ChannelStore.emitChange();
break;
-
+ case ActionTypes.RECEIVED_MEMBERS_IN_CHANNEL:
+ ChannelStore.saveMembersInChannel(action.channel_id, action.channel_members);
+ ChannelStore.emitChange();
+ break;
case ActionTypes.RECEIVED_CHANNEL_STATS:
var stats = Object.assign({}, ChannelStore.getStats());
stats[action.stats.channel_id] = action.stats;
diff --git a/webapp/stores/notification_store.jsx b/webapp/stores/notification_store.jsx
index 878ac3c9d..4c89fe480 100644
--- a/webapp/stores/notification_store.jsx
+++ b/webapp/stores/notification_store.jsx
@@ -111,7 +111,7 @@ class NotificationStoreClass extends EventEmitter {
// the window itself is not active
const activeChannel = ChannelStore.getCurrent();
const channelId = channel ? channel.id : null;
- const notify = activeChannel.id !== channelId || !this.inFocus;
+ const notify = (activeChannel && activeChannel.id !== channelId) || !this.inFocus;
if (notify) {
Utils.notifyMe(title, body, channel, teamId, duration, !sound);
diff --git a/webapp/stores/opengraph_store.jsx b/webapp/stores/opengraph_store.jsx
new file mode 100644
index 000000000..4ad156df0
--- /dev/null
+++ b/webapp/stores/opengraph_store.jsx
@@ -0,0 +1,68 @@
+import EventEmitter from 'events';
+
+import AppDispatcher from 'dispatcher/app_dispatcher.jsx';
+import Constants from 'utils/constants.jsx';
+
+const ActionTypes = Constants.ActionTypes;
+
+const CHANGE_EVENT = 'change';
+const URL_DATA_CHANGE_EVENT = 'url_data_change';
+
+class OpenGraphStoreClass extends EventEmitter {
+ constructor() {
+ super();
+ this.ogDataObject = {}; // Format: {<url>: <data-object>}
+ }
+
+ emitChange() {
+ this.emit(CHANGE_EVENT);
+ }
+
+ addChangeListener(callback) {
+ this.on(CHANGE_EVENT, callback);
+ }
+
+ removeChangeListener(callback) {
+ this.removeListener(CHANGE_EVENT, callback);
+ }
+
+ emitUrlDataChange(url) {
+ this.emit(URL_DATA_CHANGE_EVENT, url);
+ }
+
+ addUrlDataChangeListener(callback) {
+ this.on(URL_DATA_CHANGE_EVENT, callback);
+ }
+
+ removeUrlDataChangeListener(callback) {
+ this.removeListener(URL_DATA_CHANGE_EVENT, callback);
+ }
+
+ storeOgInfo(url, ogInfo) {
+ this.ogDataObject[url] = ogInfo;
+ }
+
+ getOgInfo(url) {
+ return this.ogDataObject[url];
+ }
+}
+
+var OpenGraphStore = new OpenGraphStoreClass();
+
+// Not expecting more that `Constants.POST_CHUNK_SIZE` post previews rendered at a time
+OpenGraphStore.setMaxListeners(Constants.POST_CHUNK_SIZE);
+
+OpenGraphStore.dispatchToken = AppDispatcher.register((payload) => {
+ var action = payload.action;
+
+ switch (action.type) {
+ case ActionTypes.RECIVED_OPEN_GRAPH_METADATA:
+ OpenGraphStore.storeOgInfo(action.url, action.data);
+ OpenGraphStore.emitUrlDataChange(action.url);
+ OpenGraphStore.emitChange();
+ break;
+ default:
+ }
+});
+
+export default OpenGraphStore;
diff --git a/webapp/stores/post_store.jsx b/webapp/stores/post_store.jsx
index 023e74e35..411eaf724 100644
--- a/webapp/stores/post_store.jsx
+++ b/webapp/stores/post_store.jsx
@@ -23,6 +23,7 @@ class PostStoreClass extends EventEmitter {
this.selectedPostId = null;
this.postsInfo = {};
this.latestPageTime = {};
+ this.earliestPostFromPage = {};
this.currentFocusedPostId = null;
}
emitChange() {
@@ -116,20 +117,8 @@ class PostStoreClass extends EventEmitter {
return null;
}
- getEarliestPost(id) {
- if (this.postsInfo.hasOwnProperty(id)) {
- const postList = this.postsInfo[id].postList;
-
- for (let i = postList.order.length - 1; i >= 0; i--) {
- const postId = postList.order[i];
-
- if (postList.posts[postId].state !== Constants.POST_DELETED) {
- return postList.posts[postId];
- }
- }
- }
-
- return null;
+ getEarliestPostFromPage(id) {
+ return this.earliestPostFromPage[id];
}
getLatestPost(id) {
@@ -207,7 +196,7 @@ class PostStoreClass extends EventEmitter {
return this.currentFocusedPostId;
}
- storePosts(id, newPosts, checkLatest) {
+ storePosts(id, newPosts, checkLatest, checkEarliest) {
if (isPostListNull(newPosts)) {
return;
}
@@ -225,6 +214,17 @@ class PostStoreClass extends EventEmitter {
}
}
+ if (checkEarliest) {
+ const currentEarliest = this.earliestPostFromPage[id] || {create_at: Number.MAX_SAFE_INTEGER};
+ const orderLength = newPosts.order.length;
+ if (orderLength >= 1) {
+ const newEarliestPost = newPosts.posts[newPosts.order[orderLength - 1]];
+ if (newEarliestPost.create_at < currentEarliest.create_at) {
+ this.earliestPostFromPage[id] = newEarliestPost;
+ }
+ }
+ }
+
const combinedPosts = makePostListNonNull(this.getAllPosts(id));
for (const pid in newPosts.posts) {
@@ -638,10 +638,10 @@ PostStore.dispatchToken = AppDispatcher.register((payload) => {
switch (action.type) {
case ActionTypes.RECEIVED_POSTS: {
if (PostStore.currentFocusedPostId !== null && action.isPost) {
- PostStore.storePosts(PostStore.currentFocusedPostId, makePostListNonNull(action.post_list), action.checkLatest);
+ PostStore.storePosts(PostStore.currentFocusedPostId, makePostListNonNull(action.post_list), action.checkLatest, action.checkEarliest);
PostStore.checkBounds(PostStore.currentFocusedPostId, action.numRequested, makePostListNonNull(action.post_list), action.before);
}
- PostStore.storePosts(action.id, makePostListNonNull(action.post_list), action.checkLatest);
+ PostStore.storePosts(action.id, makePostListNonNull(action.post_list), action.checkLatest, action.checkEarliest);
PostStore.checkBounds(action.id, action.numRequested, makePostListNonNull(action.post_list), action.before);
PostStore.emitChange();
break;
diff --git a/webapp/stores/team_store.jsx b/webapp/stores/team_store.jsx
index b2cb3ad26..6de47c65c 100644
--- a/webapp/stores/team_store.jsx
+++ b/webapp/stores/team_store.jsx
@@ -379,5 +379,7 @@ TeamStore.dispatchToken = AppDispatcher.register((payload) => {
}
});
+TeamStore.setMaxListeners(15);
+
window.TeamStore = TeamStore;
export default TeamStore;
diff --git a/webapp/stores/user_store.jsx b/webapp/stores/user_store.jsx
index 2369c38df..fb1e36590 100644
--- a/webapp/stores/user_store.jsx
+++ b/webapp/stores/user_store.jsx
@@ -576,7 +576,7 @@ class UserStoreClass extends EventEmitter {
var current = this.getCurrentUser();
if (current) {
- return Utils.isAdmin(current.roles);
+ return Utils.isSystemAdmin(current.roles);
}
return false;
diff --git a/webapp/tests/client_team.test.jsx b/webapp/tests/client_team.test.jsx
index 5fac2da6d..20610f676 100644
--- a/webapp/tests/client_team.test.jsx
+++ b/webapp/tests/client_team.test.jsx
@@ -23,19 +23,20 @@ describe('Client.Team', function() {
});
it('createTeam', function(done) {
- var client = TestHelper.createClient();
var team = TestHelper.fakeTeam();
- client.createTeam(
- team,
- function(data) {
- assert.equal(data.id.length > 0, true);
- assert.equal(data.name, team.name);
- done();
- },
- function(err) {
- done(new Error(err.message));
- }
- );
+ TestHelper.initBasic(() => {
+ TestHelper.basicClient().createTeam(
+ team,
+ function(data) {
+ assert.equal(data.id.length > 0, true);
+ assert.equal(data.name, team.name);
+ done();
+ },
+ function(err) {
+ done(new Error(err.message));
+ }
+ );
+ });
});
it('getAllTeams', function(done) {
diff --git a/webapp/tests/utils_get_nearest_point.test.jsx b/webapp/tests/utils_get_nearest_point.test.jsx
new file mode 100644
index 000000000..b0b0a2e0e
--- /dev/null
+++ b/webapp/tests/utils_get_nearest_point.test.jsx
@@ -0,0 +1,35 @@
+import assert from 'assert';
+import * as CommonUtils from 'utils/commons.jsx';
+
+describe('CommonUtils.getNearestPoint', function() {
+ this.timeout(10000);
+ it('should return nearest point', function() {
+ for (const data of [
+ {
+ points: [{x: 30, y: 40}, {x: 50, y: 50}, {x: 100, y: 2}, {x: 500, y: 200}, {x: 110, y: 20}, {x: 10, y: 20}],
+ pivotPoint: {x: 10, y: 20},
+ nearestPoint: {x: 10, y: 20},
+ nearestPointLte: {x: 10, y: 20}
+ },
+ {
+ points: [{x: 50, y: 50}, {x: 100, y: 2}, {x: 500, y: 200}, {x: 110, y: 20}, {x: 100, y: 90}, {x: 30, y: 40}],
+ pivotPoint: {x: 10, y: 20},
+ nearestPoint: {x: 30, y: 40},
+ nearestPointLte: {}
+ },
+ {
+ points: [{x: 50, y: 50}, {x: 1, y: 1}, {x: 15, y: 25}, {x: 100, y: 2}, {x: 500, y: 200}, {x: 110, y: 20}],
+ pivotPoint: {x: 10, y: 20},
+ nearestPoint: {x: 15, y: 25},
+ nearestPointLte: {x: 1, y: 1}
+ }
+ ]) {
+ const nearestPointData = CommonUtils.getNearestPoint(data.pivotPoint, data.points);
+
+ assert.equal(nearestPointData.nearestPoint.x, data.nearestPoint.x);
+ assert.equal(nearestPointData.nearestPoint.y, data.nearestPoint.y);
+ assert.equal(nearestPointData.nearestPointLte.x, data.nearestPointLte.x);
+ assert.equal(nearestPointData.nearestPointLte.y, data.nearestPointLte.y);
+ }
+ });
+});
diff --git a/webapp/utils/async_client.jsx b/webapp/utils/async_client.jsx
index 25724ec5e..cd38be811 100644
--- a/webapp/utils/async_client.jsx
+++ b/webapp/utils/async_client.jsx
@@ -1039,6 +1039,14 @@ export function getStandardAnalytics(teamId) {
if (data[index].name === 'total_read_db_connections') {
stats[StatTypes.TOTAL_READ_DB_CONNECTIONS] = data[index].value;
}
+
+ if (data[index].name === 'daily_active_users') {
+ stats[StatTypes.DAILY_ACTIVE_USERS] = data[index].value;
+ }
+
+ if (data[index].name === 'monthly_active_users') {
+ stats[StatTypes.MONTHLY_ACTIVE_USERS] = data[index].value;
+ }
}
AppDispatcher.handleServerAction({
diff --git a/webapp/utils/channel_intro_messages.jsx b/webapp/utils/channel_intro_messages.jsx
index 0c011734a..991bf54e8 100644
--- a/webapp/utils/channel_intro_messages.jsx
+++ b/webapp/utils/channel_intro_messages.jsx
@@ -48,7 +48,7 @@ export function createDMIntroMessage(channel, centeredIntro) {
<div className={'channel-intro ' + centeredIntro}>
<div className='post-profile-img__container channel-intro-img'>
<ProfilePicture
- src={Client.getUsersRoute() + '/' + teammate.id + '/image?time=' + teammate.update_at}
+ src={Client.getUsersRoute() + '/' + teammate.id + '/image?time=' + teammate.last_picture_update}
width='50'
height='50'
user={teammate}
diff --git a/webapp/utils/channel_utils.jsx b/webapp/utils/channel_utils.jsx
index ffc69d7b4..2189cd789 100644
--- a/webapp/utils/channel_utils.jsx
+++ b/webapp/utils/channel_utils.jsx
@@ -23,7 +23,7 @@ import LocalizationStore from 'stores/localization_store.jsx';
export function buildDisplayableChannelList(persistentChannels) {
const missingDMChannels = createMissingDirectChannels(persistentChannels);
- const channels = persistentChannels.concat(missingDMChannels).map(completeDirectChannelInfo);
+ const channels = persistentChannels.concat(missingDMChannels).map(completeDirectChannelInfo).filter(isNotDeletedChannel);
channels.sort(sortChannelsByDisplayName);
const favoriteChannels = channels.filter(isFavoriteChannel);
@@ -43,6 +43,14 @@ export function isFavoriteChannel(channel) {
return PreferenceStore.getBool(Preferences.CATEGORY_FAVORITE_CHANNEL, channel.id);
}
+export function isFavoriteChannelId(channelId) {
+ return PreferenceStore.getBool(Preferences.CATEGORY_FAVORITE_CHANNEL, channelId);
+}
+
+export function isNotDeletedChannel(channel) {
+ return channel.delete_at === 0;
+}
+
export function isOpenChannel(channel) {
return channel.type === Constants.OPEN_CHANNEL;
}
diff --git a/webapp/utils/commons.jsx b/webapp/utils/commons.jsx
new file mode 100644
index 000000000..1888869dc
--- /dev/null
+++ b/webapp/utils/commons.jsx
@@ -0,0 +1,36 @@
+export function getDistanceBW2Points(point1, point2, xAttr = 'x', yAttr = 'y') {
+ return Math.sqrt(Math.pow(point1[xAttr] - point2[xAttr], 2) + Math.pow(point1[yAttr] - point2[yAttr], 2));
+}
+
+/**
+ * Funtion to return nearest point of given pivot point.
+ * It return two points one nearest and other nearest but having both coorditanes smaller than the given point's coordinates.
+ */
+export function getNearestPoint(pivotPoint, points, xAttr = 'x', yAttr = 'y') {
+ var nearestPoint = {};
+ var nearestPointLte = {}; // Nearest point smaller than or equal to point
+ for (const point of points) {
+ if (typeof nearestPoint[xAttr] === 'undefined' || typeof nearestPoint[yAttr] === 'undefined') {
+ nearestPoint = point;
+ } else if (getDistanceBW2Points(point, pivotPoint, xAttr, yAttr) < getDistanceBW2Points(nearestPoint, pivotPoint, xAttr, yAttr)) {
+ // Check for bestImage
+ nearestPoint = point;
+ }
+
+ if (typeof nearestPointLte[xAttr] === 'undefined' || typeof nearestPointLte[yAttr] === 'undefined') {
+ if (point[xAttr] <= pivotPoint[xAttr] && point[yAttr] <= pivotPoint[yAttr]) {
+ nearestPointLte = point;
+ }
+ } else if (
+ // Check for bestImageLte
+ getDistanceBW2Points(point, pivotPoint, xAttr, yAttr) < getDistanceBW2Points(nearestPointLte, pivotPoint, xAttr, yAttr) &&
+ point[xAttr] <= pivotPoint[xAttr] && point[yAttr] <= pivotPoint[yAttr]
+ ) {
+ nearestPointLte = point;
+ }
+ }
+ return {
+ nearestPoint,
+ nearestPointLte
+ };
+}
diff --git a/webapp/utils/constants.jsx b/webapp/utils/constants.jsx
index 8162846e2..86147ee8c 100644
--- a/webapp/utils/constants.jsx
+++ b/webapp/utils/constants.jsx
@@ -74,6 +74,7 @@ export const ActionTypes = keyMirror({
RECEIVED_MORE_CHANNELS: null,
RECEIVED_CHANNEL_STATS: null,
RECEIVED_MY_CHANNEL_MEMBERS: null,
+ RECEIVED_MEMBERS_IN_CHANNEL: null,
FOCUS_POST: null,
RECEIVED_POSTS: null,
@@ -145,6 +146,9 @@ export const ActionTypes = keyMirror({
RECEIVED_LOCALE: null,
+ UPDATE_OPEN_GRAPH_METADATA: null,
+ RECIVED_OPEN_GRAPH_METADATA: null,
+
SHOW_SEARCH: null,
USER_TYPING: null,
@@ -256,7 +260,9 @@ export const Constants = {
NEWLY_CREATED_USERS: null,
TOTAL_WEBSOCKET_CONNECTIONS: null,
TOTAL_MASTER_DB_CONNECTIONS: null,
- TOTAL_READ_DB_CONNECTIONS: null
+ TOTAL_READ_DB_CONNECTIONS: null,
+ DAILY_ACTIVE_USERS: null,
+ MONTHLY_ACTIVE_USERS: null
}),
STAT_MAX_ACTIVE_USERS: 20,
STAT_MAX_NEW_USERS: 20,
@@ -737,63 +743,63 @@ export const Constants = {
},
CODE_PREVIEW_MAX_FILE_SIZE: 500000, // 500 KB
HighlightedLanguages: {
- actionscript: {name: 'ActionScript', extensions: ['as']},
+ actionscript: {name: 'ActionScript', extensions: ['as'], aliases: ['as', 'as3']},
applescript: {name: 'AppleScript', extensions: ['applescript', 'osascript', 'scpt']},
bash: {name: 'Bash', extensions: ['bash', 'sh', 'zsh']},
clojure: {name: 'Clojure', extensions: ['clj', 'boot', 'cl2', 'cljc', 'cljs', 'cljs.hl', 'cljscm', 'cljx', 'hic']},
- coffeescript: {name: 'CoffeeScript', extensions: ['coffee', '_coffee', 'cake', 'cjsx', 'cson', 'iced']},
- cpp: {name: 'C/C++', extensions: ['cpp', 'c', 'cc', 'h', 'c++', 'h++', 'hpp']},
- cs: {name: 'C#', extensions: ['cs', 'csharp']},
+ coffeescript: {name: 'CoffeeScript', extensions: ['coffee', '_coffee', 'cake', 'cjsx', 'cson', 'iced'], aliases: ['coffee', 'coffee-script']},
+ cpp: {name: 'C/C++', extensions: ['cpp', 'c', 'cc', 'h', 'c++', 'h++', 'hpp'], aliases: ['c++']},
+ cs: {name: 'C#', extensions: ['cs', 'csharp'], aliases: ['c#', 'csharp']},
css: {name: 'CSS', extensions: ['css']},
- d: {name: 'D', extensions: ['d', 'di']},
+ d: {name: 'D', extensions: ['d', 'di'], aliases: ['dlang']},
dart: {name: 'Dart', extensions: ['dart']},
delphi: {name: 'Delphi', extensions: ['delphi', 'dpr', 'dfm', 'pas', 'pascal', 'freepascal', 'lazarus', 'lpr', 'lfm']},
- diff: {name: 'Diff', extensions: ['diff', 'patch']},
+ diff: {name: 'Diff', extensions: ['diff', 'patch'], aliases: ['patch', 'udiff']},
django: {name: 'Django', extensions: ['django', 'jinja']},
- dockerfile: {name: 'Dockerfile', extensions: ['dockerfile', 'docker']},
- erlang: {name: 'Erlang', extensions: ['erl']},
+ dockerfile: {name: 'Dockerfile', extensions: ['dockerfile', 'docker'], aliases: ['docker']},
+ erlang: {name: 'Erlang', extensions: ['erl'], aliases: ['erl']},
fortran: {name: 'Fortran', extensions: ['f90', 'f95']},
fsharp: {name: 'F#', extensions: ['fsharp', 'fs']},
gcode: {name: 'G-Code', extensions: ['gcode', 'nc']},
- go: {name: 'Go', extensions: ['go']},
+ go: {name: 'Go', extensions: ['go'], aliases: ['golang']},
groovy: {name: 'Groovy', extensions: ['groovy']},
- handlebars: {name: 'Handlebars', extensions: ['handlebars', 'hbs', 'html.hbs', 'html.handlebars']},
- haskell: {name: 'Haskell', extensions: ['hs']},
+ handlebars: {name: 'Handlebars', extensions: ['handlebars', 'hbs', 'html.hbs', 'html.handlebars'], aliases: ['hbs', 'mustache']},
+ haskell: {name: 'Haskell', extensions: ['hs'], aliases: ['hs']},
haxe: {name: 'Haxe', extensions: ['hx']},
java: {name: 'Java', extensions: ['java', 'jsp']},
- javascript: {name: 'JavaScript', extensions: ['js', 'jsx']},
+ javascript: {name: 'JavaScript', extensions: ['js', 'jsx'], aliases: ['js']},
json: {name: 'JSON', extensions: ['json']},
- julia: {name: 'Julia', extensions: ['jl']},
+ julia: {name: 'Julia', extensions: ['jl'], aliases: ['jl']},
kotlin: {name: 'Kotlin', extensions: ['kt', 'ktm', 'kts']},
less: {name: 'Less', extensions: ['less']},
lisp: {name: 'Lisp', extensions: ['lisp']},
lua: {name: 'Lua', extensions: ['lua']},
- makefile: {name: 'Makefile', extensions: ['mk', 'mak']},
- markdown: {name: 'Markdown', extensions: ['md', 'mkdown', 'mkd']},
- matlab: {name: 'Matlab', extensions: ['matlab', 'm']},
- objectivec: {name: 'Objective C', extensions: ['mm', 'objc', 'obj-c']},
+ makefile: {name: 'Makefile', extensions: ['mk', 'mak'], aliases: ['make', 'mf', 'gnumake', 'bsdmake']},
+ markdown: {name: 'Markdown', extensions: ['md', 'mkdown', 'mkd'], aliases: ['md', 'mkd']},
+ matlab: {name: 'Matlab', extensions: ['matlab', 'm'], aliases: ['m']},
+ objectivec: {name: 'Objective C', extensions: ['mm', 'objc', 'obj-c'], aliases: ['objective_c', 'objc']},
ocaml: {name: 'OCaml', extensions: ['ml']},
- perl: {name: 'Perl', extensions: ['perl', 'pl']},
- php: {name: 'PHP', extensions: ['php', 'php3', 'php4', 'php5', 'php6']},
- powershell: {name: 'PowerShell', extensions: ['ps', 'ps1']},
- puppet: {name: 'Puppet', extensions: ['pp']},
- python: {name: 'Python', extensions: ['py', 'gyp']},
- r: {name: 'R', extensions: ['r']},
- ruby: {name: 'Ruby', extensions: ['ruby', 'rb', 'gemspec', 'podspec', 'thor', 'irb']},
- rust: {name: 'Rust', extensions: ['rs']},
+ perl: {name: 'Perl', extensions: ['perl', 'pl'], aliases: ['pl']},
+ php: {name: 'PHP', extensions: ['php', 'php3', 'php4', 'php5', 'php6'], aliases: ['php3', 'php4', 'php5']},
+ powershell: {name: 'PowerShell', extensions: ['ps', 'ps1'], aliases: ['posh']},
+ puppet: {name: 'Puppet', extensions: ['pp'], aliases: ['pp']},
+ python: {name: 'Python', extensions: ['py', 'gyp'], aliases: ['py']},
+ r: {name: 'R', extensions: ['r'], aliases: ['r', 's']},
+ ruby: {name: 'Ruby', extensions: ['ruby', 'rb', 'gemspec', 'podspec', 'thor', 'irb'], aliases: ['rb']},
+ rust: {name: 'Rust', extensions: ['rs'], aliases: ['rs']},
scala: {name: 'Scala', extensions: ['scala']},
scheme: {name: 'Scheme', extensions: ['scm', 'sld']},
scss: {name: 'SCSS', extensions: ['scss']},
- smalltalk: {name: 'Smalltalk', extensions: ['st']},
+ smalltalk: {name: 'Smalltalk', extensions: ['st'], aliases: ['st', 'squeak']},
sql: {name: 'SQL', extensions: ['sql']},
swift: {name: 'Swift', extensions: ['swift']},
- tex: {name: 'TeX', extensions: ['tex']},
+ tex: {name: 'TeX', extensions: ['tex'], aliases: ['latex']},
text: {name: 'Text', extensions: ['txt']},
- vbnet: {name: 'VB.Net', extensions: ['vbnet', 'vb', 'bas']},
+ vbnet: {name: 'VB.Net', extensions: ['vbnet', 'vb', 'bas'], aliases: ['vb', 'visualbasic']},
vbscript: {name: 'VBScript', extensions: ['vbs']},
verilog: {name: 'Verilog', extensions: ['v', 'veo']},
xml: {name: 'HTML, XML', extensions: ['xml', 'html', 'xhtml', 'rss', 'atom', 'xsl', 'plist']},
- yaml: {name: 'YAML', extensions: ['yaml']}
+ yaml: {name: 'YAML', extensions: ['yaml'], aliases: ['yml']}
},
PostsViewJumpTypes: {
BOTTOM: 1,
@@ -859,6 +865,13 @@ export const Constants = {
PERMISSIONS_ALL: 'all',
PERMISSIONS_TEAM_ADMIN: 'team_admin',
PERMISSIONS_SYSTEM_ADMIN: 'system_admin',
+ PERMISSIONS_DELETE_POST_ALL: 'all',
+ PERMISSIONS_DELETE_POST_TEAM_ADMIN: 'team_admin',
+ PERMISSIONS_DELETE_POST_SYSTEM_ADMIN: 'system_admin',
+ ALLOW_EDIT_POST_ALWAYS: 'always',
+ ALLOW_EDIT_POST_NEVER: 'never',
+ ALLOW_EDIT_POST_TIME_LIMIT: 'time_limit',
+ DEFAULT_POST_EDIT_TIME_LIMIT: 300,
MENTION_CHANNELS: 'mention.channels',
MENTION_MORE_CHANNELS: 'mention.morechannels',
MENTION_MEMBERS: 'mention.members',
diff --git a/webapp/utils/markdown.jsx b/webapp/utils/markdown.jsx
index 8a0b9ef0a..c84df0fa5 100644
--- a/webapp/utils/markdown.jsx
+++ b/webapp/utils/markdown.jsx
@@ -8,7 +8,18 @@ import marked from 'marked';
import katex from 'katex';
function markdownImageLoaded(image) {
- image.style.height = 'auto';
+ if (image.hasAttribute('height') && image.attributes.height.value !== 'auto') {
+ const maxHeight = parseInt(global.getComputedStyle(image).maxHeight, 10);
+
+ if (image.attributes.height.value > maxHeight) {
+ image.style.height = maxHeight + 'px';
+ image.style.width = ((maxHeight * image.attributes.width.value) / image.attributes.height.value) + 'px';
+ } else {
+ image.style.height = image.attributes.height.value + 'px';
+ }
+ } else {
+ image.style.height = 'auto';
+ }
}
global.markdownImageLoaded = markdownImageLoaded;
@@ -117,10 +128,29 @@ class MattermostMarkdownRenderer extends marked.Renderer {
}
image(href, title, text) {
- let out = '<img src="' + href + '" alt="' + text + '"';
+ let src = href;
+ let dimensions = [];
+ const parts = href.split(' ');
+ if (parts.length > 1) {
+ const lastPart = parts.pop();
+ src = parts.join(' ');
+ if (lastPart[0] === '=') {
+ dimensions = lastPart.substr(1).split('x');
+ if (dimensions.length === 2 && dimensions[1] === '') {
+ dimensions[1] = 'auto';
+ }
+ }
+ }
+ let out = '<img src="' + src + '" alt="' + text + '"';
if (title) {
out += ' title="' + title + '"';
}
+ if (dimensions.length > 0) {
+ out += ' width="' + dimensions[0] + '"';
+ }
+ if (dimensions.length > 1) {
+ out += ' height="' + dimensions[1] + '"';
+ }
out += ' onload="window.markdownImageLoaded(this)" onerror="window.markdownImageLoaded(this)" class="markdown-inline-img"';
out += this.options.xhtml ? '/>' : '>';
return out;
@@ -222,7 +252,8 @@ export function format(text, options = {}) {
renderer: new MattermostMarkdownRenderer(null, options),
sanitize: true,
gfm: true,
- tables: true
+ tables: true,
+ mangle: false
};
return marked(text, markdownOptions);
diff --git a/webapp/utils/post_utils.jsx b/webapp/utils/post_utils.jsx
index 4bba784cb..20993b95c 100644
--- a/webapp/utils/post_utils.jsx
+++ b/webapp/utils/post_utils.jsx
@@ -3,11 +3,19 @@
import Client from 'client/web_client.jsx';
import Constants from 'utils/constants.jsx';
+import * as Utils from 'utils/utils.jsx';
+
+import TeamStore from 'stores/team_store.jsx';
+import UserStore from 'stores/user_store.jsx';
export function isSystemMessage(post) {
return post.type && (post.type.lastIndexOf(Constants.SYSTEM_MESSAGE_PREFIX) === 0);
}
+export function isPostOwner(post) {
+ return UserStore.getCurrentId() === post.user_id;
+}
+
export function isComment(post) {
if ('root_id' in post) {
return post.root_id !== '' && post.root_id != null;
@@ -15,6 +23,10 @@ export function isComment(post) {
return false;
}
+export function isEdited(post) {
+ return post.edit_at > 0;
+}
+
export function getProfilePicSrcForPost(post, timestamp) {
let src = Client.getUsersRoute() + '/' + post.user_id + '/image?time=' + timestamp;
if (post.props && post.props.from_webhook && global.window.mm_config.EnablePostIconOverride === 'true') {
@@ -28,4 +40,37 @@ export function getProfilePicSrcForPost(post, timestamp) {
}
return src;
+}
+
+export function canDeletePost(post) {
+ var isOwner = isPostOwner(post);
+ var isAdmin = TeamStore.isTeamAdminForCurrentTeam() || UserStore.isSystemAdminForCurrentUser();
+ var isSystemAdmin = UserStore.isSystemAdminForCurrentUser();
+
+ if (global.window.mm_license.IsLicensed === 'true') {
+ return (global.window.mm_config.RestrictPostDelete === Constants.PERMISSIONS_DELETE_POST_ALL && (isOwner || isAdmin)) ||
+ (global.window.mm_config.RestrictPostDelete === Constants.PERMISSIONS_DELETE_POST_TEAM_ADMIN && isAdmin) ||
+ (global.window.mm_config.RestrictPostDelete === Constants.PERMISSIONS_DELETE_POST_SYSTEM_ADMIN && isSystemAdmin);
+ }
+ return isOwner || isAdmin;
+}
+
+export function canEditPost(post, editDisableAction) {
+ var isOwner = isPostOwner(post);
+
+ var canEdit = isOwner && !isSystemMessage(post);
+
+ if (canEdit && global.window.mm_license.IsLicensed === 'true') {
+ if (global.window.mm_config.AllowEditPost === Constants.ALLOW_EDIT_POST_NEVER) {
+ canEdit = false;
+ } else if (global.window.mm_config.AllowEditPost === Constants.ALLOW_EDIT_POST_TIME_LIMIT) {
+ var timeLeft = (post.create_at + (global.window.mm_config.PostEditTimeLimit * 1000)) - Utils.getTimestamp();
+ if (timeLeft > 0) {
+ editDisableAction.fireAfter(timeLeft + 1000);
+ } else {
+ canEdit = false;
+ }
+ }
+ }
+ return canEdit;
} \ No newline at end of file
diff --git a/webapp/utils/syntax_highlighting.jsx b/webapp/utils/syntax_highlighting.jsx
index 47ba5bd4e..73e1087cb 100644
--- a/webapp/utils/syntax_highlighting.jsx
+++ b/webapp/utils/syntax_highlighting.jsx
@@ -123,9 +123,9 @@ hlJS.registerLanguage('yaml', hljsYaml);
const HighlightedLanguages = Constants.HighlightedLanguages;
export function highlight(lang, code) {
- const language = lang.toLowerCase();
+ const language = getLanguageFromNameOrAlias(lang);
- if (HighlightedLanguages[language]) {
+ if (language) {
try {
return hlJS.highlight(language, code).value;
} catch (e) {
@@ -147,13 +147,25 @@ export function getLanguageFromFileExtension(extension) {
}
export function canHighlight(language) {
- return Boolean(HighlightedLanguages[language.toLowerCase()]);
+ return Boolean(getLanguageFromNameOrAlias(language));
}
export function getLanguageName(language) {
if (canHighlight(language)) {
- return HighlightedLanguages[language.toLowerCase()].name;
+ return HighlightedLanguages[getLanguageFromNameOrAlias(language)].name;
}
return '';
-} \ No newline at end of file
+}
+
+function getLanguageFromNameOrAlias(name) {
+ const langName = name.toLowerCase();
+ if (HighlightedLanguages[langName]) {
+ return langName;
+ }
+
+ return Object.keys(HighlightedLanguages).find((key) => {
+ const aliases = HighlightedLanguages[key].aliases;
+ return aliases && aliases.find((a) => a === langName);
+ });
+}
diff --git a/webapp/utils/text_formatting.jsx b/webapp/utils/text_formatting.jsx
index 9f983a1ee..171226558 100644
--- a/webapp/utils/text_formatting.jsx
+++ b/webapp/utils/text_formatting.jsx
@@ -395,7 +395,13 @@ function parseSearchTerms(searchTerm) {
}
// remove punctuation from each term
- terms = terms.map((term) => term.replace(puncStart, '').replace(puncEnd, ''));
+ terms = terms.map((term) => {
+ term.replace(puncStart, '');
+ if (term.charAt(term.length - 1) !== '*') {
+ term.replace(puncEnd, '');
+ }
+ return term;
+ });
return terms;
}
@@ -452,6 +458,11 @@ export function highlightSearchTerms(text, tokens, searchPatterns) {
output = output.replace(alias, newAlias);
}
+
+ // The pattern regexes are global, so calling pattern.test() above alters their
+ // state. Reset lastIndex to 0 between calls to test() to ensure it returns the
+ // same result every time it is called with the same value of token.originalText.
+ pattern.lastIndex = 0;
}
// the new tokens are stashed in a separate map since we can't add objects to a map during iteration
diff --git a/webapp/utils/utils.jsx b/webapp/utils/utils.jsx
index e7ed9567b..a0aecbdb3 100644
--- a/webapp/utils/utils.jsx
+++ b/webapp/utils/utils.jsx
@@ -54,6 +54,14 @@ export function isInRole(roles, inRole) {
return false;
}
+export function isChannelAdmin(roles) {
+ if (isInRole(roles, 'channel_admin')) {
+ return true;
+ }
+
+ return false;
+}
+
export function isAdmin(roles) {
if (isInRole(roles, 'team_admin')) {
return true;
@@ -1316,3 +1324,15 @@ export function handleFormattedTextClick(e) {
browserHistory.push('/' + TeamStore.getCurrent().name + '/channels/' + channelMentionAttribute.value);
}
}
+
+export function isEmptyObject(object) {
+ if (!object) {
+ return true;
+ }
+
+ if (Object.keys(object).length === 0) {
+ return true;
+ }
+
+ return false;
+}