diff --git a/app/ui-flextab/client/tabs/userEdit.js b/app/ui-flextab/client/tabs/userEdit.js
index 2e7e4afa08da..cb888b400b5e 100644
--- a/app/ui-flextab/client/tabs/userEdit.js
+++ b/app/ui-flextab/client/tabs/userEdit.js
@@ -5,8 +5,11 @@ import { Template } from 'meteor/templating';
import { TAPi18n } from 'meteor/tap:i18n';
import { t, handleError } from '../../../utils';
import { Roles } from '../../../models';
+import { Notifications } from '../../../notifications';
import { hasAtLeastOnePermission } from '../../../authorization';
+import { settings } from '../../../settings';
import toastr from 'toastr';
+import { callbacks } from '../../../callbacks';
import s from 'underscore.string';
Template.userEdit.helpers({
@@ -18,10 +21,23 @@ Template.userEdit.helpers({
return (Template.instance().user && hasAtLeastOnePermission('edit-other-user-info')) || (!Template.instance().user && hasAtLeastOnePermission('create-user'));
},
+ selectUrl() {
+ return Template.instance().url.get().trim() ? '' : 'disabled';
+ },
+
user() {
return Template.instance().user;
},
+ initialsUsername() {
+ const { user } = Template.instance();
+ return `@${ user && user.username }`;
+ },
+
+ avatarPreview() {
+ return Template.instance().avatar.get();
+ },
+
requirePasswordChange() {
return !Template.instance().user || Template.instance().user.requirePasswordChange;
},
@@ -41,6 +57,54 @@ Template.userEdit.helpers({
});
Template.userEdit.events({
+ 'click .js-select-avatar-initials'(e, template) {
+ template.avatar.set({
+ service: 'initials',
+ blob: `@${ template.user.username }`,
+ });
+ },
+
+ 'click .js-select-avatar-url'(e, template) {
+ const url = template.url.get().trim();
+ if (!url) {
+ return;
+ }
+
+ template.avatar.set({
+ service: 'url',
+ contentType: '',
+ blob: url,
+ });
+ },
+
+ 'input .js-avatar-url-input'(e, template) {
+ const text = e.target.value;
+ template.url.set(text);
+ },
+
+ 'change .js-select-avatar-upload [type=file]'(event, template) {
+ const e = event.originalEvent || event;
+ let { files } = e.target;
+ if (!files || files.length === 0) {
+ files = (e.dataTransfer && e.dataTransfer.files) || [];
+ }
+ Object.keys(files).forEach((key) => {
+ const blob = files[key];
+ if (!/image\/.+/.test(blob.type)) {
+ return;
+ }
+ const reader = new FileReader();
+ reader.readAsDataURL(blob);
+ reader.onloadend = function() {
+ template.avatar.set({
+ service: 'upload',
+ contentType: blob.type,
+ blob: reader.result,
+ });
+ };
+ });
+ },
+
'click .cancel'(e, t) {
e.stopPropagation();
e.preventDefault();
@@ -98,7 +162,9 @@ Template.userEdit.events({
Template.userEdit.onCreated(function() {
this.user = this.data != null ? this.data.user : undefined;
this.roles = this.user ? new ReactiveVar(this.user.roles) : new ReactiveVar([]);
-
+ this.avatar = new ReactiveVar;
+ this.url = new ReactiveVar('');
+ Notifications.onLogged('updateAvatar', () => this.avatar.set());
const { tabBar } = Template.currentData();
@@ -136,7 +202,7 @@ Template.userEdit.onCreated(function() {
const userData = this.getUserData();
const errors = [];
- if (!userData.name) {
+ if (settings.get('Accounts_RequireNameForSignUp') && !userData.name) {
errors.push('Name');
}
if (!userData.username) {
@@ -162,7 +228,6 @@ Template.userEdit.onCreated(function() {
return;
}
const userData = this.getUserData();
-
if (this.user != null) {
for (const key in userData) {
if (key) {
@@ -176,6 +241,28 @@ Template.userEdit.onCreated(function() {
}
}
+ const avatar = this.avatar.get();
+ if (avatar) {
+ let method;
+ const params = [];
+
+ if (avatar.service === 'initials') {
+ method = 'resetAvatar';
+ } else {
+ method = 'setAvatarFromService';
+ params.push(avatar.blob, avatar.contentType, avatar.service);
+ }
+
+ Meteor.call(method, ...params, Template.instance().user._id, function(err) {
+ if (err && err.details) {
+ toastr.error(t(err.message));
+ } else {
+ toastr.success(t('Avatar_changed_successfully'));
+ callbacks.run('userAvatarSet', avatar.service);
+ }
+ });
+ }
+
Meteor.call('insertOrUpdateUser', userData, (error) => {
if (error) {
return handleError(error);
diff --git a/app/ui-master/client/main.js b/app/ui-master/client/main.js
index d14224512a95..c5fad0cba14d 100644
--- a/app/ui-master/client/main.js
+++ b/app/ui-master/client/main.js
@@ -218,13 +218,7 @@ Template.main.events({
Template.main.onRendered(function() {
$('#initial-page-loading').remove();
- window.addEventListener('focus', function() {
- return Meteor.setTimeout(function() {
- if (!$(':focus').is('INPUT,TEXTAREA')) {
- return $('.input-message').focus();
- }
- }, 100);
- });
+
return Tracker.autorun(function() {
const userId = Meteor.userId();
const Show_Setup_Wizard = settings.get('Show_Setup_Wizard');
diff --git a/app/ui-message/client/message.html b/app/ui-message/client/message.html
index e94649cdb49c..da32cc24061c 100644
--- a/app/ui-message/client/message.html
+++ b/app/ui-message/client/message.html
@@ -1,35 +1,35 @@
-
+
{{#if avatar}}
{{#if avatarFromUsername}}
- {{> avatar username=avatarFromUsername}}
+ {{> avatar username=avatarFromUsername}}
{{else}}
-
+
{{/if}}
{{else}}
{{#if emoji}}
-
+
- {{{getEmoji emoji}}}
+ {{> renderEmoji emoji}}
{{else}}
- {{> avatar username=u.username}}
+ {{> avatar username=msg.u.username}}
{{/if}}
{{/if}}
- {{getName}}{{#if showUsername}} @{{u.username}} {{/if}}
+ {{getName}}{{#if showUsername}} @{{msg.u.username}} {{/if}}
- {{#each roleTags}}
- {{description}}
+ {{#each role in roleTags}}
+ {{role.description}}
{{/each}}
{{#if isBot}}
- Bot
+ {{_ "Bot"}}
{{/if}}
- {{time}}
+ {{time}}
{{#if showTranslated}}
@@ -46,6 +46,7 @@
{{> avatar username=editedBy}}
{{/if}}
+ {{#if msg.alert}}
{{/if}}
{{#if private}}
{{_ "Only_you_can_see_this_message"}}
{{/if}}
@@ -56,7 +57,7 @@
{{/if}}
{{#if isIgnored}}
- {{_ "Message_Ignored"}}
+ {{_ "Message_Ignored"}}
{{/if}}
{{#if isSnippet}}
@@ -65,31 +66,53 @@
{{#if isDecrypting}}
******
{{else}}
- {{{body}}}
+ {{#if and settings.showreply msg.tmid}}
+
+ {{_ "Replied_on"}}:
+
+ {{{parentMessage}}}
+
+
+
+ {{{body}}}
+
+ {{else}}
+ {{{body}}}
+ {{/if}}
{{/if}}
{{#if hasOembed}}
- {{#each urls}}
+ {{#each msg.urls}}
{{injectIndex . @index}} {{> oembedBaseWidget}}
{{/each}}
{{/if}}
- {{#each attachments}}
+ {{#each msg.attachments}}
{{injectIndex . @index}} {{> messageAttachment}}
{{/each}}
- {{#if drid}}
+ {{#if msg.drid}}
- {{#if $gt dcount 0}}
+ {{#if $gt msg.dcount 0}}
- {{dcount}} {{_ i18nKeyReply}}
+ {{msg.dcount}} {{_ i18nKeyMessage}}
{{else}}
{{_ "No_messages_yet" }}
{{/if}}
- {{dlm}}
+ {{ formatDate msg.dlm }}
+
+ {{/if}}
+
+ {{#if and settings.showReplyButton msg.tcount}}
+
+
+ {{> icon icon="thread"}}
+ {{msg.tcount}} {{_ i18nKeyReply}}
+
+ {{ formatDate msg.tlm}}
{{/if}}
@@ -100,23 +123,21 @@
{{/with}}
{{#unless system}}
- {{#if messageActions 'message'}}
-
- {{#each action in messageActions 'message'}}
-
- {{> icon block="message-actions__button-icon" icon=action.icon}}
-
- {{/each}}
-
- {{/if}}
- {{#if messageActions 'menu'}}
-
- {{/if}}
+
+
+ {{#each action in messageActions 'message'}}
+
+ {{> icon block="message-actions__button-icon" icon=action.icon}}
+
+ {{/each}}
+
+
{{/unless}}
-
+ {{#unless hideActionLinks}}
+
{{#each actionLink in actionLinks}}
@@ -132,26 +153,29 @@
{{/each}}
+ {{/unless}}
{{# if broadcast}}
- {{#with u}}
- {{> icon block="rc-button-broadcast__icon" icon="reply"}} {{_'Reply'}}
+ {{#with msg.u}}
+
+ {{> icon icon="reply"}} {{_'Reply'}}
+
{{/with}}
{{/if}}
-
+ {{# unless hideReactions}}
+
{{#each reaction in reactions}}
{{> renderEmoji reaction.emoji}}
{{reaction.count}}
-
-
- {{reaction.usernames}} {{reaction.reaction}}
-
-
+
+ {{reaction.usernames}} {{reaction.reaction}}
+
{{/each}}
+ {{/unless}}
diff --git a/app/ui-message/client/message.js b/app/ui-message/client/message.js
index d0b8e951c0f7..bfe2b2b81be7 100644
--- a/app/ui-message/client/message.js
+++ b/app/ui-message/client/message.js
@@ -1,20 +1,20 @@
+import _ from 'underscore';
+import moment from 'moment';
+
import { Meteor } from 'meteor/meteor';
import { Blaze } from 'meteor/blaze';
-import { Session } from 'meteor/session';
import { Template } from 'meteor/templating';
import { TAPi18n } from 'meteor/tap:i18n';
-import _ from 'underscore';
-import moment from 'moment';
-import { DateFormat } from '../../lib';
-import { renderEmoji } from '../../emoji';
-import { renderMessageBody, MessageTypes, MessageAction } from '../../ui-utils';
-import { settings } from '../../settings';
-import { RoomRoles, UserRoles, Roles, Subscriptions, Rooms } from '../../models';
+
+import { timeAgo } from '../../lib/client/lib/formatDate';
+import { DateFormat } from '../../lib/client';
+import { renderMessageBody, MessageTypes, MessageAction, call, normalizeThreadMessage } from '../../ui-utils/client';
+import { RoomRoles, UserRoles, Roles, Messages } from '../../models/client';
import { AutoTranslate } from '../../autotranslate/client';
-import { hasAtLeastOnePermission } from '../../authorization';
-import { callbacks } from '../../callbacks';
+import { callbacks } from '../../callbacks/client';
import { Markdown } from '../../markdown/client';
-import { t, getUserPreference, roomTypes } from '../../utils';
+import { t, roomTypes, getURL } from '../../utils';
+import { messageArgs } from '../../ui-utils/client/lib/messageArgs';
async function renderPdfToCanvas(canvasId, pdfLink) {
const isSafari = /constructor/i.test(window.HTMLElement) ||
@@ -31,6 +31,7 @@ async function renderPdfToCanvas(canvasId, pdfLink) {
if (!pdfLink || !/\.pdf$/i.test(pdfLink)) {
return;
}
+ pdfLink = getURL(pdfLink);
const canvas = document.getElementById(canvasId);
if (!canvas) {
@@ -68,47 +69,60 @@ async function renderPdfToCanvas(canvasId, pdfLink) {
}
Template.message.helpers({
- i18nKeyReply() {
- return this.dcount > 1
+ and(a, b) {
+ return a && b;
+ },
+ i18nKeyMessage() {
+ const { msg } = this;
+ return msg.dcount > 1
? 'messages'
: 'message';
},
- dlm() {
- return this.dlm && moment(this.dlm).format('LLL');
+ i18nKeyReply() {
+ const { msg } = this;
+ return msg.tcount > 1
+ ? 'replies'
+ : 'reply';
+ },
+ formatDate(date) {
+ return moment(date).format('LLL');
},
encodeURI(text) {
return encodeURI(text);
},
broadcast() {
- const instance = Template.instance();
- return !this.private && !this.t && this.u._id !== Meteor.userId() && instance.room && instance.room.broadcast;
+ const { msg, room = {}, u } = this;
+ return !msg.private && !msg.t && msg.u._id !== u._id && room && room.broadcast;
},
isIgnored() {
- return this.ignored;
+ const { msg } = this;
+ return msg.ignored;
},
ignoredClass() {
- return this.ignored ? 'message--ignored' : '';
+ const { msg } = this;
+ return msg.ignored ? 'message--ignored' : '';
},
isDecrypting() {
- return this.e2e === 'pending';
+ const { msg } = this;
+ return msg.e2e === 'pending';
},
isBot() {
- if (this.bot != null) {
- return 'bot';
- }
+ const { msg } = this;
+ return msg.bot && 'bot';
},
roleTags() {
- if (!settings.get('UI_DisplayRoles') || getUserPreference(Meteor.userId(), 'hideRoles')) {
+ const { msg, hideRoles } = this;
+ if (hideRoles) {
return [];
}
- if (!this.u || !this.u._id) {
+ if (!msg.u || !msg.u._id) {
return [];
}
- const userRoles = UserRoles.findOne(this.u._id);
+ const userRoles = UserRoles.findOne(msg.u._id);
const roomRoles = RoomRoles.findOne({
- 'u._id': this.u._id,
- rid: this.rid,
+ 'u._id': msg.u._id,
+ rid: msg.rid,
});
const roles = [...(userRoles && userRoles.roles) || [], ...(roomRoles && roomRoles.roles) || []];
return Roles.find({
@@ -126,59 +140,64 @@ Template.message.helpers({
});
},
isGroupable() {
- if (Template.instance().room.broadcast || this.groupable === false) {
+ const { msg, room = {}, settings, groupable } = this;
+ if (groupable === false || settings.allowGroup === false || room.broadcast || msg.groupable === false) {
return 'false';
}
},
- isSequential() {
- return this.groupable !== false && !Template.instance().room.broadcast;
- },
sequentialClass() {
- if (this.groupable !== false) {
- return 'sequential';
- }
+ const { msg, groupable } = this;
+ return groupable !== false && msg.groupable !== false && 'sequential';
},
avatarFromUsername() {
- if ((this.avatar != null) && this.avatar[0] === '@') {
- return this.avatar.replace(/^@/, '');
+ const { msg } = this;
+
+ if (msg.avatar != null && msg.avatar[0] === '@') {
+ return msg.avatar.replace(/^@/, '');
}
},
- getEmoji(emoji) {
- return renderEmoji(emoji);
- },
getName() {
- if (this.alias) {
- return this.alias;
+ const { msg, settings } = this;
+ if (msg.alias) {
+ return msg.alias;
}
- if (!this.u) {
+ if (!msg.u) {
return '';
}
- return (settings.get('UI_Use_Real_Name') && this.u.name) || this.u.username;
+ return (settings.UI_Use_Real_Name && msg.u.name) || msg.u.username;
},
showUsername() {
- return this.alias || (settings.get('UI_Use_Real_Name') && this.u && this.u.name);
+ const { msg, settings } = this;
+ return msg.alias || (settings.UI_Use_Real_Name && msg.u && msg.u.name);
},
own() {
- if (this.u && this.u._id === Meteor.userId()) {
+ const { msg, u } = this;
+ if (msg.u && msg.u._id === u._id) {
return 'own';
}
},
timestamp() {
- return +this.ts;
+ const { msg } = this;
+ return +msg.ts;
},
chatops() {
- if (this.u && this.u.username === settings.get('Chatops_Username')) {
+ const { msg, settings } = this;
+ if (msg.u && msg.u.username === settings.Chatops_Username) {
return 'chatops-message';
}
},
time() {
- return DateFormat.formatTime(this.ts);
+ const { msg, timeAgo: useTimeAgo } = this;
+
+ return useTimeAgo ? timeAgo(msg.ts) : DateFormat.formatTime(msg.ts);
},
date() {
- return DateFormat.formatDate(this.ts);
+ const { msg } = this;
+ return DateFormat.formatDate(msg.ts);
},
isTemp() {
- if (this.temp === true) {
+ const { msg } = this;
+ if (msg.temp === true) {
return 'temp';
}
},
@@ -186,10 +205,12 @@ Template.message.helpers({
return Template.instance().body;
},
bodyClass() {
- return MessageTypes.isSystemMessage(this) ? 'color-info-font-color' : 'color-primary-font-color';
+ const { msg } = this;
+ return MessageTypes.isSystemMessage(msg) ? 'color-info-font-color' : 'color-primary-font-color';
},
system(returnClass) {
- if (MessageTypes.isSystemMessage(this)) {
+ const { msg } = this;
+ if (MessageTypes.isSystemMessage(msg)) {
if (returnClass) {
return 'color-info-font-color';
}
@@ -197,130 +218,85 @@ Template.message.helpers({
}
},
showTranslated() {
- if (settings.get('AutoTranslate_Enabled') && this.u && this.u._id !== Meteor.userId() && !MessageTypes.isSystemMessage(this)) {
- const subscription = Subscriptions.findOne({
- rid: this.rid,
- 'u._id': Meteor.userId(),
- }, {
- fields: {
- autoTranslate: 1,
- autoTranslateLanguage: 1,
- },
- });
- const language = AutoTranslate.getLanguage(this.rid);
- return this.autoTranslateFetching || (subscription && subscription.autoTranslate !== this.autoTranslateShowInverse && this.translations && this.translations[language]);
+ const { msg, subscription, settings, u } = this;
+ if (settings.AutoTranslate_Enabled && msg.u && msg.u._id !== u._id && !MessageTypes.isSystemMessage(msg)) {
+ const language = AutoTranslate.getLanguage(msg.rid);
+ const autoTranslate = subscription && subscription.autoTranslate;
+ return msg.autoTranslateFetching || (!!autoTranslate !== !!msg.autoTranslateShowInverse && msg.translations && msg.translations[language]);
}
},
edited() {
return Template.instance().wasEdited;
},
editTime() {
+ const { msg } = this;
if (Template.instance().wasEdited) {
- return DateFormat.formatDateAndTime(this.editedAt);
+ return DateFormat.formatDateAndTime(msg.editedAt);
}
},
editedBy() {
if (!Template.instance().wasEdited) {
return '';
}
+ const { msg } = this;
// try to return the username of the editor,
// otherwise a special "?" character that will be
// rendered as a special avatar
- return (this.editedBy && this.editedBy.username) || '?';
- },
- canEdit() {
- const hasPermission = hasAtLeastOnePermission('edit-message', this.rid);
- const isEditAllowed = settings.get('Message_AllowEditing');
- const editOwn = this.u && this.u._id === Meteor.userId();
- if (!(hasPermission || (isEditAllowed && editOwn))) {
- return;
- }
- const blockEditInMinutes = settings.get('Message_AllowEditing_BlockEditInMinutes');
- if (blockEditInMinutes) {
- let msgTs;
- if (this.ts != null) {
- msgTs = moment(this.ts);
- }
- let currentTsDiff;
- if (msgTs != null) {
- currentTsDiff = moment().diff(msgTs, 'minutes');
- }
- return currentTsDiff < blockEditInMinutes;
- } else {
- return true;
- }
- },
- canDelete() {
- const hasPermission = hasAtLeastOnePermission('delete-message', this.rid);
- const isDeleteAllowed = settings.get('Message_AllowDeleting');
- const deleteOwn = this.u && this.u._id === Meteor.userId();
- if (!(hasPermission || (isDeleteAllowed && deleteOwn))) {
- return;
- }
- const blockDeleteInMinutes = settings.get('Message_AllowDeleting_BlockDeleteInMinutes');
- if (blockDeleteInMinutes) {
- let msgTs;
- if (this.ts != null) {
- msgTs = moment(this.ts);
- }
- let currentTsDiff;
- if (msgTs != null) {
- currentTsDiff = moment().diff(msgTs, 'minutes');
- }
- return currentTsDiff < blockDeleteInMinutes;
- } else {
- return true;
- }
- },
- showEditedStatus() {
- return settings.get('Message_ShowEditedStatus');
+ return (msg.editedBy && msg.editedBy.username) || '?';
},
label() {
- if (this.i18nLabel) {
- return t(this.i18nLabel);
- } else if (this.label) {
- return this.label;
+ const { msg } = this;
+
+ if (msg.i18nLabel) {
+ return t(msg.i18nLabel);
+ } else if (msg.label) {
+ return msg.label;
}
},
hasOembed() {
+ const { msg, settings } = this;
// there is no URLs, there is no template to show the oembed (oembed package removed) or oembed is not enable
- if (!(this.urls && this.urls.length > 0) || !Template.oembedBaseWidget || !settings.get('API_Embed')) {
+ if (!(msg.urls && msg.urls.length > 0) || !Template.oembedBaseWidget || !settings.API_Embed) {
return false;
}
// check if oembed is disabled for message's sender
- if ((settings.get('API_EmbedDisabledFor') || '').split(',').map((username) => username.trim()).includes(this.u && this.u.username)) {
+ if ((settings.API_EmbedDisabledFor || '').split(',').map((username) => username.trim()).includes(msg.u && msg.u.username)) {
return false;
}
return true;
},
reactions() {
- const userUsername = Meteor.user() && Meteor.user().username;
- return Object.keys(this.reactions || {}).map((emoji) => {
- const reaction = this.reactions[emoji];
- const total = reaction.usernames.length;
- let usernames = reaction.usernames
- .slice(0, 15)
- .map((username) => (username === userUsername ? t('You').toLowerCase() : `@${ username }`))
- .join(', ');
- if (total > 15) {
- usernames = `${ usernames } ${ t('And_more', {
- length: total - 15,
- }).toLowerCase() }`;
- } else {
- usernames = usernames.replace(/,([^,]+)$/, ` ${ t('and') }$1`);
- }
- if (usernames[0] !== '@') {
- usernames = usernames[0].toUpperCase() + usernames.substr(1);
- }
- return {
- emoji,
- count: reaction.usernames.length,
- usernames,
- reaction: ` ${ t('Reacted_with').toLowerCase() } ${ emoji }`,
- userReacted: reaction.usernames.indexOf(userUsername) > -1,
- };
- });
+ const { msg: { reactions = {} }, u: { username: myUsername, name: myName } } = this;
+
+ return Object.entries(reactions)
+ .map(([emoji, reaction]) => {
+ const myDisplayName = reaction.names ? myName : `@${ myUsername }`;
+ const displayNames = (reaction.names || reaction.usernames.map((username) => `@${ username }`));
+ const selectedDisplayNames = displayNames.slice(0, 15).filter((displayName) => displayName !== myDisplayName);
+
+ if (displayNames.some((displayName) => displayName === myDisplayName)) {
+ selectedDisplayNames.unshift(t('You'));
+ }
+
+ let usernames;
+
+ if (displayNames.length > 15) {
+ usernames = `${ selectedDisplayNames.join(', ') }${ t('And_more', { length: displayNames.length - 15 }).toLowerCase() }`;
+ } else if (displayNames.length > 1) {
+ usernames = `${ selectedDisplayNames.slice(0, -1).join(', ') } ${ t('and') } ${ selectedDisplayNames[selectedDisplayNames.length - 1] }`;
+ } else {
+ usernames = selectedDisplayNames[0];
+ }
+
+ return {
+ emoji,
+ count: displayNames.length,
+ usernames,
+ reaction: ` ${ t('Reacted_with').toLowerCase() } ${ emoji }`,
+ userReacted: displayNames.indexOf(myDisplayName) > -1,
+ };
+ });
},
markUserReaction(reaction) {
if (reaction.userReacted) {
@@ -330,54 +306,53 @@ Template.message.helpers({
}
},
hideReactions() {
- if (_.isEmpty(this.reactions)) {
+ const { msg } = this;
+ if (_.isEmpty(msg.reactions)) {
return 'hidden';
}
},
actionLinks() {
+ const { msg } = this;
// remove 'method_id' and 'params' properties
- return _.map(this.actionLinks, function(actionLink, key) {
+ return _.map(msg.actionLinks, function(actionLink, key) {
return _.extend({
id: key,
}, _.omit(actionLink, 'method_id', 'params'));
});
},
hideActionLinks() {
- if (_.isEmpty(this.actionLinks)) {
+ const { msg } = this;
+ if (_.isEmpty(msg.actionLinks)) {
return 'hidden';
}
},
injectIndex(data, index) {
data.index = index;
},
- hideCog() {
- const subscription = Subscriptions.findOne({
- rid: this.rid,
- });
- if (subscription == null) {
- return 'hidden';
- }
- },
channelName() {
- const subscription = Subscriptions.findOne({ rid: this.rid });
+ const { subscription } = this;
+ // const subscription = Subscriptions.findOne({ rid: this.rid });
return subscription && subscription.name;
},
roomIcon() {
- const room = Session.get(`roomData${ this.rid }`);
+ const { room } = this;
if (room && room.t === 'd') {
return 'at';
}
return roomTypes.getIcon(room);
},
fromSearch() {
- return this.customClass === 'search';
+ const { customClass } = this;
+ return customClass === 'search';
},
actionContext() {
- return this.actionContext;
+ const { msg } = this;
+ return msg.actionContext;
},
messageActions(group) {
+ const { msg, context: ctx } = this;
let messageGroup = group;
- let context = this.actionContext;
+ let context = ctx || msg.actionContext;
if (!group) {
messageGroup = 'message';
@@ -387,114 +362,179 @@ Template.message.helpers({
context = 'message';
}
- return MessageAction.getButtons(Template.currentData(), context, messageGroup);
+ return MessageAction.getButtons(msg, context, messageGroup);
},
isSnippet() {
- return this.actionContext === 'snippeted';
+ const { msg } = this;
+ return msg.actionContext === 'snippeted';
+ },
+ parentMessage() {
+ const { msg: { threadMsg } } = this;
+ return threadMsg;
},
});
-Template.message.onCreated(function() {
- let msg = Template.currentData();
-
- this.wasEdited = (msg.editedAt != null) && !MessageTypes.isSystemMessage(msg);
+const findParentMessage = (() => {
- this.room = Rooms.findOne({
- _id: msg.rid,
- }, {
- fields: {
- broadcast: 1,
- },
- });
+ const waiting = [];
- return this.body = (() => {
- const isSystemMessage = MessageTypes.isSystemMessage(msg);
- const messageType = MessageTypes.getType(msg) || {};
- if (messageType.render) {
- msg = messageType.render(msg);
- } else if (messageType.template) {
- // render template
- } else if (messageType.message) {
- if (typeof messageType.data === 'function' && messageType.data(msg)) {
- msg = TAPi18n.__(messageType.message, messageType.data(msg));
- } else {
- msg = TAPi18n.__(messageType.message);
+ const getMessages = _.debounce(async function() {
+ const _tmp = [...waiting];
+ waiting.length = 0;
+ const messages = await call('getMessages', _tmp);
+ messages.forEach((message) => {
+ if (!message) {
+ return;
}
- } else if (msg.u && msg.u.username === settings.get('Chatops_Username')) {
- msg.html = msg.msg;
- msg = callbacks.run('renderMentions', msg);
- msg = msg.html;
- } else {
- msg = renderMessageBody(msg);
+ const { _id, ...msg } = message;
+ Messages.update({ tmid: _id, repliesCount: { $exists: 0 } }, {
+ $set: {
+ threadMsg: normalizeThreadMessage(msg),
+ repliesCount: msg.tcount,
+ },
+ }, { multi: true });
+ Messages.upsert({ _id }, msg);
+ });
+ }, 500);
+
+ return (tmid) => {
+ if (waiting.indexOf(tmid) > -1) {
+ return;
}
- if (isSystemMessage) {
- msg.html = Markdown.parse(msg.html);
+ const message = Messages.findOne({ _id: tmid });
+
+ if (message) {
+ return Messages.update({ tmid, repliesCount: { $exists: 0 } }, {
+ $set: {
+ threadMsg: normalizeThreadMessage(message),
+ repliesCount: message.tcount,
+ },
+ }, { multi: true });
}
- return msg;
- })();
+
+ waiting.push(tmid);
+ getMessages();
+ };
+})();
+
+
+const renderBody = (msg, settings) => {
+ const isSystemMessage = MessageTypes.isSystemMessage(msg);
+ const messageType = MessageTypes.getType(msg) || {};
+
+ if (messageType.render) {
+ msg = messageType.render(msg);
+ } else if (messageType.template) {
+ // render template
+ } else if (messageType.message) {
+ msg = TAPi18n.__(messageType.message, { ... typeof messageType.data === 'function' && messageType.data(msg) });
+ } else if (msg.u && msg.u.username === settings.Chatops_Username) {
+ msg.html = msg.msg;
+ msg = callbacks.run('renderMentions', msg);
+ msg = msg.html;
+ } else {
+ msg = renderMessageBody(msg);
+ }
+
+ if (isSystemMessage) {
+ msg.html = Markdown.parse(msg.html);
+ }
+ return msg;
+};
+
+Template.message.onCreated(function() {
+ const { msg, settings } = Template.currentData();
+
+ this.wasEdited = msg.editedAt && !MessageTypes.isSystemMessage(msg);
+ if (msg.tmid && !msg.threadMsg) {
+ findParentMessage(msg.tmid);
+ }
+ return this.body = renderBody(msg, settings);
});
-Template.message.onViewRendered = function(context) {
+const hasTempClass = (node) => node.classList.contains('temp');
+
+const getPreviousSentMessage = (currentNode) => {
+ if (hasTempClass(currentNode)) {
+ return currentNode.previousElementSibling;
+ }
+ if (currentNode.previousElementSibling != null) {
+ let previousValid = currentNode.previousElementSibling;
+ while (previousValid != null && (hasTempClass(previousValid) || !previousValid.classList.contains('message'))) {
+ previousValid = previousValid.previousElementSibling;
+ }
+ return previousValid;
+ }
+};
+
+const setNewDayAndGroup = (currentNode, previousNode, forceDate, period, noDate) => {
+ const { classList, dataset: currentDataset } = currentNode;
+
+ if (!previousNode) {
+ classList.remove('sequential');
+ !noDate && classList.add('new-day');
+ return;
+ }
+
+ const { dataset: previousDataset } = previousNode;
+ const previousMessageDate = new Date(parseInt(previousDataset.timestamp));
+ const currentMessageDate = new Date(parseInt(currentDataset.timestamp));
+
+ if (!noDate && (forceDate || previousMessageDate.toDateString() !== currentMessageDate.toDateString())) {
+ classList.remove('sequential');
+ classList.add('new-day');
+ }
+
+ if (previousDataset.tmid !== currentDataset.tmid) {
+ return classList.remove('sequential');
+ }
+
+ if (previousDataset.username !== currentDataset.username || parseInt(currentDataset.timestamp) - parseInt(previousDataset.timestamp) > period) {
+ return classList.remove('sequential');
+ }
+
+ if ([previousDataset.groupable, currentDataset.groupable].includes('false')) {
+ return classList.remove('sequential');
+ }
+};
+
+Template.message.onViewRendered = function() {
+ const { settings, forceDate, noDate, groupable, msg } = messageArgs(Template.currentData());
+
+ if (noDate && !groupable) {
+ return;
+ }
+
return this._domrange.onAttached((domRange) => {
- if (context.file && context.file.type === 'application/pdf') {
- Meteor.defer(() => { renderPdfToCanvas(context.file._id, context.attachments[0].title_link); });
+ if (msg.file && msg.file.type === 'application/pdf') {
+ Meteor.defer(() => { renderPdfToCanvas(msg.file._id, msg.attachments[0].title_link); });
}
const currentNode = domRange.lastNode();
const currentDataset = currentNode.dataset;
- const getPreviousSentMessage = (currentNode) => {
- if ($(currentNode).hasClass('temp')) {
- return currentNode.previousElementSibling;
- }
- if (currentNode.previousElementSibling != null) {
- let previousValid = currentNode.previousElementSibling;
- while (previousValid != null && $(previousValid).hasClass('temp')) {
- previousValid = previousValid.previousElementSibling;
- }
- return previousValid;
- }
- };
const previousNode = getPreviousSentMessage(currentNode);
const nextNode = currentNode.nextElementSibling;
- const $currentNode = $(currentNode);
- const $nextNode = $(nextNode);
- if (previousNode == null) {
- $currentNode.addClass('new-day').removeClass('sequential');
- } else if (previousNode.dataset) {
- const previousDataset = previousNode.dataset;
- const previousMessageDate = new Date(parseInt(previousDataset.timestamp));
- const currentMessageDate = new Date(parseInt(currentDataset.timestamp));
- if (previousMessageDate.toDateString() !== currentMessageDate.toDateString()) {
- $currentNode.addClass('new-day').removeClass('sequential');
- } else {
- $currentNode.removeClass('new-day');
- }
- if (previousDataset.groupable === 'false' || currentDataset.groupable === 'false') {
- $currentNode.removeClass('sequential');
- } else if (previousDataset.username !== currentDataset.username || parseInt(currentDataset.timestamp) - parseInt(previousDataset.timestamp) > settings.get('Message_GroupingPeriod') * 1000) {
- $currentNode.removeClass('sequential');
- } else if (!$currentNode.hasClass('new-day')) {
- $currentNode.addClass('sequential');
- }
- }
+ setNewDayAndGroup(currentNode, previousNode, forceDate, settings.Message_GroupingPeriod, noDate);
if (nextNode && nextNode.dataset) {
const nextDataset = nextNode.dataset;
- if (nextDataset.date !== currentDataset.date) {
- $nextNode.addClass('new-day').removeClass('sequential');
+ if (forceDate || nextDataset.date !== currentDataset.date) {
+ if (!noDate) {
+ currentNode.classList.add('new-day');
+ }
+ currentNode.classList.remove('sequential');
} else {
- $nextNode.removeClass('new-day');
+ nextNode.classList.remove('new-day');
}
if (nextDataset.groupable !== 'false') {
- if (nextDataset.username !== currentDataset.username || parseInt(nextDataset.timestamp) - parseInt(currentDataset.timestamp) > settings.get('Message_GroupingPeriod') * 1000) {
- $nextNode.removeClass('sequential');
- } else if (!$nextNode.hasClass('new-day') && !$currentNode.hasClass('temp')) {
- $nextNode.addClass('sequential');
+ if (nextDataset.tmid !== currentDataset.tmid || nextDataset.username !== currentDataset.username || parseInt(nextDataset.timestamp) - parseInt(currentDataset.timestamp) > settings.Message_GroupingPeriod) {
+ nextNode.classList.remove('sequential');
+ } else if (!nextNode.classList.contains('new-day') && !currentNode.classList.contains('temp')) {
+ nextNode.classList.add('sequential');
}
}
- }
- if (nextNode == null) {
- const [el] = $(`#chat-window-${ context.rid }`);
+ } else {
+ const [el] = $(`#chat-window-${ msg.rid }`);
const view = el && Blaze.getView(el);
const templateInstance = view && view.templateInstance();
if (!templateInstance) {
diff --git a/app/ui-message/client/messageBox.html b/app/ui-message/client/messageBox.html
index 6b672023a1ea..3a31d5572da9 100644
--- a/app/ui-message/client/messageBox.html
+++ b/app/ui-message/client/messageBox.html
@@ -1,22 +1,26 @@
- {{#unless isAnonymousOrJoinCode}}
- {{> messageBoxTyping rid=_id}}
+ {{#unless isAnonymousOrMustJoinWithCode}}
+ {{#unless tmid}}
+ {{> messageBoxTyping rid=rid}}
+ {{/unless}}
- {{#if canSend}}
- {{> messagePopupConfig popupConfig}}
- {{> messagePopupSlashCommandPreview popupConfig}}
+ {{#if isWritable}}
+ {{#if popupConfig}}
+ {{> messagePopupConfig popupConfig}}
+ {{> messagePopupSlashCommandPreview popupConfig}}
+ {{/if}}
{{#if replyMessageData}}
{{/if}}
-
+
{{> icon block="rc-input__icon-svg" icon="emoji"}}
@@ -25,8 +29,8 @@
{{> icon block="rc-input__icon-svg" icon="send"}}
{{else}}
- {{#if subscribed}}
- {{> messageBoxAudioMessage rid=_id}}
+ {{#if canSend}}
+ {{> messageBoxAudioMessage rid=rid tmid=tmid}}
- {{#unless isEmbedded}}
- {{#if showFormattingTips}}
-
- {{/if}}
- {{/unless}}
+ {{#if showFormattingTips}}
+
+ {{/if}}
{{else}}
{{#if isBlockedOrBlocker}}
@@ -79,7 +83,7 @@
{{/if}}
{{else}}
- {{> messageBoxNotSubscribed _id=_id}}
+ {{> messageBoxNotSubscribed rid=rid}}
{{/unless}}
diff --git a/app/ui-message/client/messageBox.js b/app/ui-message/client/messageBox.js
index f4610733df61..e9e0b093c1ff 100644
--- a/app/ui-message/client/messageBox.js
+++ b/app/ui-message/client/messageBox.js
@@ -1,224 +1,163 @@
import { Meteor } from 'meteor/meteor';
import { ReactiveVar } from 'meteor/reactive-var';
+import { ReactiveDict } from 'meteor/reactive-dict';
import { Session } from 'meteor/session';
import { Template } from 'meteor/templating';
import { Tracker } from 'meteor/tracker';
import { EmojiPicker } from '../../emoji';
-import { katex } from '../../katex/client';
-import { Markdown } from '../../markdown/client';
-import { ChatSubscription } from '../../models';
+import { Users } from '../../models';
import { settings } from '../../settings';
import {
- ChatMessages,
- chatMessages,
fileUpload,
KonchatNotification,
} from '../../ui';
-import { Layout, messageBox, popover, RoomManager, call } from '../../ui-utils';
-import { t, roomTypes, getUserPreference } from '../../utils';
+import {
+ messageBox,
+ popover,
+ call,
+ keyCodes,
+ isRTL,
+} from '../../ui-utils';
+import {
+ t,
+ roomTypes,
+ getUserPreference,
+} from '../../utils';
import moment from 'moment';
-
+import {
+ formattingButtons,
+ applyFormatting,
+} from './messageBoxFormatting';
import './messageBoxReplyPreview';
import './messageBoxTyping';
import './messageBoxAudioMessage';
import './messageBoxNotSubscribed';
import './messageBox.html';
-const formattingButtons = [
- {
- label: 'bold',
- icon: 'bold',
- pattern: '*{{text}}*',
- command: 'b',
- condition: () => Markdown && settings.get('Markdown_Parser') === 'original',
- },
- {
- label: 'bold',
- icon: 'bold',
- pattern: '**{{text}}**',
- command: 'b',
- condition: () => Markdown && settings.get('Markdown_Parser') === 'marked',
- },
- {
- label: 'italic',
- icon: 'italic',
- pattern: '_{{text}}_',
- command: 'i',
- condition: () => Markdown && settings.get('Markdown_Parser') !== 'disabled',
- },
- {
- label: 'strike',
- icon: 'strike',
- pattern: '~{{text}}~',
- condition: () => Markdown && settings.get('Markdown_Parser') === 'original',
- },
- {
- label: 'strike',
- icon: 'strike',
- pattern: '~~{{text}}~~',
- condition: () => Markdown && settings.get('Markdown_Parser') === 'marked',
- },
- {
- label: 'inline_code',
- icon: 'code',
- pattern: '`{{text}}`',
- condition: () => Markdown && settings.get('Markdown_Parser') !== 'disabled',
- },
- {
- label: 'multi_line',
- icon: 'multiline',
- pattern: '```\n{{text}}\n``` ',
- condition: () => Markdown && settings.get('Markdown_Parser') !== 'disabled',
- },
- {
- label: 'KaTeX',
- text: () => {
- if (!katex.isEnabled()) {
- return;
- }
- if (katex.isDollarSyntaxEnabled()) {
- return '$$KaTeX$$';
- }
- if (katex.isParenthesisSyntaxEnabled()) {
- return '\\[KaTeX\\]';
- }
- },
- link: 'https://khan.github.io/KaTeX/function-support.html',
- condition: () => katex.isEnabled(),
- },
-];
-
-
-function applyFormatting(event, instance) {
- event.preventDefault();
- const { input } = chatMessages[RoomManager.openedRoom];
- const { selectionEnd = input.value.length, selectionStart = 0 } = input;
- const initText = input.value.slice(0, selectionStart);
- const selectedText = input.value.slice(selectionStart, selectionEnd);
- const finalText = input.value.slice(selectionEnd, input.value.length);
- const [btn] = instance.findAll(`.js-format[aria-label=${ this.label }]`);
- if (btn) {
- btn.classList.add('active');
- setTimeout(() => {
- btn.classList.remove('active');
- }, 100);
- }
- input.focus();
-
- const startPattern = this.pattern.substr(0, this.pattern.indexOf('{{text}}'));
- const startPatternFound = [...startPattern].reverse().every((char, index) => input.value.substr(selectionStart - index - 1, 1) === char);
+Template.messageBox.onCreated(function() {
+ this.state = new ReactiveDict();
+ EmojiPicker.init();
+ this.popupConfig = new ReactiveVar(null);
+ this.replyMessageData = new ReactiveVar();
+ this.isMicrophoneDenied = new ReactiveVar(true);
+ this.sendIconDisabled = new ReactiveVar(false);
- if (startPatternFound) {
- const endPattern = this.pattern.substr(this.pattern.indexOf('{{text}}') + '{{text}}'.length);
- const endPatternFound = [...endPattern].every((char, index) => input.value.substr(selectionEnd + index, 1) === char);
+ this.send = (event) => {
+ if (!this.input) {
+ return;
+ }
- if (endPatternFound) {
- input.selectionStart = selectionStart - startPattern.length;
- input.selectionEnd = selectionEnd + endPattern.length;
+ const { rid, tmid, onSend } = this.data;
+ const { value } = this.input;
+ this.input.value = '';
+ onSend && onSend.call(this.data, event, { rid, tmid, value }, () => {
+ this.input.updateAutogrow();
+ this.input.focus();
+ });
+ };
+});
- if (!document.execCommand || !document.execCommand('insertText', false, selectedText)) {
- input.value = initText.substr(0, initText.length - startPattern.length) + selectedText + finalText.substr(endPattern.length);
- }
+Template.messageBox.onRendered(function() {
- input.selectionStart = selectionStart - startPattern.length;
- input.selectionEnd = input.selectionStart + selectedText.length;
- $(input).change();
- return;
+ this.autorun(() => {
+ const { rid, subscription } = Template.currentData();
+ const room = Session.get(`roomData${ rid }`);
+
+ if (!room) {
+ return this.state.set({
+ room: false,
+ isBlockedOrBlocker: false,
+ mustJoinWithCode: false,
+ });
}
- }
-
- if (!document.execCommand || !document.execCommand('insertText', false, this.pattern.replace('{{text}}', selectedText))) {
- input.value = initText + this.pattern.replace('{{text}}', selectedText) + finalText;
- }
- input.selectionStart = selectionStart + this.pattern.indexOf('{{text}}');
- input.selectionEnd = input.selectionStart + selectedText.length;
- $(input).change();
-}
+ const isBlocked = (room && room.t === 'd' && subscription && subscription.blocked);
+ const isBlocker = (room && room.t === 'd' && subscription && subscription.blocker);
+ const isBlockedOrBlocker = isBlocked || isBlocker;
+ const mustJoinWithCode = !subscription && room.joinCodeRequired;
-Template.messageBox.onCreated(function() {
- EmojiPicker.init();
- this.replyMessageData = new ReactiveVar();
- this.isMessageFieldEmpty = new ReactiveVar(true);
- this.isMicrophoneDenied = new ReactiveVar(true);
- this.sendIconDisabled = new ReactiveVar(false);
- messageBox.emit('created', this);
-});
+ return this.state.set({
+ room: false,
+ isBlockedOrBlocker,
+ mustJoinWithCode,
+ });
+ });
-Template.messageBox.onRendered(function() {
this.autorun(() => {
- const subscribed = roomTypes.verifyCanSendMessage(this.data._id);
+ const { rid, onInputChanged, onResize } = Template.currentData();
Tracker.afterFlush(() => {
- const input = subscribed && this.find('.js-input-message');
+ const input = this.find('.js-input-message');
+
+ if (this.input === input) {
+ return;
+ }
+
+ this.input = input;
+ onInputChanged && onInputChanged(input);
+
+ if (input && rid) {
+ this.popupConfig.set({
+ rid,
+ getInput: () => input,
+ });
+ } else {
+ this.popupConfig.set(null);
+ }
if (!input) {
return;
}
const $input = $(input);
- $input.on('dataChange', () => { // TODO: remove jQuery event layer dependency
- const reply = $input.data('reply');
- this.replyMessageData.set(reply);
- });
- $input.autogrow({
- animate: true,
- onInitialize: true,
- })
- .on('autogrow', () => {
- this.data && this.data.onResize && this.data.onResize();
- });
+ $input.on('dataChange', () => {
+ const messages = $input.data('reply') || [];
+ this.replyMessageData.set(messages);
+ });
- chatMessages[RoomManager.openedRoom] = chatMessages[RoomManager.openedRoom] || new ChatMessages;
- chatMessages[RoomManager.openedRoom].input = input;
+ $input.autogrow().on('autogrow', () => {
+ onResize && onResize();
+ });
});
});
});
Template.messageBox.helpers({
- isEmbedded() {
- return Layout.isEmbedded();
- },
- subscribed() {
- return roomTypes.verifyCanSendMessage(this._id);
- },
- canSend() {
- if (roomTypes.readOnly(this._id, Meteor.user())) {
+ isAnonymousOrMustJoinWithCode() {
+ const instance = Template.instance();
+ const { rid } = Template.currentData();
+ if (!rid) {
return false;
}
- if (roomTypes.archived(this._id)) {
- return false;
+
+ const isAnonymous = !Meteor.userId();
+ return isAnonymous || instance.state.get('mustJoinWithCode');
+ },
+ isWritable() {
+ const { rid, subscription } = Template.currentData();
+ if (!rid) {
+ return true;
}
- const roomData = Session.get(`roomData${ this._id }`);
- if (roomData && roomData.t === 'd') {
- const subscription = ChatSubscription.findOne({
- rid: this._id,
- }, {
- fields: {
- archived: 1,
- blocked: 1,
- blocker: 1,
- },
- });
- if (subscription && (subscription.archived || subscription.blocked || subscription.blocker)) {
- return false;
- }
+
+ const isBlockedOrBlocker = Template.instance().state.get('isBlockedOrBlocker');
+
+ if (isBlockedOrBlocker) {
+ return false;
}
- return true;
+
+ const isReadOnly = roomTypes.readOnly(rid, Users.findOne({ _id: Meteor.userId() }, { fields: { username: 1 } }));
+ const isArchived = roomTypes.archived(rid) || (subscription && subscription.t === 'd' && subscription.archived);
+
+ return !isReadOnly && !isArchived;
},
popupConfig() {
- const template = Template.instance();
- return {
- getInput() {
- return template.find('.js-input-message');
- },
- };
+ return Template.instance().popupConfig.get();
},
input() {
- return Template.instance().find('.js-input-message');
+ return Template.instance().input;
},
replyMessageData() {
return Template.instance().replyMessageData.get();
@@ -232,53 +171,106 @@ Template.messageBox.helpers({
isSendIconDisabled() {
return !Template.instance().sendIconDisabled.get();
},
+ canSend() {
+ const { rid } = Template.currentData();
+ if (!rid) {
+ return true;
+ }
+
+ return roomTypes.verifyCanSendMessage(rid);
+ },
actions() {
const actionGroups = messageBox.actions.get();
return Object.values(actionGroups)
.reduce((actions, actionGroup) => [...actions, ...actionGroup], []);
},
- isAnonymousOrJoinCode() {
- const room = Session.get(`roomData${ this._id }`);
- return !Meteor.userId() || (!ChatSubscription.findOne({
- rid: this._id,
- }) && room && room.joinCodeRequired);
- },
- showFormattingTips() {
- return settings.get('Message_ShowFormattingTips');
- },
formattingButtons() {
- return formattingButtons.filter((button) => !button.condition || button.condition());
+ return formattingButtons.filter(({ condition }) => !condition || condition());
},
isBlockedOrBlocker() {
- const roomData = Session.get(`roomData${ this._id }`);
- if (roomData && roomData.t === 'd') {
- const subscription = ChatSubscription.findOne({
- rid: this._id,
- }, {
- fields: {
- blocked: 1,
- blocker: 1,
- },
- });
- if (subscription && (subscription.blocked || subscription.blocker)) {
- return true;
- }
- }
+ return Template.instance().state.get('isBlockedOrBlocker');
},
});
+const handleFormattingShortcut = (event, instance) => {
+ const isMacOS = navigator.platform.indexOf('Mac') !== -1;
+ const isCmdOrCtrlPressed = (isMacOS && event.metaKey) || (!isMacOS && event.ctrlKey);
+
+ if (!isCmdOrCtrlPressed) {
+ return false;
+ }
+
+ const key = event.key.toLowerCase();
+
+ const { pattern } = formattingButtons
+ .filter(({ condition }) => !condition || condition())
+ .find(({ command }) => command === key) || {};
+
+ if (!pattern) {
+ return false;
+ }
+
+ const { input } = instance;
+ applyFormatting(pattern, input);
+ return true;
+};
+
+const insertNewLine = (input) => {
+ if (document.selection) {
+ input.focus();
+ const sel = document.selection.createRange();
+ sel.text = '\n';
+ } else if (input.selectionStart || input.selectionStart === 0) {
+ const newPosition = input.selectionStart + 1;
+ const before = input.value.substring(0, input.selectionStart);
+ const after = input.value.substring(input.selectionEnd, input.value.length);
+ input.value = `${ before }\n${ after }`;
+ input.selectionStart = input.selectionEnd = newPosition;
+ } else {
+ input.value += '\n';
+ }
+
+ input.blur();
+ input.focus();
+ input.updateAutogrow();
+};
+
+const handleSubmit = (event, instance) => {
+ const { input } = instance;
+ const { which: keyCode } = event;
+
+ const isSubmitKey = keyCode === keyCodes.CARRIAGE_RETURN || keyCode === keyCodes.NEW_LINE;
+
+ if (!isSubmitKey) {
+ return false;
+ }
+
+ const sendOnEnter = getUserPreference(Meteor.userId(), 'sendOnEnter');
+ const sendOnEnterActive = sendOnEnter == null || sendOnEnter === 'normal' ||
+ (sendOnEnter === 'desktop' && Meteor.Device.isDesktop());
+ const withModifier = event.shiftKey || event.ctrlKey || event.altKey || event.metaKey;
+ const isSending = (sendOnEnterActive && !withModifier) || (!sendOnEnterActive && withModifier);
+
+ if (isSending) {
+ instance.send(event);
+ return true;
+ }
+
+ insertNewLine(input);
+ return true;
+};
+
Template.messageBox.events({
- 'click .js-join'(event) {
+ async 'click .js-join'(event) {
event.stopPropagation();
event.preventDefault();
const joinCodeInput = Template.instance().find('[name=joinCode]');
const joinCode = joinCodeInput && joinCodeInput.value;
- call('joinRoom', this._id, joinCode);
+ await call('joinRoom', this.rid, joinCode);
},
-
- 'click .emoji-picker-icon'(event) {
+ 'click .js-emoji-picker'(event, instance) {
event.stopPropagation();
event.preventDefault();
@@ -293,7 +285,8 @@ Template.messageBox.events({
EmojiPicker.open(event.currentTarget, (emoji) => {
const emojiValue = `:${ emoji }: `;
- const { input } = chatMessages[RoomManager.openedRoom];
+
+ const { input } = instance;
const caretPos = input.selectionStart;
const textAreaTxt = input.value;
@@ -308,29 +301,28 @@ Template.messageBox.events({
input.selectionEnd = caretPos + emojiValue.length;
});
},
- 'focus .js-input-message'(event, instance) {
- KonchatNotification.removeRoomNotification(this._id);
- if (chatMessages[this._id]) {
- chatMessages[this._id].input = instance.find('.js-input-message');
- }
+ 'focus .js-input-message'() {
+ KonchatNotification.removeRoomNotification(this.rid);
},
- 'click .cancel-reply'(event, instance) {
+ 'keydown .js-input-message'(event, instance) {
+ const isEventHandled = handleFormattingShortcut(event, instance) || handleSubmit(event, instance);
- const input = instance.find('.js-input-message');
- const messages = $(input).data('reply') || [];
- const filtered = messages.filter((msg) => msg._id !== this._id);
+ if (isEventHandled) {
+ event.preventDefault();
+ event.stopPropagation();
+ return;
+ }
- $(input)
- .data('reply', filtered)
- .trigger('dataChange');
+ const { rid, tmid, onKeyDown } = this;
+ onKeyDown && onKeyDown.call(this, event, { rid, tmid });
},
- 'keyup .js-input-message'(event, instance) {
- chatMessages[this._id].keyup(this._id, event, instance);
- instance.isMessageFieldEmpty.set(chatMessages[this._id].isEmpty());
+ 'keyup .js-input-message'(event) {
+ const { rid, tmid, onKeyUp } = this;
+ onKeyUp && onKeyUp.call(this, event, { rid, tmid });
},
'paste .js-input-message'(event, instance) {
- const { $input } = chatMessages[RoomManager.openedRoom];
- const [input] = $input;
+ const { rid, tmid } = this;
+ const { input } = instance;
setTimeout(() => {
typeof input.updateAutogrow === 'function' && input.updateAutogrow();
}, 50);
@@ -349,37 +341,46 @@ Template.messageBox.events({
if (files.length) {
event.preventDefault();
- fileUpload(files, input);
+ fileUpload(files, input, { rid, tmid });
return;
}
-
- instance.isMessageFieldEmpty.set(false);
- },
- 'keydown .js-input-message'(event, instance) {
- const isMacOS = navigator.platform.indexOf('Mac') !== -1;
- if (isMacOS && (event.metaKey || event.ctrlKey)) {
- const action = formattingButtons.find(
- (action) => action.command === event.key.toLowerCase() && (!action.condition || action.condition()));
- action && applyFormatting.apply(action, [event, instance]);
- }
- chatMessages[this._id].keydown(this._id, event, Template.instance());
},
'input .js-input-message'(event, instance) {
- instance.sendIconDisabled.set(event.target.value !== '');
- chatMessages[this._id].valueChanged(this._id, event, Template.instance());
+ const { input } = instance;
+ if (!input) {
+ return;
+ }
+
+ instance.sendIconDisabled.set(!!input.value);
+
+ if (input.value.length > 0) {
+ input.dir = isRTL(input.value) ? 'rtl' : 'ltr';
+ }
+
+ const { rid, tmid, onValueChanged } = this;
+ onValueChanged && onValueChanged.call(this, event, { rid, tmid });
},
- 'propertychange .js-input-message'(event) {
- if (event.originalEvent.propertyName === 'value') {
- chatMessages[this._id].valueChanged(this._id, event, Template.instance());
+ 'propertychange .js-input-message'(event, instance) {
+ if (event.originalEvent.propertyName !== 'value') {
+ return;
}
+
+ const { input } = instance;
+ if (!input) {
+ return;
+ }
+
+ instance.sendIconDisabled.set(!!input.value);
+
+ if (input.value.length > 0) {
+ input.dir = isRTL(input.value) ? 'rtl' : 'ltr';
+ }
+
+ const { rid, tmid, onValueChanged } = this;
+ onValueChanged && onValueChanged.call(this, event, { rid, tmid });
},
async 'click .js-send'(event, instance) {
- const { input } = chatMessages[RoomManager.openedRoom];
- chatMessages[this._id].send(this._id, input, () => {
- input.updateAutogrow();
- instance.isMessageFieldEmpty.set(chatMessages[this._id].isEmpty());
- input.focus();
- });
+ instance.send(event);
},
'click .js-action-menu'(event, instance) {
const groups = messageBox.actions.get();
@@ -408,7 +409,8 @@ Template.messageBox.events({
direction: 'top-inverted',
currentTarget: event.currentTarget.firstElementChild.firstElementChild,
data: {
- rid: this._id,
+ rid: this.rid,
+ tmid: this.tmid,
messageBox: instance.firstNode,
},
activeElement: event.currentTarget,
@@ -423,13 +425,26 @@ Template.messageBox.events({
.filter(({ action }) => !!action)
.forEach(({ action }) => {
action.call(null, {
- rid: this._id,
+ rid: this.rid,
+ tmid: this.tmid,
messageBox: instance.firstNode,
event,
});
});
},
- 'click .js-format'(e, t) {
- applyFormatting.apply(this, [e, t]);
+ 'click .js-format'(event, instance) {
+ event.preventDefault();
+ event.stopPropagation();
+
+ const { id } = event.currentTarget.dataset;
+ const { pattern } = formattingButtons
+ .filter(({ condition }) => !condition || condition())
+ .find(({ label }) => label === id) || {};
+
+ if (!pattern) {
+ return;
+ }
+
+ applyFormatting(pattern, instance.input);
},
});
diff --git a/app/ui-message/client/messageBoxAudioMessage.js b/app/ui-message/client/messageBoxAudioMessage.js
index 9073726df0ef..88a940fac416 100644
--- a/app/ui-message/client/messageBoxAudioMessage.js
+++ b/app/ui-message/client/messageBoxAudioMessage.js
@@ -4,7 +4,7 @@ import { Tracker } from 'meteor/tracker';
import { Template } from 'meteor/templating';
import { fileUploadHandler } from '../../file-upload';
import { settings } from '../../settings';
-import { AudioRecorder, chatMessages } from '../../ui';
+import { AudioRecorder } from '../../ui';
import { call } from '../../ui-utils';
import { t } from '../../utils';
import './messageBoxAudioMessage.html';
@@ -39,7 +39,7 @@ const unregisterUploadProgress = (upload) => setTimeout(() => {
Session.set('uploading', uploads.filter(({ id }) => id !== upload.id));
}, 2000);
-const uploadRecord = async ({ rid, blob }) => {
+const uploadRecord = async ({ rid, tmid, blob }) => {
const upload = fileUploadHandler('Uploads', {
name: `${ t('Audio record') }.mp3`,
size: blob.size,
@@ -59,7 +59,7 @@ const uploadRecord = async ({ rid, blob }) => {
upload.start((error, ...args) => (error ? reject(error) : resolve(args)));
});
- await call('sendFileMessage', rid, storage, file);
+ await call('sendFileMessage', rid, storage, file, { tmid });
unregisterUploadProgress(upload);
} catch (error) {
@@ -135,7 +135,6 @@ Template.messageBoxAudioMessage.events({
return;
}
- chatMessages[this.rid].recording = true;
instance.state.set('recording');
try {
@@ -153,7 +152,6 @@ Template.messageBoxAudioMessage.events({
} catch (error) {
instance.state.set(null);
instance.isMicrophoneDenied.set(true);
- chatMessages[this.rid].recording = false;
}
},
@@ -171,7 +169,6 @@ Template.messageBoxAudioMessage.events({
await stopRecording();
instance.state.set(null);
- chatMessages[this.rid].recording = false;
},
async 'click .js-audio-message-done'(event, instance) {
@@ -190,8 +187,8 @@ Template.messageBoxAudioMessage.events({
const blob = await stopRecording();
instance.state.set(null);
- chatMessages[this.rid].recording = false;
- await uploadRecord({ rid: this.rid, blob });
+ const { rid, tmid } = this;
+ await uploadRecord({ rid, tmid, blob });
},
});
diff --git a/app/ui-message/client/messageBoxFormatting.js b/app/ui-message/client/messageBoxFormatting.js
new file mode 100644
index 000000000000..2a6d41a4fd6e
--- /dev/null
+++ b/app/ui-message/client/messageBoxFormatting.js
@@ -0,0 +1,107 @@
+import { katex } from '../../katex/client';
+import { Markdown } from '../../markdown/client';
+import { settings } from '../../settings';
+
+
+export const formattingButtons = [
+ {
+ label: 'bold',
+ icon: 'bold',
+ pattern: '*{{text}}*',
+ command: 'b',
+ condition: () => Markdown && settings.get('Markdown_Parser') === 'original',
+ },
+ {
+ label: 'bold',
+ icon: 'bold',
+ pattern: '**{{text}}**',
+ command: 'b',
+ condition: () => Markdown && settings.get('Markdown_Parser') === 'marked',
+ },
+ {
+ label: 'italic',
+ icon: 'italic',
+ pattern: '_{{text}}_',
+ command: 'i',
+ condition: () => Markdown && settings.get('Markdown_Parser') !== 'disabled',
+ },
+ {
+ label: 'strike',
+ icon: 'strike',
+ pattern: '~{{text}}~',
+ condition: () => Markdown && settings.get('Markdown_Parser') === 'original',
+ },
+ {
+ label: 'strike',
+ icon: 'strike',
+ pattern: '~~{{text}}~~',
+ condition: () => Markdown && settings.get('Markdown_Parser') === 'marked',
+ },
+ {
+ label: 'inline_code',
+ icon: 'code',
+ pattern: '`{{text}}`',
+ condition: () => Markdown && settings.get('Markdown_Parser') !== 'disabled',
+ },
+ {
+ label: 'multi_line',
+ icon: 'multiline',
+ pattern: '```\n{{text}}\n``` ',
+ condition: () => Markdown && settings.get('Markdown_Parser') !== 'disabled',
+ },
+ {
+ label: 'KaTeX',
+ text: () => {
+ if (!katex.isEnabled()) {
+ return;
+ }
+ if (katex.isDollarSyntaxEnabled()) {
+ return '$$KaTeX$$';
+ }
+ if (katex.isParenthesisSyntaxEnabled()) {
+ return '\\[KaTeX\\]';
+ }
+ },
+ link: 'https://khan.github.io/KaTeX/function-support.html',
+ condition: () => katex.isEnabled(),
+ },
+];
+
+export function applyFormatting(pattern, input) {
+ const { selectionEnd = input.value.length, selectionStart = 0 } = input;
+ const initText = input.value.slice(0, selectionStart);
+ const selectedText = input.value.slice(selectionStart, selectionEnd);
+ const finalText = input.value.slice(selectionEnd, input.value.length);
+
+ input.focus();
+
+ const startPattern = pattern.substr(0, pattern.indexOf('{{text}}'));
+ const startPatternFound = [...startPattern].reverse().every((char, index) => input.value.substr(selectionStart - index - 1, 1) === char);
+
+ if (startPatternFound) {
+ const endPattern = pattern.substr(pattern.indexOf('{{text}}') + '{{text}}'.length);
+ const endPatternFound = [...endPattern].every((char, index) => input.value.substr(selectionEnd + index, 1) === char);
+
+ if (endPatternFound) {
+ input.selectionStart = selectionStart - startPattern.length;
+ input.selectionEnd = selectionEnd + endPattern.length;
+
+ if (!document.execCommand || !document.execCommand('insertText', false, selectedText)) {
+ input.value = initText.substr(0, initText.length - startPattern.length) + selectedText + finalText.substr(endPattern.length);
+ }
+
+ input.selectionStart = selectionStart - startPattern.length;
+ input.selectionEnd = input.selectionStart + selectedText.length;
+ $(input).change();
+ return;
+ }
+ }
+
+ if (!document.execCommand || !document.execCommand('insertText', false, pattern.replace('{{text}}', selectedText))) {
+ input.value = initText + pattern.replace('{{text}}', selectedText) + finalText;
+ }
+
+ input.selectionStart = selectionStart + pattern.indexOf('{{text}}');
+ input.selectionEnd = input.selectionStart + selectedText.length;
+ $(input).change();
+}
diff --git a/app/ui-message/client/messageBoxNotSubscribed.html b/app/ui-message/client/messageBoxNotSubscribed.html
index feccd6e089b2..c1813279b4cb 100644
--- a/app/ui-message/client/messageBoxNotSubscribed.html
+++ b/app/ui-message/client/messageBoxNotSubscribed.html
@@ -1,4 +1,4 @@
-
+
{{#if customTemplate}}
{{> Template.dynamic customTemplate }}
{{else}}
@@ -6,7 +6,9 @@
{{{_ "you_are_in_preview_mode_of" room_name=roomName}}}
+ {{#if isJoinCodeRequired}}
+ {{/if}}
{{_ "join"}}
diff --git a/app/ui-message/client/messageBoxNotSubscribed.js b/app/ui-message/client/messageBoxNotSubscribed.js
index f3fe690e670d..0c9511f9344d 100644
--- a/app/ui-message/client/messageBoxNotSubscribed.js
+++ b/app/ui-message/client/messageBoxNotSubscribed.js
@@ -11,17 +11,17 @@ import './messageBoxNotSubscribed.html';
Template.messageBoxNotSubscribed.helpers({
customTemplate() {
- return roomTypes.getNotSubscribedTpl(this._id);
+ return roomTypes.getNotSubscribedTpl(this.rid);
},
canJoinRoom() {
- return Meteor.userId() && roomTypes.verifyShowJoinLink(this._id);
+ return Meteor.userId() && roomTypes.verifyShowJoinLink(this.rid);
},
roomName() {
- const room = Session.get(`roomData${ this._id }`);
+ const room = Session.get(`roomData${ this.rid }`);
return roomTypes.getRoomName(room.t, room);
},
isJoinCodeRequired() {
- const room = Session.get(`roomData${ this._id }`);
+ const room = Session.get(`roomData${ this.rid }`);
return room && room.joinCodeRequired;
},
isAnonymousReadAllowed() {
@@ -43,16 +43,14 @@ Template.messageBoxNotSubscribed.events({
const joinCodeInput = Template.instance().find('[name=joinCode]');
const joinCode = joinCodeInput && joinCodeInput.value;
- await call('joinRoom', this._id, joinCode);
+ await call('joinRoom', this.rid, joinCode);
- if (hasAllPermission('preview-c-room') === false && RoomHistoryManager.getRoom(this._id).loaded === 0) {
- RoomManager.getOpenedRoomByRid(this._id).streamActive = false;
- RoomManager.getOpenedRoomByRid(this._id).ready = false;
- RoomHistoryManager.getRoom(this._id).loaded = null;
+ if (hasAllPermission('preview-c-room') === false && RoomHistoryManager.getRoom(this.rid).loaded === 0) {
+ RoomManager.getOpenedRoomByRid(this.rid).streamActive = false;
+ RoomManager.getOpenedRoomByRid(this.rid).ready = false;
+ RoomHistoryManager.getRoom(this.rid).loaded = null;
RoomManager.computation.invalidate();
}
-
-
},
'click .js-register'(event) {
event.stopPropagation();
@@ -60,7 +58,6 @@ Template.messageBoxNotSubscribed.events({
Session.set('forceLogin', true);
},
-
async 'click .js-register-anonymous'(event) {
event.stopPropagation();
event.preventDefault();
diff --git a/app/ui-message/client/messageBoxReplyPreview.html b/app/ui-message/client/messageBoxReplyPreview.html
index 669e8d5e9e00..1d874ff7208e 100644
--- a/app/ui-message/client/messageBoxReplyPreview.html
+++ b/app/ui-message/client/messageBoxReplyPreview.html
@@ -1,9 +1,9 @@
-
+
diff --git a/app/ui-sidenav/client/sidebarItem.js b/app/ui-sidenav/client/sidebarItem.js
index 0ff8026d01e2..8064b91cf111 100644
--- a/app/ui-sidenav/client/sidebarItem.js
+++ b/app/ui-sidenav/client/sidebarItem.js
@@ -3,12 +3,12 @@ import { ReactiveVar } from 'meteor/reactive-var';
import { Session } from 'meteor/session';
import { Template } from 'meteor/templating';
import { t, getUserPreference, roomTypes } from '../../utils';
-import moment from 'moment';
import { popover, renderMessageBody } from '../../ui-utils';
import { Users, ChatSubscription } from '../../models';
import { settings } from '../../settings';
import { hasAtLeastOnePermission } from '../../authorization';
import { menu } from '../../ui-utils';
+import { timeAgo } from '../../lib/client/lib/formatDate';
Template.sidebarItem.helpers({
streaming() {
@@ -35,18 +35,27 @@ Template.sidebarItem.helpers({
showUnread() {
return this.unread > 0 || (!this.hideUnreadStatus && this.alert);
},
-});
+ badgeClass() {
+ const { t, unread, userMentions, groupMentions } = this;
-function timeAgo(time) {
- const now = new Date();
- const yesterday = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 1);
+ const badges = ['badge'];
+
+ if (unread) {
+ badges.push('badge--unread');
+ }
+
+ if (unread && t === 'd') {
+ badges.push('badge--dm');
+ } else if (userMentions) {
+ badges.push('badge--user-mentions');
+ } else if (groupMentions) {
+ badges.push('badge--group-mentions');
+ }
+
+ return badges.join(' ');
+ },
+});
- return (
- (now.getDate() === time.getDate() && moment(time).format('LT')) ||
- (yesterday.getDate() === time.getDate() && t('yesterday')) ||
- moment(time).format('L')
- );
-}
function setLastMessageTs(instance, ts) {
if (instance.timeAgoInterval) {
clearInterval(instance.timeAgoInterval);
@@ -101,6 +110,7 @@ Template.sidebarItem.events({
return menu.close();
},
'click .sidebar-item__menu'(e) {
+ e.stopPropagation(); // to not close the menu
e.preventDefault();
const canLeave = () => {
diff --git a/app/ui-utils/client/index.js b/app/ui-utils/client/index.js
index 8c38ae822e42..293698e9d66f 100644
--- a/app/ui-utils/client/index.js
+++ b/app/ui-utils/client/index.js
@@ -10,7 +10,7 @@ export { messageBox } from './lib/messageBox';
export { popover } from './lib/popover';
export { readMessage } from './lib/readMessages';
export { RoomManager } from './lib/RoomManager';
-export { upsertMessage, RoomHistoryManager } from './lib/RoomHistoryManager';
+export { upsertMessage, RoomHistoryManager, normalizeThreadMessage } from './lib/RoomHistoryManager';
export { mainReady } from './lib/mainReady';
export { renderMessageBody } from './lib/renderMessageBody';
export { Layout } from './lib/Layout';
@@ -25,3 +25,6 @@ export { MessageTypes } from '../lib/MessageTypes';
export { alerts } from './lib/alerts';
export { Message } from '../lib/Message';
export { openRoom } from './lib/openRoom';
+export * from './lib/rtl';
+export * from './lib/keyCodes';
+export * from './lib/prependReplies';
diff --git a/app/ui-utils/client/lib/MessageAction.js b/app/ui-utils/client/lib/MessageAction.js
index d795ccab92f8..3178f2346923 100644
--- a/app/ui-utils/client/lib/MessageAction.js
+++ b/app/ui-utils/client/lib/MessageAction.js
@@ -1,16 +1,19 @@
+import _ from 'underscore';
+import moment from 'moment';
+import toastr from 'toastr';
+import mem from 'mem';
+
import { Meteor } from 'meteor/meteor';
import { TAPi18n } from 'meteor/tap:i18n';
import { ReactiveVar } from 'meteor/reactive-var';
import { Tracker } from 'meteor/tracker';
import { Session } from 'meteor/session';
-import { t, handleError, roomTypes } from '../../../utils';
-import { Messages, Rooms, Subscriptions } from '../../../models';
-import { hasAtLeastOnePermission } from '../../../authorization';
-import { settings } from '../../../settings';
-import _ from 'underscore';
-import moment from 'moment';
-import toastr from 'toastr';
-import mem from 'mem';
+
+import { t, handleError, roomTypes, canDeleteMessage } from '../../../utils/client';
+import { messageArgs } from '../../../ui-utils/client/lib/messageArgs';
+import { Messages, Rooms, Subscriptions } from '../../../models/client';
+import { hasAtLeastOnePermission } from '../../../authorization/client';
+import { settings } from '../../../settings/client';
const call = (method, ...args) => new Promise((resolve, reject) => {
Meteor.call(method, ...args, function(err, data) {
@@ -67,7 +70,7 @@ export const MessageAction = new class {
}
if (config.condition) {
- config.condition = mem(config.condition);
+ config.condition = mem(config.condition, { maxAge: 1000 });
}
return Tracker.nonreactive(() => {
@@ -100,23 +103,26 @@ export const MessageAction = new class {
return allButtons[id];
}
+ _getButtons = mem(function() {
+ return _.sortBy(_.toArray(this.buttons.get()), 'order');
+ }, { maxAge: 100 })
+
getButtons(message, context, group) {
- let allButtons = _.toArray(this.buttons.get());
+ let allButtons = this._getButtons();
if (group) {
allButtons = allButtons.filter((button) => button.group === group);
}
if (message) {
- allButtons = _.compact(_.map(allButtons, function(button) {
+ return allButtons.filter(function(button) {
if (button.context == null || button.context.includes(context)) {
- if (button.condition == null || button.condition(message, context)) {
- return button;
- }
+ return button.condition == null || button.condition(message, context);
}
- }));
+ return false;
+ });
}
- return _.sortBy(allButtons, 'order');
+ return allButtons;
}
resetButtons() {
@@ -154,7 +160,7 @@ Meteor.startup(async function() {
label: 'Reply',
context: ['message', 'message-mobile'],
action() {
- const message = this._arguments[1];
+ const { msg: message } = messageArgs(this);
const { input } = chatMessages[message.rid];
const $input = $(input);
@@ -183,8 +189,8 @@ Meteor.startup(async function() {
label: 'Edit',
context: ['message', 'message-mobile'],
action() {
- const messageId = this._arguments[1]._id;
- chatMessages[Session.get('openedRoom')].edit(document.getElementById(messageId));
+ const { msg } = messageArgs(this);
+ chatMessages[Session.get('openedRoom')].edit(document.getElementById(msg._id));
},
condition(message) {
if (Subscriptions.findOne({
@@ -224,37 +230,19 @@ Meteor.startup(async function() {
context: ['message', 'message-mobile'],
color: 'alert',
action() {
- const message = this._arguments[1];
+ const { msg: message } = messageArgs(this);
chatMessages[Session.get('openedRoom')].confirmDeleteMsg(message);
},
condition(message) {
if (Subscriptions.findOne({ rid: message.rid }) == null) {
return false;
}
- const forceDelete = hasAtLeastOnePermission('force-delete-message', message.rid);
- const hasPermission = hasAtLeastOnePermission('delete-message', message.rid);
- const isDeleteAllowed = settings.get('Message_AllowDeleting');
- const deleteOwn = message.u && message.u._id === Meteor.userId();
- if (!(hasPermission || (isDeleteAllowed && deleteOwn) || forceDelete)) {
- return;
- }
- const blockDeleteInMinutes = settings.get('Message_AllowDeleting_BlockDeleteInMinutes');
- if (forceDelete) {
- return true;
- }
- if (blockDeleteInMinutes != null && blockDeleteInMinutes !== 0) {
- let msgTs;
- if (message.ts != null) {
- msgTs = moment(message.ts);
- }
- let currentTsDiff;
- if (msgTs != null) {
- currentTsDiff = moment().diff(msgTs, 'minutes');
- }
- return currentTsDiff < blockDeleteInMinutes;
- } else {
- return true;
- }
+
+ return canDeleteMessage({
+ rid: message.rid,
+ ts: message.ts,
+ uid: message.u._id,
+ });
},
order: 3,
group: 'menu',
@@ -267,7 +255,7 @@ Meteor.startup(async function() {
classes: 'clipboard',
context: ['message', 'message-mobile'],
async action(event) {
- const message = this._arguments[1];
+ const { msg: message } = messageArgs(this);
const permalink = await MessageAction.getPermaLink(message._id);
$(event.currentTarget).attr('data-clipboard-text', permalink);
toastr.success(TAPi18n.__('Copied'));
@@ -290,7 +278,7 @@ Meteor.startup(async function() {
classes: 'clipboard',
context: ['message', 'message-mobile'],
action(event) {
- const message = this._arguments[1].msg;
+ const { msg: message } = messageArgs(this);
$(event.currentTarget).attr('data-clipboard-text', message);
toastr.success(TAPi18n.__('Copied'));
},
@@ -311,7 +299,7 @@ Meteor.startup(async function() {
label: 'Quote',
context: ['message', 'message-mobile'],
action() {
- const message = this._arguments[1];
+ const { msg: message } = messageArgs(this);
const { input } = chatMessages[message.rid];
const $input = $(input);
@@ -343,7 +331,7 @@ Meteor.startup(async function() {
label: t('Ignore'),
context: ['message', 'message-mobile'],
action() {
- const [, { rid, u: { _id } }] = this._arguments;
+ const { msg: { rid, u: { _id } } } = messageArgs(this);
Meteor.call('ignoreUser', { rid, userId:_id, ignore: true }, success(() => toastr.success(t('User_has_been_ignored'))));
},
condition(message) {
@@ -361,12 +349,12 @@ Meteor.startup(async function() {
label: t('Unignore'),
context: ['message', 'message-mobile'],
action() {
- const [, { rid, u: { _id } }] = this._arguments;
+ const { msg: { rid, u: { _id } } } = messageArgs(this);
Meteor.call('ignoreUser', { rid, userId:_id, ignore: false }, success(() => toastr.success(t('User_has_been_unignored'))));
},
condition(message) {
- const subscription = Subscriptions.findOne({ rid: message.rid });
+ const subscription = Subscriptions.findOne({ rid: message.rid }, { fields: { ignored: 1 } });
return Meteor.userId() !== message.u._id && subscription && subscription.ignored && subscription.ignored.indexOf(message.u._id) > -1;
},
order: 20,
diff --git a/app/ui-utils/client/lib/RocketChatTabBar.js b/app/ui-utils/client/lib/RocketChatTabBar.js
index d22b7c5e32a7..12e6c126c957 100644
--- a/app/ui-utils/client/lib/RocketChatTabBar.js
+++ b/app/ui-utils/client/lib/RocketChatTabBar.js
@@ -31,6 +31,10 @@ export class RocketChatTabBar {
this.group.set(group);
}
+ extendsData(data) {
+ this.data.set({ ...this.data.get(), ...data });
+ }
+
setData(d) {
this.data.set(d);
}
diff --git a/app/ui-utils/client/lib/RoomHistoryManager.js b/app/ui-utils/client/lib/RoomHistoryManager.js
index 6f59aec7258e..cd0ec20625ad 100644
--- a/app/ui-utils/client/lib/RoomHistoryManager.js
+++ b/app/ui-utils/client/lib/RoomHistoryManager.js
@@ -1,10 +1,30 @@
+import mem from 'mem';
+import s from 'underscore.string';
import { Meteor } from 'meteor/meteor';
import { ReactiveVar } from 'meteor/reactive-var';
import { Blaze } from 'meteor/blaze';
-import { UserRoles, RoomRoles, ChatMessage, ChatSubscription, ChatRoom } from '../../../models';
-import _ from 'underscore';
+import { ChatMessage, ChatSubscription, ChatRoom } from '../../../models';
import { RoomManager } from './RoomManager';
import { readMessage } from './readMessages';
+import { renderMessageBody } from './renderMessageBody';
+
+export const normalizeThreadMessage = mem((message) => {
+ if (message.msg) {
+ return renderMessageBody(message).replace(/ /g, ' ');
+ }
+
+ if (message.attachments) {
+ const attachment = message.attachments.find((attachment) => attachment.title || attachment.description);
+
+ if (attachment.description) {
+ return s.escapeHTML(attachment.description);
+ }
+
+ if (attachment.title) {
+ return s.escapeHTML(attachment.title);
+ }
+ }
+}, { maxAge: 1000 });
export const upsertMessage = ({ msg: { _id, ...msg }, subscription }) => {
const userId = msg.u && msg.u._id;
@@ -12,15 +32,26 @@ export const upsertMessage = ({ msg: { _id, ...msg }, subscription }) => {
if (subscription && subscription.ignored && subscription.ignored.indexOf(userId) > -1) {
msg.ignored = true;
}
- const roles = [
- (userId && UserRoles.findOne(userId, { fields: { roles: 1 } })) || {},
- (userId && RoomRoles.findOne({ rid: msg.rid, 'u._id': userId })) || {},
- ].map((e) => e.roles);
- msg.roles = _.union.apply(_.union, roles);
+
+ // const roles = [
+ // (userId && UserRoles.findOne(userId, { fields: { roles: 1 } })) || {},
+ // (userId && RoomRoles.findOne({ rid: msg.rid, 'u._id': userId })) || {},
+ // ].map((e) => e.roles);
+ // msg.roles = _.union.apply(_.union, roles);
+
if (msg.t === 'e2e' && !msg.file) {
msg.e2e = 'pending';
}
+ if (msg.tcount) {
+ ChatMessage.update({ tmid: _id }, {
+ $set: {
+ threadMsg: normalizeThreadMessage(msg),
+ repliesCount: msg.tcount,
+ },
+ }, { multi: true });
+ }
+
return ChatMessage.upsert({ _id }, msg);
};
@@ -35,13 +66,15 @@ function upsertMessageBulk({ msgs, subscription }) {
});
}
+const defaultLimit = parseInt(localStorage && localStorage.getItem('rc-defaultLimit')) || 50 ;
+
export const RoomHistoryManager = new class {
constructor() {
- this.defaultLimit = 50;
this.histories = {};
}
+
getRoom(rid) {
- if ((this.histories[rid] == null)) {
+ if (!this.histories[rid]) {
this.histories[rid] = {
hasMore: new ReactiveVar(true),
hasMoreNext: new ReactiveVar(false),
@@ -55,9 +88,8 @@ export const RoomHistoryManager = new class {
return this.histories[rid];
}
- getMore(rid, limit) {
+ getMore(rid, limit = defaultLimit) {
let ts;
- if (limit == null) { limit = this.defaultLimit; }
const room = this.getRoom(rid);
if (room.hasMore.curValue !== true) {
return;
@@ -69,7 +101,7 @@ export const RoomHistoryManager = new class {
const lastMessage = ChatMessage.findOne({ rid }, { sort: { ts: 1 } });
// lastMessage ?= ChatMessage.findOne({rid: rid}, {sort: {ts: 1}})
- if (lastMessage != null) {
+ if (lastMessage) {
({ ts } = lastMessage);
} else {
ts = undefined;
@@ -79,12 +111,12 @@ export const RoomHistoryManager = new class {
let typeName = undefined;
const subscription = ChatSubscription.findOne({ rid });
- if (subscription != null) {
+ if (subscription) {
({ ls } = subscription);
typeName = subscription.t + subscription.name;
} else {
const curRoomDoc = ChatRoom.findOne({ _id: rid });
- typeName = (curRoomDoc != null ? curRoomDoc.t : undefined) + (curRoomDoc != null ? curRoomDoc.name : undefined);
+ typeName = (curRoomDoc ? curRoomDoc.t : undefined) + (curRoomDoc ? curRoomDoc.name : undefined);
}
Meteor.call('loadHistory', rid, ts, limit, ls, function(err, result) {
@@ -98,7 +130,7 @@ export const RoomHistoryManager = new class {
room.firstUnread.set(result.firstUnread);
const wrapper = $('.messages-box .wrapper').get(0);
- if (wrapper != null) {
+ if (wrapper) {
previousHeight = wrapper.scrollHeight;
}
@@ -118,7 +150,9 @@ export const RoomHistoryManager = new class {
});
room.isLoading.set(false);
- if (room.loaded == null) { room.loaded = 0; }
+ if (!room.loaded) {
+ room.loaded = 0;
+ }
room.loaded += messages.length;
if (messages.length < limit) {
return room.hasMore.set(false);
@@ -126,8 +160,7 @@ export const RoomHistoryManager = new class {
});
}
- getMoreNext(rid, limit) {
- if (limit == null) { limit = this.defaultLimit; }
+ getMoreNext(rid, limit = defaultLimit) {
const room = this.getRoom(rid);
if (room.hasMoreNext.curValue !== true) {
return;
@@ -143,28 +176,30 @@ export const RoomHistoryManager = new class {
let typeName = undefined;
const subscription = ChatSubscription.findOne({ rid });
- if (subscription != null) {
+ if (subscription) {
// const { ls } = subscription;
typeName = subscription.t + subscription.name;
} else {
const curRoomDoc = ChatRoom.findOne({ _id: rid });
- typeName = (curRoomDoc != null ? curRoomDoc.t : undefined) + (curRoomDoc != null ? curRoomDoc.name : undefined);
+ typeName = (curRoomDoc ? curRoomDoc.t : undefined) + (curRoomDoc ? curRoomDoc.name : undefined);
}
const { ts } = lastMessage;
if (ts) {
return Meteor.call('loadNextMessages', rid, ts, limit, function(err, result) {
- for (const msg of Array.from((result != null ? result.messages : undefined) || [])) {
- if (msg.t !== 'command') {
- upsertMessage({ msg, subscription });
- }
- }
+
+ upsertMessageBulk({
+ msgs: Array.from(result.messages).filter((msg) => msg.t !== 'command'),
+ subscription,
+ });
Meteor.defer(() => RoomManager.updateMentionsMarksOfRoom(typeName));
room.isLoading.set(false);
- if (room.loaded == null) { room.loaded = 0; }
+ if (!room.loaded) {
+ room.loaded = 0;
+ }
room.loaded += result.messages.length;
if (result.messages.length < limit) {
@@ -174,9 +209,8 @@ export const RoomHistoryManager = new class {
}
}
- getSurroundingMessages(message, limit) {
- if (limit == null) { limit = this.defaultLimit; }
- if (!(message != null ? message.rid : undefined)) {
+ getSurroundingMessages(message, limit = defaultLimit) {
+ if (!message || !message.rid) {
return;
}
@@ -213,7 +247,7 @@ export const RoomHistoryManager = new class {
typeName = subscription.t + subscription.name;
} else {
const curRoomDoc = ChatRoom.findOne({ _id: message.rid });
- typeName = (curRoomDoc != null ? curRoomDoc.t : undefined) + (curRoomDoc != null ? curRoomDoc.name : undefined);
+ typeName = (curRoomDoc ? curRoomDoc.t : undefined) + (curRoomDoc ? curRoomDoc.name : undefined);
}
return Meteor.call('loadSurroundingMessages', message, limit, function(err, result) {
@@ -247,7 +281,9 @@ export const RoomHistoryManager = new class {
return setTimeout(() => msgElement.removeClass('highlight'), 500);
});
- if (room.loaded == null) { room.loaded = 0; }
+ if (!room.loaded) {
+ room.loaded = 0;
+ }
room.loaded += result.messages.length;
room.hasMore.set(result.moreBefore);
return room.hasMoreNext.set(result.moreAfter);
@@ -282,7 +318,7 @@ export const RoomHistoryManager = new class {
clear(rid) {
ChatMessage.remove({ rid });
- if (this.histories[rid] != null) {
+ if (this.histories[rid]) {
this.histories[rid].hasMore.set(true);
this.histories[rid].isLoading.set(false);
return this.histories[rid].loaded = undefined;
diff --git a/app/ui-utils/client/lib/RoomManager.js b/app/ui-utils/client/lib/RoomManager.js
index bca2b26d7ee0..5e1a8015180f 100644
--- a/app/ui-utils/client/lib/RoomManager.js
+++ b/app/ui-utils/client/lib/RoomManager.js
@@ -4,7 +4,7 @@ import { Tracker } from 'meteor/tracker';
import { Blaze } from 'meteor/blaze';
import { FlowRouter } from 'meteor/kadira:flow-router';
import { Template } from 'meteor/templating';
-import { roomTypes as _roomTypes } from '../../../utils';
+import { roomTypes } from '../../../utils';
import { fireGlobalEvent } from './fireGlobalEvent';
import { promises } from '../../../promises/client';
import { callbacks } from '../../../callbacks';
@@ -17,7 +17,12 @@ import { mainReady } from './mainReady';
const maxRoomsOpen = parseInt(localStorage && localStorage.getItem('rc-maxRoomsOpen')) || 5 ;
-const onDeleteMessageStream = (msg) => ChatMessage.remove({ _id: msg._id });
+const onDeleteMessageStream = (msg) => {
+ ChatMessage.remove({ _id: msg._id });
+
+ // remove thread refenrece from deleted message
+ ChatMessage.update({ tmid: msg._id }, { $unset: { tmid: 1 } }, { multi: true });
+};
const onDeleteMessageBulkStream = ({ rid, ts, excludePinned, ignoreDiscussion, users }) => {
const query = { rid, ts };
if (excludePinned) {
@@ -52,7 +57,7 @@ export const RoomManager = new function() {
const type = typeName.substr(0, 1);
const name = typeName.substr(1);
- const room = Tracker.nonreactive(() => _roomTypes.findRoom(type, name, user));
+ const room = Tracker.nonreactive(() => roomTypes.findRoom(type, name, user));
if (room != null) {
openedRooms[typeName].rid = room._id;
@@ -77,7 +82,7 @@ export const RoomManager = new function() {
};
}
msg.name = room.name;
- Meteor.defer(() => RoomManager.updateMentionsMarksOfRoom(typeName));
+ RoomManager.updateMentionsMarksOfRoom(typeName);
callbacks.run('streamMessage', msg);
@@ -221,25 +226,27 @@ export const RoomManager = new function() {
updateMentionsMarksOfRoom(typeName) {
const dom = this.getDomOfRoom(typeName);
- if ((dom == null)) {
+ if (!dom) {
return;
}
- const ticksBar = $(dom).find('.ticks-bar');
- $(dom).find('.ticks-bar > .tick').remove();
-
- const scrollTop = $(dom).find('.messages-box > .wrapper').scrollTop() - 50;
- const totalHeight = $(dom).find('.messages-box > .wrapper > ul').height() + 40;
-
- return $('.messages-box .mention-link-me').each(function(index, item) {
- const topOffset = $(item).offset().top + scrollTop;
- const percent = (100 / totalHeight) * topOffset;
- if ($(item).hasClass('mention-link-all')) {
- return ticksBar.append(`
`);
- } else {
- return ticksBar.append(`
`);
- }
- });
+ const [ticksBar] = dom.getElementsByClassName('ticks-bar');
+ const [messagesBox] = dom.getElementsByClassName('messages-box');
+ const scrollTop = $('> .wrapper', messagesBox).scrollTop() - 50;
+ const totalHeight = $(' > .wrapper > ul', messagesBox).height() + 40;
+
+ ticksBar.innerHTML = Array.from(messagesBox.querySelectorAll('.mention-link--me, .mention-link--group'))
+ .map((mentionLink) => {
+ const topOffset = $(mentionLink).offset().top + scrollTop;
+ const percent = (100 / totalHeight) * topOffset;
+ const className = [
+ 'tick',
+ mentionLink.classList.contains('mention-link--me') && 'tick--me',
+ mentionLink.classList.contains('mention-link--group') && 'tick--group',
+ ].filter(Boolean).join(' ');
+ return `
`;
+ })
+ .join('');
}
};
Cls.initClass();
@@ -285,9 +292,10 @@ Meteor.startup(() => {
if ((currentUsername === undefined) && ((user != null ? user.username : undefined) != null)) {
currentUsername = user.username;
RoomManager.closeAllRooms();
- const { roomTypes } = _roomTypes;
+ const { roomTypes: types } = roomTypes;
+
// Reload only if the current route is a channel route
- const roomType = Object.keys(roomTypes).find((key) => roomTypes[key].route && roomTypes[key].route.name === FlowRouter.current().route.name);
+ const roomType = Object.keys(types).find((key) => types[key].route && types[key].route.name === FlowRouter.current().route.name);
if (roomType) {
FlowRouter.reload();
}
diff --git a/app/ui-utils/client/lib/keyCodes.js b/app/ui-utils/client/lib/keyCodes.js
new file mode 100644
index 000000000000..265559014c19
--- /dev/null
+++ b/app/ui-utils/client/lib/keyCodes.js
@@ -0,0 +1,36 @@
+export const keyCodes = {
+ TAB: 9,
+ CARRIAGE_RETURN: 10,
+ NEW_LINE: 13,
+ SHIFT: 16,
+ CONTROL: 17,
+ ALT: 18,
+ PAUSE_BREAK: 19,
+ CAPS_LOCK: 20,
+ ESCAPE: 27,
+ PAGE_DOWN: 33,
+ PAGE_UP: 34,
+ END: 35,
+ HOME: 36,
+ ARROW_LEFT: 37,
+ ARROW_UP: 38,
+ ARROW_RIGHT: 39,
+ ARROW_DOWN: 40,
+ INSERT: 45,
+ SUPER: 91,
+ MENU: 93,
+ F1: 112,
+ F2: 113,
+ F3: 114,
+ F4: 115,
+ F5: 116,
+ F6: 117,
+ F7: 118,
+ F8: 119,
+ F9: 120,
+ F10: 121,
+ F11: 122,
+ F12: 123,
+ NUM_LOCK: 144,
+ SCROLL_LOCK: 145,
+};
diff --git a/app/ui-utils/client/lib/messageArgs.js b/app/ui-utils/client/lib/messageArgs.js
new file mode 100644
index 000000000000..9c7f05a28bdd
--- /dev/null
+++ b/app/ui-utils/client/lib/messageArgs.js
@@ -0,0 +1 @@
+export const messageArgs = (context) => (context && context._arguments && context._arguments[1] && context._arguments[1].hash) || context;
diff --git a/app/ui-utils/client/lib/messageBox.js b/app/ui-utils/client/lib/messageBox.js
index ba9a3a1170d7..bf7e62083e7a 100644
--- a/app/ui-utils/client/lib/messageBox.js
+++ b/app/ui-utils/client/lib/messageBox.js
@@ -1,21 +1,8 @@
-import EventEmitter from 'wolfy87-eventemitter';
-
-export const messageBox = new EventEmitter;
-
-messageBox.actions = new class {
+class MessageBoxActions {
constructor() {
this.actions = {};
}
- /* Add a action to messagebox
- @param group
- @param label
- @param config
- icon: icon class
- action: action function
- condition: condition to display the action
- */
-
add(group, label, config) {
if (!group && !label && !config) {
return;
@@ -33,12 +20,14 @@ messageBox.actions = new class {
this.actions[group].push({ ...config, label });
}
+
remove(group, expression) {
if (!group || !this.actions[group]) {
return false;
}
return (this.actions[group] = this.actions[group].filter((action) => expression.test(action.id)));
}
+
get(group) {
if (!group) {
return Object.keys(this.actions).reduce((ret, key) => {
@@ -62,4 +51,8 @@ messageBox.actions = new class {
return actions.filter((action) => action.id === id);
}
+}
+
+export const messageBox = {
+ actions: new MessageBoxActions,
};
diff --git a/app/ui-utils/client/lib/messageContext.js b/app/ui-utils/client/lib/messageContext.js
new file mode 100644
index 000000000000..2e1ec808ae1d
--- /dev/null
+++ b/app/ui-utils/client/lib/messageContext.js
@@ -0,0 +1,43 @@
+import { Meteor } from 'meteor/meteor';
+import { Template } from 'meteor/templating';
+
+import { Subscriptions, Rooms, Users } from '../../../models/client';
+import { hasPermission } from '../../../authorization/client';
+import { settings } from '../../../settings/client';
+import { getUserPreference } from '../../../utils/client';
+
+export function messageContext() {
+ const { rid } = Template.instance();
+ const uid = Meteor.userId();
+ return {
+ u: Users.findOne({ _id: uid }, { fields: { name: 1, username: 1 } }),
+ room: Rooms.findOne({ _id: rid }, {
+ reactive: false,
+ fields: {
+ _updatedAt: 0,
+ lastMessage: 0,
+ },
+ }),
+ subscription: Subscriptions.findOne({ rid }, {
+ fields: {
+ name: 1,
+ autoTranslate: 1,
+ },
+ }),
+ settings: {
+ showreply: true,
+ showReplyButton: true,
+ hasPermissionDeleteMessage: hasPermission('delete-message', rid),
+ hideRoles: !settings.get('UI_DisplayRoles') || getUserPreference(uid, 'hideRoles'),
+ UI_Use_Real_Name: settings.get('UI_Use_Real_Name'),
+ Chatops_Username: settings.get('Chatops_Username'),
+ AutoTranslate_Enabled: settings.get('AutoTranslate_Enabled'),
+ Message_AllowEditing: settings.get('Message_AllowEditing'),
+ Message_AllowEditing_BlockEditInMinutes: settings.get('Message_AllowEditing_BlockEditInMinutes'),
+ Message_ShowEditedStatus: settings.get('Message_ShowEditedStatus'),
+ API_Embed: settings.get('API_Embed'),
+ API_EmbedDisabledFor: settings.get('API_EmbedDisabledFor'),
+ Message_GroupingPeriod: settings.get('Message_GroupingPeriod') * 1000,
+ },
+ };
+}
diff --git a/app/ui-utils/client/lib/modal.js b/app/ui-utils/client/lib/modal.js
index cd2f7e482e5e..9022af53f67a 100644
--- a/app/ui-utils/client/lib/modal.js
+++ b/app/ui-utils/client/lib/modal.js
@@ -60,13 +60,16 @@ export const modal = {
}
},
confirm(value) {
- if (this.fn) {
- this.fn(value);
- } else {
- this.close();
- }
+ const { fn } = this;
this.config.closeOnConfirm && this.close();
+
+ if (fn) {
+ fn.call(this, value);
+ return;
+ }
+
+ this.close();
},
showInputError(text) {
const errorEl = document.querySelector('.rc-modal__content-error');
diff --git a/app/ui-utils/client/lib/popover.js b/app/ui-utils/client/lib/popover.js
index 871d0924f4ef..8d522ff4a10e 100644
--- a/app/ui-utils/client/lib/popover.js
+++ b/app/ui-utils/client/lib/popover.js
@@ -5,6 +5,7 @@ import { FlowRouter } from 'meteor/kadira:flow-router';
import { Template } from 'meteor/templating';
import { TAPi18n } from 'meteor/tap:i18n';
import { isRtl, handleError } from '../../../utils';
+import { messageArgs } from '../../../ui-utils/client/lib/messageArgs';
import { ChatSubscription } from '../../../models';
import _ from 'underscore';
import { hide, leave } from './ChannelActions';
@@ -12,7 +13,6 @@ import { modal } from './modal';
import { messageBox } from './messageBox';
import { MessageAction } from './MessageAction';
import { RoomManager } from './RoomManager';
-
export const popover = {
renderedPopover: null,
open({ currentTarget, ...config }) {
@@ -180,7 +180,7 @@ Template.popover.events({
}
if (e.currentTarget.dataset.id === 'report-abuse') {
- const message = t.data.data._arguments[1];
+ const { msg: message } = messageArgs(t.data.data);
modal.open({
title: TAPi18n.__('Report_this_message_question_mark'),
text: message.msg,
diff --git a/app/ui-utils/client/lib/prependReplies.js b/app/ui-utils/client/lib/prependReplies.js
new file mode 100644
index 000000000000..b7a944eeca05
--- /dev/null
+++ b/app/ui-utils/client/lib/prependReplies.js
@@ -0,0 +1,22 @@
+import { Meteor } from 'meteor/meteor';
+import { MessageAction } from './MessageAction';
+import { Rooms, Users } from '../../../models/client';
+
+export const prependReplies = async (msg, replies = [], mention = false) => {
+ const { username } = Users.findOne({ _id: Meteor.userId() }, { fields: { username: 1 } });
+
+ const chunks = await Promise.all(replies.map(async ({ _id, rid, u }) => {
+ const permalink = await MessageAction.getPermaLink(_id);
+ const room = Rooms.findOne(rid, { fields: { t: 1 } });
+
+ let chunk = `[ ](${ permalink })`;
+ if (room.t === 'd' && u.username !== username && mention) {
+ chunk += ` @${ u.username }`;
+ }
+
+ return chunk;
+ }));
+
+ chunks.push(msg);
+ return chunks.join(' ');
+};
diff --git a/app/ui-utils/client/lib/readMessages.js b/app/ui-utils/client/lib/readMessages.js
index 71d6e93d7f25..3731f907bb6e 100644
--- a/app/ui-utils/client/lib/readMessages.js
+++ b/app/ui-utils/client/lib/readMessages.js
@@ -4,7 +4,7 @@ import { ChatSubscription, ChatMessage } from '../../../models';
import { RoomHistoryManager } from './RoomHistoryManager';
import { RoomManager } from './RoomManager';
import _ from 'underscore';
-
+import EventEmitter from 'wolfy87-eventemitter';
/* DEFINITIONS
- If window loses focus user needs to scroll or click/touch some place
- On hit ESC enable read, force read of current room and remove unread mark
@@ -18,10 +18,10 @@ import _ from 'underscore';
// window.addEventListener 'focus', ->
// readMessage.refreshUnreadMark(undefined, true)
-export const readMessage = new class {
+export const readMessage = new class extends EventEmitter {
constructor() {
+ super();
this.debug = false;
- this.callbacks = [];
this.read = _.debounce((force) => this.readNow(force), 1000);
this.canReadMessage = false;
}
@@ -48,10 +48,10 @@ export const readMessage = new class {
if (force === true) {
if (this.debug) { console.log('readMessage -> readNow via force rid:', rid); }
- return Meteor.call('readMessages', rid, function() {
+ return Meteor.call('readMessages', rid, () => {
RoomHistoryManager.getRoom(rid).unreadNotLoaded.set(0);
- self.refreshUnreadMark();
- return self.fireRead(rid);
+ this.refreshUnreadMark();
+ return this.emit(rid);
});
}
@@ -87,10 +87,10 @@ export const readMessage = new class {
}
if (this.debug) { console.log('readMessage -> readNow rid:', rid); }
- Meteor.call('readMessages', rid, function() {
+ Meteor.call('readMessages', rid, () => {
RoomHistoryManager.getRoom(rid).unreadNotLoaded.set(0);
- self.refreshUnreadMark();
- return self.fireRead(rid);
+ this.refreshUnreadMark();
+ return this.emit(rid);
});
}
@@ -106,14 +106,6 @@ export const readMessage = new class {
return this.canReadMessage === true;
}
- onRead(cb) {
- return this.callbacks.push(cb);
- }
-
- fireRead(rid) {
- return Array.from(this.callbacks).map((cb) => cb(rid));
- }
-
refreshUnreadMark(rid, force) {
if (rid == null) { rid = Session.get('openedRoom'); }
if (rid == null) {
diff --git a/app/ui-utils/client/lib/rtl.js b/app/ui-utils/client/lib/rtl.js
new file mode 100644
index 000000000000..64a55f8dbc1f
--- /dev/null
+++ b/app/ui-utils/client/lib/rtl.js
@@ -0,0 +1,7 @@
+// http://stackoverflow.com/a/14824756
+
+const ltrChars = 'A-Za-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02B8\u0300-\u0590\u0800-\u1FFF\u2C00-\uFB1C\uFDFE-\uFE6F\uFEFD-\uFFFF';
+const rtlChars = '\u0591-\u07FF\uFB1D-\uFDFD\uFE70-\uFEFC';
+const rtlRegExp = new RegExp(`^[^${ ltrChars }]*[${ rtlChars }]`);
+
+export const isRTL = (text) => rtlRegExp.test(text);
diff --git a/app/ui-vrecord/client/VRecDialog.js b/app/ui-vrecord/client/VRecDialog.js
index 5be1b0b15d0a..9ce0c98098c3 100644
--- a/app/ui-vrecord/client/VRecDialog.js
+++ b/app/ui-vrecord/client/VRecDialog.js
@@ -11,23 +11,23 @@ export const VRecDialog = new class {
this.height = 290;
}
- init() {
+ init(templateData) {
if (this.initiated) {
return;
}
this.initiated = true;
- return Blaze.render(Template.vrecDialog, document.body);
+ return Blaze.renderWithData(Template.vrecDialog, templateData, document.body);
}
- open(source, options = {}) {
+ open(source, { rid, tmid }) {
if (!this.initiated) {
- this.init();
+ this.init({ rid, tmid, input: source.querySelector('.js-input-message') });
}
this.source = source;
const dialog = $('.vrec-dialog');
- this.setPosition(dialog, source, options.anchor);
+ this.setPosition(dialog, source);
dialog.addClass('show');
this.opened = true;
@@ -44,7 +44,6 @@ export const VRecDialog = new class {
}
setPosition(dialog, source, anchor = 'left') {
-
const _set = () => {
const sourcePos = $(source).offset();
let top = sourcePos.top - this.height - 5;
@@ -71,7 +70,6 @@ export const VRecDialog = new class {
_set();
this.remove = set;
$(window).on('resize', set);
-
}
initializeCamera() {
diff --git a/app/ui-vrecord/client/vrecord.js b/app/ui-vrecord/client/vrecord.js
index b998c8c09822..544a6c757713 100644
--- a/app/ui-vrecord/client/vrecord.js
+++ b/app/ui-vrecord/client/vrecord.js
@@ -41,8 +41,9 @@ Template.vrecDialog.events({
},
'click .vrec-dialog .ok'() {
+ const { rid, tmid, input } = this;
const cb = (blob) => {
- fileUpload([{ file: blob, type: 'video', name: `${ TAPi18n.__('Video record') }.webm` }]);
+ fileUpload([{ file: blob, type: 'video', name: `${ TAPi18n.__('Video record') }.webm` }], input, { rid, tmid });
VRecDialog.close();
};
VideoRecorder.stop(cb);
diff --git a/app/ui/client/components/contextualBar.html b/app/ui/client/components/contextualBar.html
index 7d601e30cd33..0211162326a2 100644
--- a/app/ui/client/components/contextualBar.html
+++ b/app/ui/client/components/contextualBar.html
@@ -1,22 +1,24 @@
{{#if template}}
-
+
-
+
{{> Template.dynamic template=template data=flexData}}
-
-
+
+
{{/if}}
diff --git a/app/ui/client/components/contextualBar.js b/app/ui/client/components/contextualBar.js
index d8e52e5b4917..b96e6b61894b 100644
--- a/app/ui/client/components/contextualBar.js
+++ b/app/ui/client/components/contextualBar.js
@@ -21,8 +21,11 @@ Template.contextualBar.helpers({
return Template.instance().tabBar.getData();
},
flexData() {
- return Object.assign(Template.currentData().data || {}, {
- tabBar: Template.instance().tabBar,
- });
+ const { tabBar } = Template.instance();
+ return {
+ tabBar,
+ ...tabBar.getData(),
+ ...Template.currentData().data,
+ };
},
});
diff --git a/app/ui/client/components/header/headerRoom.js b/app/ui/client/components/header/headerRoom.js
index cb4e6f1f85be..ed718d018ba1 100644
--- a/app/ui/client/components/header/headerRoom.js
+++ b/app/ui/client/components/header/headerRoom.js
@@ -153,17 +153,6 @@ Template.headerRoom.events({
);
},
- 'click .edit-room-title'(event) {
- event.preventDefault();
- Session.set('editRoomTitle', true);
- $('.rc-header').addClass('visible');
- return Meteor.setTimeout(() =>
- $('#room-title-field')
- .focus()
- .select(),
- 10);
- },
-
'click .js-open-parent-channel'(event, t) {
event.preventDefault();
const { prid } = t.currentChannel;
diff --git a/app/ui/client/index.js b/app/ui/client/index.js
index 1d34f5fc5c7f..0a3ae71620d4 100644
--- a/app/ui/client/index.js
+++ b/app/ui/client/index.js
@@ -14,7 +14,7 @@ import './lib/textarea-autogrow';
import './lib/codeMirror/codeMirror';
export { AudioRecorder } from './lib/recorderjs/audioRecorder';
export { VideoRecorder } from './lib/recorderjs/videoRecorder';
-import './lib/textarea-cursor/set-cursor-position';
+import './lib/textarea-cursor';
import './views/cmsPage.html';
import './views/fxos.html';
import './views/modal.html';
diff --git a/app/ui/client/lib/chatMessages.js b/app/ui/client/lib/chatMessages.js
index f0c38ec223d3..4c7df34f3d65 100644
--- a/app/ui/client/lib/chatMessages.js
+++ b/app/ui/client/lib/chatMessages.js
@@ -1,351 +1,407 @@
+import _ from 'underscore';
+import moment from 'moment';
+import toastr from 'toastr';
+
import { Meteor } from 'meteor/meteor';
-import { ReactiveVar } from 'meteor/reactive-var';
import { Random } from 'meteor/random';
-import { Tracker } from 'meteor/tracker';
import { FlowRouter } from 'meteor/kadira:flow-router';
import { Session } from 'meteor/session';
import { TAPi18n } from 'meteor/tap:i18n';
-import { t, getUserPreference, slashCommands, handleError } from '../../../utils';
-import { MessageAction, messageProperties, MessageTypes, readMessage, modal, call } from '../../../ui-utils';
-import { settings } from '../../../settings';
-import { callbacks } from '../../../callbacks';
+
+import { t, slashCommands, handleError } from '../../../utils/client';
+import {
+ messageProperties,
+ MessageTypes,
+ readMessage,
+ modal,
+ call,
+ keyCodes,
+ prependReplies,
+} from '../../../ui-utils/client';
+import { settings } from '../../../settings/client';
+import { callbacks } from '../../../callbacks/client';
import { promises } from '../../../promises/client';
-import { hasAtLeastOnePermission } from '../../../authorization';
-import { Messages, Rooms, ChatMessage, ChatSubscription } from '../../../models';
-import { emoji } from '../../../emoji';
+import { hasAtLeastOnePermission } from '../../../authorization/client';
+import { Messages, Rooms, ChatMessage, ChatSubscription } from '../../../models/client';
+import { emoji } from '../../../emoji/client';
+
import { KonchatNotification } from './notification';
import { MsgTyping } from './msgTyping';
-import _ from 'underscore';
-import s from 'underscore.string';
-import moment from 'moment';
-import toastr from 'toastr';
import { fileUpload } from './fileUpload';
-let sendOnEnter = '';
+const messageBoxState = {
+ saveValue: _.debounce(({ rid, tmid }, value) => {
+ const key = ['messagebox', rid, tmid].filter(Boolean).join('_');
+ value ? localStorage.setItem(key, value) : localStorage.removeItem(key);
+ }, 1000),
+
+ restoreValue: ({ rid, tmid }) => {
+ const key = ['messagebox', rid, tmid].filter(Boolean).join('_');
+ return localStorage.getItem(key);
+ },
+
+ restore: ({ rid, tmid }, input) => {
+ const value = messageBoxState.restoreValue({ rid, tmid });
+ if (typeof value === 'string') {
+ messageBoxState.set(input, value);
+ }
+ },
+
+ save: ({ rid, tmid }, input) => {
+ messageBoxState.saveValue({ rid, tmid }, input.value);
+ },
+
+ set: (input, value) => {
+ input.value = value;
+ $(input).trigger('change').trigger('input');
+ },
+
+ purgeAll: () => {
+ Object.keys(localStorage)
+ .filter((key) => key.indexOf('messagebox_') === 0)
+ .forEach((key) => localStorage.removeItem(key));
+ },
+};
-Meteor.startup(() => {
- Tracker.autorun(function() {
- const user = Meteor.userId();
- sendOnEnter = getUserPreference(user, 'sendOnEnter');
- });
-});
+callbacks.add('afterLogoutCleanUp', messageBoxState.purgeAll, callbacks.priority.MEDIUM, 'chatMessages-after-logout-cleanup');
-export const getPermaLinks = async (replies) => {
- const promises = replies.map(async (reply) =>
- MessageAction.getPermaLink(reply._id)
- );
+const showModal = (config) => new Promise((resolve, reject) => modal.open(config, resolve, reject));
- return Promise.all(promises);
-};
+export class ChatMessages {
+ editing = {}
-export const mountReply = async (msg, input) => {
- const replies = $(input).data('reply');
- const mentionUser = $(input).data('mention-user') || false;
+ records = {}
- if (replies && replies.length) {
- const permalinks = await getPermaLinks(replies);
+ initializeWrapper(wrapper) {
+ this.wrapper = wrapper;
+ }
- replies.forEach(async (reply, replyIndex) => {
- if (reply !== undefined) {
- msg += `[ ](${ permalinks[replyIndex] }) `;
+ initializeInput(input, { rid, tmid }) {
+ this.input = input;
+ this.$input = $(this.input);
- const roomInfo = Rooms.findOne(reply.rid, { fields: { t: 1 } });
- if (roomInfo.t !== 'd' && reply.u.username !== Meteor.user().username && mentionUser) {
- msg += `@${ reply.u.username } `;
- }
- }
- });
+ if (!input || !rid) {
+ return;
+ }
+
+ messageBoxState.restore({ rid, tmid }, input);
+ this.restoreReplies();
+ this.requestInputFocus();
}
- return msg;
-};
+ async restoreReplies() {
+ const mid = FlowRouter.getQueryParam('reply');
+ if (!mid) {
+ return;
+ }
-export const ChatMessages = class ChatMessages {
- constructor() {
+ const message = Messages.findOne(mid) || await call('getSingleMessage', mid);
+ if (!message) {
+ return;
+ }
- this.saveTextMessageBox = _.debounce((rid, value) => {
- const key = `messagebox_${ rid }`;
- return value.length ? localStorage.setItem(key, value) : localStorage.removeItem(key);
- }, 1000);
+ this.$input.data('reply', [message]).trigger('dataChange');
}
- init(node) {
- this.editing = {};
- this.records = {};
- this.messageMaxSize = settings.get('Message_MaxAllowedSize');
- this.wrapper = $(node).find('.wrapper');
- this.input = this.input || $(node).find('.js-input-message').get(0);
- this.$input = $(this.input);
- this.hasValue = new ReactiveVar(false);
- this.bindEvents();
+ requestInputFocus() {
+ setTimeout(() => {
+ if (this.input && window.matchMedia('screen and (min-device-width: 500px)').matches) {
+ this.input.focus();
+ }
+ }, 200);
}
getEditingIndex(element) {
- const msgs = this.wrapper.get(0).querySelectorAll('.own:not(.system)');
- let index = 0;
- for (const msg of Array.from(msgs)) {
- if (msg === element) {
- return index;
- }
- index++;
- }
- return -1;
+ const msgs = this.wrapper.querySelectorAll('.own:not(.system)');
+ return Array.from(msgs).findIndex((msg) => msg === element);
}
recordInputAsDraft() {
- const { id } = this.editing;
-
- const message = this.getMessageById(id);
- const record = this.records[id] || {};
+ const message = ChatMessage.findOne(this.editing.id);
+ const record = this.records[this.editing.id] || {};
const draft = this.input.value;
if (draft === message.msg) {
- return this.clearCurrentDraft();
- } else {
- record.draft = draft;
- return this.records[id] = record;
+ this.clearCurrentDraft();
+ return;
}
- }
-
- getMessageDraft(id) {
- return this.records[id];
- }
- clearMessageDraft(id) {
- return delete this.records[id];
+ record.draft = draft;
+ this.records[this.editing.id] = record;
}
clearCurrentDraft() {
- return this.clearMessageDraft(this.editing.id);
+ delete this.records[this.editing.id];
}
resetToDraft(id) {
- const message = this.getMessageById(id);
-
- const old_value = this.input.value;
- this.input.value = message.msg;
-
- return old_value !== message.msg;
- }
-
- getMessageById(id) {
- return ChatMessage.findOne(id);
+ const message = ChatMessage.findOne(id);
+ const oldValue = this.input.value;
+ messageBoxState.set(this.input, message.msg);
+ return oldValue !== message.msg;
}
toPrevMessage() {
const { index } = this.editing;
- return this.editByIndex((index != null) ? index - 1 : undefined);
+ this.editByIndex(index ? index - 1 : undefined);
}
toNextMessage() {
const { index } = this.editing;
- if (!this.editByIndex(index + 1)) { return this.clearEditing(); }
+ if (!this.editByIndex(index + 1)) {
+ this.clearEditing();
+ }
}
editByIndex(index) {
- if (!this.editing.element && index) { return false; }
+ if (!this.editing.element && index) {
+ return false;
+ }
+
+ const messageElements = this.wrapper.querySelectorAll('.own:not(.system)');
- const msgs = this.wrapper.get(0).querySelectorAll('.own:not(.system)');
- if (index == null) { index = msgs.length - 1; }
+ if (!index) {
+ index = messageElements.length - 1;
+ }
- if (!msgs[index]) { return false; }
+ if (!messageElements[index]) {
+ return false;
+ }
- const element = msgs[index];
+ const element = messageElements[index];
this.edit(element, index);
return true;
}
edit(element, index) {
- index = index != null ? index : this.getEditingIndex(element);
+ index = index || this.getEditingIndex(element);
- const message = this.getMessageById(element.getAttribute('id'));
+ const message = ChatMessage.findOne(element.dataset.id);
const hasPermission = hasAtLeastOnePermission('edit-message', message.rid);
const editAllowed = settings.get('Message_AllowEditing');
const editOwn = message && message.u && message.u._id === Meteor.userId();
- if (!hasPermission && (!editAllowed || !editOwn)) { return; }
- if (element.classList.contains('system')) { return; }
+ if (!hasPermission && (!editAllowed || !editOwn)) {
+ return;
+ }
+
+ if (element.classList.contains('system')) {
+ return;
+ }
const blockEditInMinutes = settings.get('Message_AllowEditing_BlockEditInMinutes');
if (blockEditInMinutes && blockEditInMinutes !== 0) {
let currentTsDiff;
let msgTs;
- if (message.ts != null) { msgTs = moment(message.ts); }
- if (msgTs != null) { currentTsDiff = moment().diff(msgTs, 'minutes'); }
+ if (message.ts) {
+ msgTs = moment(message.ts);
+ }
+ if (msgTs) {
+ currentTsDiff = moment().diff(msgTs, 'minutes');
+ }
if (currentTsDiff > blockEditInMinutes) {
return;
}
}
- const draft = this.getMessageDraft(message._id);
+ const draft = this.records[message._id];
let msg = draft && draft.draft;
msg = msg || message.msg;
const editingNext = this.editing.index < index;
- // const old_input = this.input.value;
-
this.clearEditing();
- this.hasValue.set(true);
this.editing.element = element;
this.editing.index = index;
this.editing.id = message._id;
- // TODO: stop set two elements
this.input.parentElement.classList.add('editing');
- this.input.classList.add('editing');
-
element.classList.add('editing');
if (message.attachments && message.attachments[0].description) {
- this.input.value = message.attachments[0].description;
+ messageBoxState.set(this.input, message.attachments[0].description);
} else {
- this.input.value = msg;
+ messageBoxState.set(this.input, msg);
}
- $(this.input).trigger('change').trigger('input');
- const cursor_pos = editingNext ? 0 : -1;
- this.$input.setCursorPosition(cursor_pos);
+ const cursorPosition = editingNext ? 0 : -1;
+ this.$input.setCursorPosition(cursorPosition);
this.input.focus();
- return this.input;
}
clearEditing() {
- if (this.editing.element) {
- this.recordInputAsDraft();
- // TODO: stop set two elements
- this.input.classList.remove('editing');
- this.input.parentElement.classList.remove('editing');
-
- this.editing.element.classList.remove('editing');
- delete this.editing.id;
- delete this.editing.element;
- delete this.editing.index;
-
- this.input.value = this.editing.saved || '';
- $(this.input).trigger('change').trigger('input');
- const cursor_pos = this.editing.savedCursor != null ? this.editing.savedCursor : -1;
- this.$input.setCursorPosition(cursor_pos);
-
- return this.hasValue.set(this.input.value !== '');
- }
- this.editing.saved = this.input.value;
- return this.editing.savedCursor = this.input.selectionEnd;
+ if (!this.editing.element) {
+ this.editing.saved = this.input.value;
+ this.editing.savedCursor = this.input.selectionEnd;
+ return;
+ }
+
+ this.recordInputAsDraft();
+ this.input.parentElement.classList.remove('editing');
+ this.editing.element.classList.remove('editing');
+ delete this.editing.id;
+ delete this.editing.element;
+ delete this.editing.index;
+
+ messageBoxState.set(this.input, this.editing.saved || '');
+ const cursorPosition = this.editing.savedCursor ? this.editing.savedCursor : -1;
+ this.$input.setCursorPosition(cursorPosition);
}
- /**
- * * @param {string} rim room ID
- * * @param {Element} input DOM element
- * * @param {function?} done callback
- */
- async send(rid, input, done = function() {}) {
+
+ async send(event, { rid, tmid, value }, done = () => {}) {
+ const threadsEnabled = settings.get('Threads_enabled');
+
+ MsgTyping.stop(rid);
if (!ChatSubscription.findOne({ rid })) {
await call('joinRoom', rid);
}
- if (s.trim(input.value) !== '') {
+ messageBoxState.save({ rid, tmid }, this.input);
+
+ let msg = value;
+ if (value.trim()) {
+ const mention = this.$input.data('mention-user') || false;
+ const replies = this.$input.data('reply') || [];
+ if (!mention || !threadsEnabled) {
+ msg = await prependReplies(msg, replies, mention);
+ }
+
+ if (mention && threadsEnabled && replies.length) {
+ tmid = replies[0]._id;
+ }
+ } else {
+ msg = '';
+ }
+
+ if (msg) {
readMessage.enable();
readMessage.readNow();
$('.message.first-unread').removeClass('first-unread');
- let msg = '';
-
- msg += await mountReply(msg, input);
+ const message = await promises.run('onClientBeforeSendMessage', {
+ _id: Random.id(),
+ rid,
+ tmid,
+ msg,
+ });
- msg += input.value;
- $(input)
- .removeData('reply')
- .trigger('dataChange');
+ try {
+ await this.processMessageSend(message);
+ this.$input.removeData('reply').trigger('dataChange');
+ } catch (error) {
+ console.error(error);
+ handleError(error);
+ } finally {
+ return done();
+ }
+ }
+ if (this.editing.id) {
+ const message = ChatMessage.findOne(this.editing.id);
+ const isDescription = message.attachments && message.attachments[0] && message.attachments[0].description;
- if (msg.slice(0, 2) === '+:') {
- const reaction = msg.slice(1).trim();
- if (emoji.list[reaction]) {
- const lastMessage = ChatMessage.findOne({ rid }, { fields: { ts: 1 }, sort: { ts: -1 } });
- Meteor.call('setReaction', reaction, lastMessage._id);
- input.value = '';
- $(input).trigger('change').trigger('input');
- return;
+ try {
+ if (isDescription) {
+ await this.processMessageEditing({ _id: this.editing.id, rid, msg: '' });
+ return done();
}
+
+ this.resetToDraft(this.editing.id);
+ this.confirmDeleteMsg(message, done);
+ } catch (error) {
+ console.error(error);
+ handleError(error);
}
+ }
+ }
- // Run to allow local encryption, and maybe other client specific actions to be run before send
- const msgObject = await promises.run('onClientBeforeSendMessage', { _id: Random.id(), rid, msg });
+ async processMessageSend(message) {
+ if (await this.processSetReaction(message)) {
+ return;
+ }
- // checks for the final msgObject.msg size before actually sending the message
- if (this.isMessageTooLong(msgObject.msg)) {
- if (!settings.get('FileUpload_Enabled') || !settings.get('Message_AllowConvertLongMessagesToAttachment') || this.editing.id) {
- return toastr.error(t('Message_too_long'));
- }
- return modal.open({
- text: t('Message_too_long_as_an_attachment_question'),
- title: '',
- type: 'warning',
- showCancelButton: true,
- confirmButtonText: t('Yes'),
- cancelButtonText: t('No'),
- closeOnConfirm: true,
- }, () => {
- const contentType = 'text/plain';
- const messageBlob = new Blob([msgObject.msg], { type: contentType });
- const fileName = `${ Meteor.user().username } - ${ new Date() }.txt`;
- const file = new File([messageBlob], fileName, { type: contentType, lastModified: Date.now() });
- fileUpload([{ file, name: fileName }]);
- this.clearCurrentDraft();
- input.value = '';
- $(input).trigger('change').trigger('input');
- this.hasValue.set(false);
- this.stopTyping(rid);
- this.saveTextMessageBox(rid, '');
- return done();
- }, done);
- }
+ this.clearCurrentDraft();
- this.clearCurrentDraft();
+ if (await this.processTooLongMessage(message)) {
+ return;
+ }
- if (this.editing.id) {
- this.update(this.editing.id, rid, msgObject.msg);
- return;
- }
+ if (await this.processMessageEditing({ ...message, _id: this.editing.id })) {
+ return;
+ }
- KonchatNotification.removeRoomNotification(rid);
- input.value = '';
- $(input).trigger('change').trigger('input');
+ KonchatNotification.removeRoomNotification(message.rid);
- if (typeof input.updateAutogrow === 'function') {
- input.updateAutogrow();
- }
- this.hasValue.set(false);
- this.stopTyping(rid);
+ if (await this.processSlashCommand(message)) {
+ return;
+ }
- if (this.processSlashCommand(msgObject)) {
- return;
- }
+ await call('sendMessage', message);
+ }
- Meteor.call('sendMessage', msgObject);
+ async processSetReaction({ rid, tmid, msg }) {
+ if (msg.slice(0, 2) !== '+:') {
+ return false;
+ }
- this.saveTextMessageBox(rid, '');
+ const reaction = msg.slice(1).trim();
+ if (!emoji.list[reaction]) {
+ return false;
+ }
- return done();
+ const lastMessage = ChatMessage.findOne({ rid, tmid }, { fields: { ts: 1 }, sort: { ts: -1 } });
+ await call('setReaction', reaction, lastMessage._id);
+ return true;
+ }
+ async processTooLongMessage({ msg, rid, tmid }) {
+ const adjustedMessage = messageProperties.messageWithoutEmojiShortnames(msg);
+ if (messageProperties.length(adjustedMessage) <= settings.get('Message_MaxAllowedSize') && msg) {
+ return false;
+ }
- // If edited message was emptied we ask for deletion
+ if (!settings.get('FileUpload_Enabled') || !settings.get('Message_AllowConvertLongMessagesToAttachment') || this.editing.id) {
+ throw { error: 'Message_too_long' };
}
- if (this.editing.element) {
- const message = this.getMessageById(this.editing.id);
- if (message.attachments && message.attachments[0] && message.attachments[0].description) {
- return this.update(this.editing.id, rid, '', true);
- }
- // Restore original message in textbox in case delete is canceled
- this.resetToDraft(this.editing.id);
- return this.confirmDeleteMsg(message, done);
+ try {
+ await showModal({
+ text: t('Message_too_long_as_an_attachment_question'),
+ title: '',
+ type: 'warning',
+ showCancelButton: true,
+ confirmButtonText: t('Yes'),
+ cancelButtonText: t('No'),
+ closeOnConfirm: true,
+ });
+
+ const contentType = 'text/plain';
+ const messageBlob = new Blob([msg], { type: contentType });
+ const fileName = `${ Meteor.user().username } - ${ new Date() }.txt`;
+ const file = new File([messageBlob], fileName, { type: contentType, lastModified: Date.now() });
+ fileUpload([{ file, name: fileName }], this.input, { rid, tmid });
+ } finally {
+ return true;
}
}
- processSlashCommand(msgObject) {
- // Check if message starts with /command
+ async processMessageEditing(message) {
+ if (!message._id) {
+ return false;
+ }
+
+ if (MessageTypes.isSystemMessage(message)) {
+ return false;
+ }
+
+ await call('updateMessage', message);
+ this.clearEditing();
+ return true;
+ }
+
+ async processSlashCommand(msgObject) {
if (msgObject.msg[0] === '/') {
const match = msgObject.msg.match(/^\/([^\s]+)(?:\s+(.*))?$/m);
if (match) {
@@ -359,7 +415,9 @@ export const ChatMessages = class ChatMessages {
if (commandOptions.clientOnly) {
commandOptions.callback(command, param, msgObject);
} else {
- Meteor.call('slashCommand', { cmd: command, params: param, msg: msgObject }, (err, result) => typeof commandOptions.result === 'function' && commandOptions.result(err, result, { cmd: command, params: param, msg: msgObject }));
+ Meteor.call('slashCommand', { cmd: command, params: param, msg: msgObject }, (err, result) => {
+ typeof commandOptions.result === 'function' && commandOptions.result(err, result, { cmd: command, params: param, msg: msgObject });
+ });
}
return true;
@@ -387,13 +445,16 @@ export const ChatMessages = class ChatMessages {
return false;
}
- confirmDeleteMsg(message, done = function() {}) {
- if (MessageTypes.isSystemMessage(message)) { return; }
+ confirmDeleteMsg(message, done = () => {}) {
+ if (MessageTypes.isSystemMessage(message)) {
+ return done();
+ }
const room = message.drid && Rooms.findOne({
_id: message.drid,
prid: { $exists: true },
});
+
modal.open({
title: t('Are_you_sure'),
text: room ? t('The_message_is_a_discussion_you_will_not_be_able_to_recover') : t('You_will_not_be_able_to_recover'),
@@ -413,266 +474,100 @@ export const ChatMessages = class ChatMessages {
});
if (this.editing.id === message._id) {
- this.clearEditing(message);
+ this.clearEditing();
}
+
this.deleteMsg(message);
this.$input.focus();
- return done();
+ done();
});
}
- deleteMsg(message) {
- const forceDelete = hasAtLeastOnePermission('force-delete-message', message.rid);
+ async deleteMsg({ _id, rid, ts }) {
+ const forceDelete = hasAtLeastOnePermission('force-delete-message', rid);
const blockDeleteInMinutes = settings.get('Message_AllowDeleting_BlockDeleteInMinutes');
if (blockDeleteInMinutes && forceDelete === false) {
let msgTs;
- if (message.ts != null) { msgTs = moment(message.ts); }
+ if (ts) {
+ msgTs = moment(ts);
+ }
let currentTsDiff;
- if (msgTs != null) { currentTsDiff = moment().diff(msgTs, 'minutes'); }
+ if (msgTs) {
+ currentTsDiff = moment().diff(msgTs, 'minutes');
+ }
if (currentTsDiff > blockDeleteInMinutes) {
toastr.error(t('Message_deleting_blocked'));
return;
}
}
- return Meteor.call('deleteMessage', { _id: message._id }, function(error) {
- if (error) {
- return handleError(error);
- }
- });
- }
-
- pinMsg(message) {
- message.pinned = true;
- return Meteor.call('pinMessage', message, function(error) {
- if (error) {
- return handleError(error);
- }
- });
- }
-
- unpinMsg(message) {
- message.pinned = false;
- return Meteor.call('unpinMessage', message, function(error) {
- if (error) {
- return handleError(error);
- }
- });
- }
-
- update(id, rid, msg, isDescription) {
- if ((s.trim(msg) !== '') || (isDescription === true)) {
- Meteor.call('updateMessage', { _id: id, msg, rid });
- this.clearEditing();
- return this.stopTyping(rid);
- }
- }
-
- startTyping(rid, input) {
- if (s.trim(input.value) === '') {
- return this.stopTyping(rid);
- }
- return MsgTyping.start(rid);
- }
-
- stopTyping(rid) {
- return MsgTyping.stop(rid);
- }
-
- bindEvents() {
- if (this.wrapper && this.wrapper.length) {
- $('.input-message').autogrow();
- }
- }
-
- tryCompletion(input) {
- const [value] = input.value.match(/[^\s]+$/) || [];
- if (!value) { return; }
- const re = new RegExp(value, 'i');
- const user = Meteor.users.findOne({ username: re });
- if (user) {
- return input.value = input.value.replace(value, `@${ user.username } `);
- }
- }
-
- insertNewLine(input) {
- if (document.selection) {
- input.focus();
- const sel = document.selection.createRange();
- sel.text = '\n';
- } else if (input.selectionStart || input.selectionStart === 0) {
- const newPosition = input.selectionStart + 1;
- const before = input.value.substring(0, input.selectionStart);
- const after = input.value.substring(input.selectionEnd, input.value.length);
- input.value = `${ before }\n${ after }`;
- input.selectionStart = input.selectionEnd = newPosition;
- } else {
- input.value += '\n';
- }
- input.blur();
- input.focus();
- typeof input.updateAutogrow === 'function' && input.updateAutogrow();
- }
-
- restoreText(rid) {
- const text = localStorage.getItem(`messagebox_${ rid }`);
- if (typeof text === 'string' && this.input) {
- this.input.value = text;
- this.$input.trigger('input');
+ try {
+ await call('deleteMessage', { _id });
+ } catch (error) {
+ console.error(error);
+ handleError(error);
}
- const msgId = FlowRouter.getQueryParam('reply');
- if (!msgId) {
- return;
- }
- const message = Messages.findOne(msgId);
- if (message) {
- return this.$input.data('reply', message).trigger('dataChange');
- }
- Meteor.call('getSingleMessage', msgId, (err, msg) => !err && this.$input.data('reply', msg).trigger('dataChange'));
}
- keyup(rid, event) {
- let i;
- const input = event.currentTarget;
- const k = event.which;
- const keyCodes = [
- 13, // Enter
- 20, // Caps lock
- 16, // Shift
- 9, // Tab
- 27, // Escape Key
- 17, // Control Key
- 91, // Windows Command Key
- 19, // Pause Break
- 18, // Alt Key
- 93, // Right Click Point Key
- 45, // Insert Key
- 34, // Page Down
- 35, // Page Up
- 144, // Num Lock
- 145, // Scroll Lock
- ];
- for (i = 35; i <= 40; i++) { keyCodes.push(i); } // Home, End, Arrow Keys
- for (i = 112; i <= 123; i++) { keyCodes.push(i); } // F1 - F12
-
- if (!Array.from(keyCodes).includes(k)) {
- this.startTyping(rid, input);
- }
-
- this.saveTextMessageBox(rid, input.value);
-
- return this.hasValue.set(input.value !== '');
- }
-
- keydown(rid, event) {
- const { currentTarget: input, which: k } = event;
-
- if (k === 13 || k === 10) { // New line or carriage return
- const sendOnEnterActive = sendOnEnter == null || sendOnEnter === 'normal' ||
- (sendOnEnter === 'desktop' && Meteor.Device.isDesktop());
- const withModifier = event.shiftKey || event.ctrlKey || event.altKey || event.metaKey;
- const isSending = (sendOnEnterActive && !withModifier) || (!sendOnEnterActive && withModifier);
+ keydown(event) {
+ const { currentTarget: input, which: keyCode } = event;
- event.preventDefault();
- event.stopPropagation();
- if (isSending) {
- this.send(rid, input);
- } else {
- this.insertNewLine(input);
+ if (keyCode === keyCodes.ESCAPE && this.editing.index) {
+ if (!this.resetToDraft(this.editing.id)) {
+ this.clearCurrentDraft();
+ this.clearEditing();
}
- return;
- }
-
- if (k === 9) { // Tab
event.preventDefault();
event.stopPropagation();
- this.tryCompletion(input);
+ return true;
}
- if (k === 27) { // Escape
- if (this.editing.index != null) {
- // const record = this.getMessageDraft(this.editing.id);
-
- // If resetting did nothing then edited message is same as original
- if (!this.resetToDraft(this.editing.id)) {
- this.clearCurrentDraft();
- this.clearEditing();
- }
-
- event.preventDefault();
- event.stopPropagation();
- return;
+ if (keyCode === keyCodes.ARROW_UP || keyCode === keyCodes.ARROW_DOWN) {
+ if (event.shiftKey) {
+ return true;
}
- } else if (k === 38 || k === 40) { // Arrow Up or down
- if (event.shiftKey) { return true; }
- const cursor_pos = input.selectionEnd;
+ const cursorPosition = input.selectionEnd;
- if (k === 38) { // Arrow Up
- if (cursor_pos === 0) {
+ if (keyCode === keyCodes.ARROW_UP) {
+ if (cursorPosition === 0) {
this.toPrevMessage();
} else if (!event.altKey) {
return true;
}
- if (event.altKey) { this.$input.setCursorPosition(0); }
-
- } else { // Arrow Down
- if (cursor_pos === input.value.length) {
+ if (event.altKey) {
+ this.$input.setCursorPosition(0);
+ }
+ } else {
+ if (cursorPosition === input.value.length) {
this.toNextMessage();
} else if (!event.altKey) {
return true;
}
- if (event.altKey) { this.$input.setCursorPosition(-1); }
+ if (event.altKey) {
+ this.$input.setCursorPosition(-1);
+ }
}
return false;
-
- // ctrl (command) + shift + k -> clear room messages
- }
- // TODO
- // else if (k === 75 && navigator && navigator.platform && event.shiftKey && (navigator.platform.indexOf('Mac') !== -1 ? event.metaKey : event.ctrlKey)) {
- // return RoomHistoryManager.clear(rid);
- // }
- }
-
- valueChanged(/* rid, event*/) {
- if (this.input.value.length === 1) {
- return this.determineInputDirection();
}
}
- determineInputDirection() {
- return this.input.dir = this.isMessageRtl(this.input.value) ? 'rtl' : 'ltr';
- }
-
- // http://stackoverflow.com/a/14824756
- isMessageRtl(message) {
- const ltrChars = 'A-Za-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02B8\u0300-\u0590\u0800-\u1FFF\u2C00-\uFB1C\uFDFE-\uFE6F\uFEFD-\uFFFF';
- const rtlChars = '\u0591-\u07FF\uFB1D-\uFDFD\uFE70-\uFEFC';
- const rtlDirCheck = new RegExp(`^[^${ ltrChars }]*[${ rtlChars }]`);
-
- return rtlDirCheck.test(message);
- }
+ keyup(event, { rid, tmid }) {
+ const { currentTarget: input, which: keyCode } = event;
- isMessageTooLong(message) {
- const adjustedMessage = messageProperties.messageWithoutEmojiShortnames(message);
- return messageProperties.length(adjustedMessage) > this.messageMaxSize && message;
- }
+ if (!Object.values(keyCodes).includes(keyCode)) {
+ if (input.value.trim()) {
+ MsgTyping.start(rid);
+ } else {
+ MsgTyping.stop(rid);
+ }
+ }
- isEmpty() {
- return !this.hasValue.get();
+ messageBoxState.save({ rid, tmid }, input);
}
-};
-
-
-callbacks.add('afterLogoutCleanUp', () => {
- Object.keys(localStorage).forEach((item) => {
- if (item.indexOf('messagebox_') === 0) {
- localStorage.removeItem(item);
- }
- });
-}, callbacks.priority.MEDIUM, 'chatMessages-after-logout-cleanup');
+}
diff --git a/app/ui/client/lib/fileUpload.js b/app/ui/client/lib/fileUpload.js
index cb7b9aea5fa0..696e54be9461 100644
--- a/app/ui/client/lib/fileUpload.js
+++ b/app/ui/client/lib/fileUpload.js
@@ -2,12 +2,10 @@ import { Meteor } from 'meteor/meteor';
import { Tracker } from 'meteor/tracker';
import { Session } from 'meteor/session';
import s from 'underscore.string';
-
-import { mountReply } from './chatMessages';
import { fileUploadHandler } from '../../../file-upload';
import { Handlebars } from 'meteor/ui';
import { t, fileUploadIsValidContentType } from '../../../utils';
-import { modal } from '../../../ui-utils';
+import { modal, prependReplies } from '../../../ui-utils';
const readAsDataURL = (file, callback) => {
@@ -139,14 +137,12 @@ const getUploadPreview = async (file, preview) => {
return getGenericUploadPreview(file, preview);
};
-export const fileUpload = async (files, input) => {
+export const fileUpload = async (files, input, { rid, tmid }) => {
files = [].concat(files);
- const roomId = Session.get('openedRoom');
-
- let msg = '';
-
- msg += await mountReply(msg, input);
+ const replies = $(input).data('reply') || [];
+ const mention = $(input).data('mention-user') || false;
+ const msg = await prependReplies('', replies, mention);
const uploadNextFile = () => {
const file = files.pop();
@@ -193,7 +189,7 @@ export const fileUpload = async (files, input) => {
name: document.getElementById('file-name').value || file.name || file.file.name,
size: file.file.size,
type: file.file.type,
- rid: roomId,
+ rid,
description: document.getElementById('file-description').value,
};
@@ -233,12 +229,12 @@ export const fileUpload = async (files, input) => {
return;
}
- Meteor.call('sendFileMessage', roomId, storage, file, { msg }, () => {
+ Meteor.call('sendFileMessage', rid, storage, file, { msg, tmid }, () => {
$(input)
.removeData('reply')
.trigger('dataChange');
- Meteor.setTimeout(() => {
+ setTimeout(() => {
const uploads = Session.get('uploading') || [];
Session.set('uploading', uploads.filter((u) => u.id !== upload.id));
}, 2000);
diff --git a/app/ui/client/lib/textarea-autogrow.js b/app/ui/client/lib/textarea-autogrow.js
index cfeab6ecca1e..b467f69087d2 100644
--- a/app/ui/client/lib/textarea-autogrow.js
+++ b/app/ui/client/lib/textarea-autogrow.js
@@ -1,131 +1,101 @@
import _ from 'underscore';
-const times = function(string, number) {
- let r = '';
- for (let i = 0; i < number; i++) { r += string; }
- return r;
+
+const replaceWhitespaces = (whitespaces) => `${ ' '.repeat(whitespaces.length - 1) } `;
+
+const getShadow = () => {
+ let shadow = document.getElementById('autogrow-shadow');
+
+ if (!shadow) {
+ shadow = document.createElement('div');
+ shadow.setAttribute('id', 'autogrow-shadow');
+ document.body.appendChild(shadow);
+ }
+
+ return shadow;
};
-const runTimes = (space) => `${ times(' ', space.length - 1) } `;
-(function($) {
- /**
- * Auto-growing textareas; technique ripped from Facebook
- *
- *
- * http://github.com/jaz303/jquery-grab-bag/tree/master/javascripts/jquery.autogrow-textarea.js
- */
- $.fn.autogrow = function(options) {
- let shadow = $('body > #autogrow-shadow');
- if (!shadow.length) {
- shadow = $('
').addClass('autogrow-shadow').appendTo(document.body);
- }
- return this.filter('textarea').each(function() {
- const self = this;
- const $self = $(self);
- const minHeight = $self.height();
-
- const settings = {
- postGrowCallback: null,
- ...options,
- };
-
- const maxHeight = window.getComputedStyle(self)['max-height'].replace('px', '');
-
- const trigger = _.debounce(() => $self.trigger('autogrow', []), 500);
- const getWidth = (() => {
- let width = 0;
- let expired = false;
- let timer = null;
- return () => {
- if (timer) {
- clearTimeout(timer);
- }
- timer = setTimeout(function() {
- expired = true;
- timer = null;
- }, 300);
- if (!width || expired) {
- width = $self.width();
- expired = false;
- }
- return width;
- };
- })();
-
- const width = getWidth();
- let lastWidth = width;
- let lastHeight = minHeight;
-
- let length = 0;
- shadow.css({
- position: 'absolute',
- top: -10000,
- left: -10000,
- width,
- fontSize: $self.css('fontSize'),
- fontFamily: $self.css('fontFamily'),
- fontWeight: $self.css('fontWeight'),
- lineHeight: $self.css('lineHeight'),
- resize: 'none',
- wordWrap: 'break-word',
- });
- const update = function update(event) {
- const width = getWidth();
- if (lastHeight >= maxHeight && length && length < self.value.length && width === lastWidth) {
- return true;
- }
-
- let val = self.value.replace(//g, '>')
- .replace(/&/g, '&')
- .replace(/\n$/, ' ')
- .replace(/\n/g, ' ')
- .replace(/ {2,}/g, runTimes);
-
- // Did enter get pressed? Resize in this keydown event so that the flicker doesn't occur.
- if (event && event.data && event.data.event === 'keydown' && event.keyCode === 13 && (event.shiftKey || event.ctrlKey || event.altKey)) {
- val += ' ';
- }
-
- if (width !== lastWidth) {
- shadow.css('width', width);
- lastWidth = width;
- }
-
- shadow[0].innerHTML = val;
-
- let newHeight = Math.max(shadow[0].clientHeight + 1, minHeight) + 1;
-
- let overflow = 'hidden';
-
- if (newHeight >= maxHeight) {
- newHeight = maxHeight;
- overflow = '';
- } else {
- length = self.value.length;
- }
-
- if (newHeight === lastHeight) {
- return true;
- }
-
- lastHeight = newHeight;
-
- $self.css({ overflow, height: newHeight });
-
- trigger();
-
- if (settings.postGrowCallback !== null) {
- settings.postGrowCallback($self);
- }
- };
-
- const updateThrottled = _.throttle(update, 300);
-
- $self.on('change input', updateThrottled);
- $self.on('focus', update);
- $(window).resize(updateThrottled);
- update();
- self.updateAutogrow = updateThrottled;
- });
- };
-}(jQuery));
+$.fn.autogrow = function({ postGrowCallback } = {}) {
+ const shadow = getShadow();
+
+ return this.filter('textarea').each((i, textarea) => {
+ const $textarea = $(textarea);
+
+ const trigger = _.debounce(() => $textarea.trigger('autogrow', []), 500);
+
+ const width = $textarea.width();
+ const minHeight = $textarea.height();
+ const maxHeight = window.getComputedStyle(textarea)['max-height'].replace('px', '');
+
+ let lastWidth = width;
+ let lastHeight = minHeight;
+ let length = 0;
+
+ shadow.style.position = 'absolute';
+ shadow.style.top = '-10000px';
+ shadow.style.left = '-10000px';
+ shadow.style.width = `${ width }px`;
+ shadow.style.fontSize = $textarea.css('fontSize');
+ shadow.style.fontFamily = $textarea.css('fontFamily');
+ shadow.style.fontWeight = $textarea.css('fontWeight');
+ shadow.style.lineHeight = `${ $textarea.css('lineHeight') }px`;
+ shadow.style.resize = 'none';
+ shadow.style.wordWrap = 'break-word';
+
+ const update = (event) => {
+ const width = $textarea.width();
+ if (lastHeight >= maxHeight && length && length < textarea.value.length && width === lastWidth) {
+ return true;
+ }
+
+ let val = textarea.value.replace(//g, '>')
+ .replace(/&/g, '&')
+ .replace(/\n$/, ' ')
+ .replace(/\n/g, ' ')
+ .replace(/ {2,}/g, replaceWhitespaces);
+
+ // Did enter get pressed? Resize in this keydown event so that the flicker doesn't occur.
+ if (event && event.data && event.data.event === 'keydown' && event.keyCode === 13 && (event.shiftKey || event.ctrlKey || event.altKey)) {
+ val += ' ';
+ }
+
+ if (width !== lastWidth) {
+ shadow.style.width = `${ width }px`;
+ lastWidth = width;
+ }
+
+ shadow.innerHTML = val;
+
+ let newHeight = Math.max(shadow.clientHeight + 1, minHeight) + 1;
+
+ let overflow = 'hidden';
+
+ if (newHeight >= maxHeight) {
+ newHeight = maxHeight;
+ overflow = '';
+ } else {
+ length = textarea.value.length;
+ }
+
+ if (newHeight === lastHeight) {
+ return true;
+ }
+
+ lastHeight = newHeight;
+
+ $textarea.css({ overflow, height: newHeight });
+
+ trigger();
+
+ postGrowCallback && postGrowCallback($textarea);
+ };
+
+ const updateThrottled = _.throttle(update, 300);
+
+ $textarea.on('change input', updateThrottled);
+ $textarea.on('focus', update);
+ $(window).resize(updateThrottled);
+ update();
+ textarea.updateAutogrow = update;
+ });
+};
diff --git a/app/ui/client/lib/textarea-cursor.js b/app/ui/client/lib/textarea-cursor.js
new file mode 100644
index 000000000000..dd89a1079705
--- /dev/null
+++ b/app/ui/client/lib/textarea-cursor.js
@@ -0,0 +1,19 @@
+// http://stackoverflow.com/a/499158
+
+
+$.fn.setCursorPosition = function(pos) {
+ this.each((index, element) => {
+ const p = pos < 0 ? element.value.length - pos : pos;
+ if (element.setSelectionRange) {
+ element.setSelectionRange(p, p);
+ } else if (element.createTextRange) {
+ const range = element.createTextRange();
+ range.collapse(true);
+ range.moveEnd('character', p);
+ range.moveStart('character', p);
+ range.select();
+ }
+ });
+
+ return this;
+};
diff --git a/app/ui/client/lib/textarea-cursor/set-cursor-position.js b/app/ui/client/lib/textarea-cursor/set-cursor-position.js
deleted file mode 100644
index 4657ac1da54b..000000000000
--- a/app/ui/client/lib/textarea-cursor/set-cursor-position.js
+++ /dev/null
@@ -1,17 +0,0 @@
-// Adapted from http://stackoverflow.com/a/499158
-
-$.fn.setCursorPosition = function(pos) {
- this.each(function(index, elem) {
- const p = pos < 0 ? elem.value.length - pos : pos;
- if (elem.setSelectionRange) {
- elem.setSelectionRange(p, p);
- } else if (elem.createTextRange) {
- const range = elem.createTextRange();
- range.collapse(true);
- range.moveEnd('character', p);
- range.moveStart('character', p);
- range.select();
- }
- });
- return this;
-};
diff --git a/app/ui/client/views/app/room.html b/app/ui/client/views/app/room.html
index 78637c20541f..772cfb0a55f0 100644
--- a/app/ui/client/views/app/room.html
+++ b/app/ui/client/views/app/room.html
@@ -131,7 +131,11 @@
{{/if}}
{{/if}}
- {{#each messagesHistory}}{{#nrr nrrargs 'message' .}}{{/nrr}}{{/each}}
+
+ {{# with messageContext}}
+ {{#each msg in messagesHistory}}{{#nrr nrrargs 'message' msg=msg room=room subscription=subscription settings=settings u=u}}{{/nrr}}{{/each}}
+ {{/with}}
+
{{#if hasMoreNext}}
{{#if isLoading}}
diff --git a/app/ui/client/views/app/room.js b/app/ui/client/views/app/room.js
index 69f739323705..8a3fb449b5dd 100644
--- a/app/ui/client/views/app/room.js
+++ b/app/ui/client/views/app/room.js
@@ -1,14 +1,13 @@
import { Meteor } from 'meteor/meteor';
import { ReactiveVar } from 'meteor/reactive-var';
import { Random } from 'meteor/random';
-import { Tracker } from 'meteor/tracker';
import { Blaze } from 'meteor/blaze';
import { FlowRouter } from 'meteor/kadira:flow-router';
import { Session } from 'meteor/session';
import { Template } from 'meteor/templating';
import { t, roomTypes, getUserPreference, handleError } from '../../../../utils';
import { WebRTC } from '../../../../webrtc/client';
-import { ChatSubscription, ChatMessage, RoomRoles, Users, Subscriptions, Rooms } from '../../../../models';
+import { ChatMessage, RoomRoles, Users, Subscriptions, Rooms } from '../../../../models';
import {
fireGlobalEvent,
RoomHistoryManager,
@@ -20,6 +19,9 @@ import {
MessageAction,
RocketChatTabBar,
} from '../../../../ui-utils';
+import { messageContext } from '../../../../ui-utils/client/lib/messageContext';
+import { messageArgs } from '../../../../ui-utils/client/lib/messageArgs';
+import { call } from '../../../../ui-utils/client/lib/callMethod';
import { settings } from '../../../../settings';
import { callbacks } from '../../../../callbacks';
import { promises } from '../../../../promises/client';
@@ -31,16 +33,16 @@ import Clipboard from 'clipboard';
import { lazyloadtick } from '../../../../lazy-load';
import { ChatMessages } from '../../lib/chatMessages';
import { fileUpload } from '../../lib/fileUpload';
+import { isURL } from '../../../../utils/lib/isURL';
export const chatMessages = {};
-const isSubscribed = (_id) => ChatSubscription.find({ rid: _id }).count() > 0;
const favoritesEnabled = () => settings.get('Favorite_Rooms');
-const userCanDrop = (_id) => !roomTypes.readOnly(_id, Meteor.user());
+const userCanDrop = (_id) => !roomTypes.readOnly(_id, Users.findOne({ _id: Meteor.userId() }, { fields: { username: 1 } }));
const openProfileTab = (e, instance, username) => {
- const roomData = Session.get(`roomData${ Session.get('openedRoom') }`);
+ const roomData = Session.get(`roomData${ RoomManager.openedRoom }`);
if (Layout.isEmbedded()) {
fireGlobalEvent('click-user-card-message', { username });
@@ -68,7 +70,7 @@ const openProfileTabOrOpenDM = (e, instance, username) => {
}
}
- if ((result != null ? result.rid : undefined) != null) {
+ if (result && result.rid) {
FlowRouter.go('direct', { username }, FlowRouter.current().queryParams);
}
});
@@ -84,7 +86,7 @@ const mountPopover = (e, i, outerContext) => {
context = 'message';
}
- const [, message] = outerContext._arguments;
+ const { msg: message } = messageArgs(outerContext);
let menuItems = MessageAction.getButtons(message, context, 'menu').map((item) => ({
icon: item.icon,
@@ -216,8 +218,8 @@ callbacks.add('enter-room', wipeFailedUploads);
Template.room.helpers({
isTranslated() {
- const sub = ChatSubscription.findOne({ rid: this._id }, { fields: { autoTranslate: 1, autoTranslateLanguage: 1 } });
- settings.get('AutoTranslate_Enabled') && ((sub != null ? sub.autoTranslate : undefined) === true) && (sub.autoTranslateLanguage != null);
+ const sub = Template.instance().subscription;
+ return settings.get('AutoTranslate_Enabled') && ((sub != null ? sub.autoTranslate : undefined) === true) && (sub.autoTranslateLanguage != null);
},
embeddedVersion() {
@@ -225,10 +227,11 @@ Template.room.helpers({
},
subscribed() {
- return isSubscribed(this._id);
+ return Template.instance().subscription;
},
messagesHistory() {
+ const { rid } = Template.instance();
const hideMessagesOfType = [];
settings.collection.find({ _id: /Message_HideType_.+/ }).forEach(function(record) {
let types;
@@ -251,8 +254,7 @@ Template.room.helpers({
});
});
- const query =
- { rid: this._id };
+ const query = { rid };
if (hideMessagesOfType.length > 0) {
query.t =
@@ -306,40 +308,53 @@ Template.room.helpers({
},
showAnnouncement() {
- const roomData = Session.get(`roomData${ this._id }`);
- if (!roomData) { return false; }
- return roomData.announcement != null && roomData.announcement !== '';
+ const { room } = Template.instance();
+ if (!room) { return false; }
+ return room.announcement != null && room.announcement !== '';
},
messageboxData() {
- const instance = Template.instance();
+ const { sendToBottomIfNecessaryDebounced, subscription } = Template.instance();
+ const { _id: rid } = this;
+ const isEmbedded = Layout.isEmbedded();
+ const showFormattingTips = settings.get('Message_ShowFormattingTips');
+
return {
- _id: this._id,
- onResize: () => {
- if (instance.sendToBottomIfNecessaryDebounced) {
- instance.sendToBottomIfNecessaryDebounced();
+ rid,
+ subscription,
+ isEmbedded,
+ showFormattingTips: showFormattingTips && !isEmbedded,
+ onInputChanged: (input) => {
+ if (!chatMessages[rid]) {
+ return;
}
+
+ chatMessages[rid].initializeInput(input, { rid });
},
+ onResize: () => sendToBottomIfNecessaryDebounced && sendToBottomIfNecessaryDebounced(),
+ onKeyUp: (...args) => chatMessages[rid] && chatMessages[rid].keyup.apply(chatMessages[rid], args),
+ onKeyDown: (...args) => chatMessages[rid] && chatMessages[rid].keydown.apply(chatMessages[rid], args),
+ onSend: (...args) => chatMessages[rid] && chatMessages[rid].send.apply(chatMessages[rid], args),
};
},
roomAnnouncement() {
- const roomData = Session.get(`roomData${ this._id }`);
- if (!roomData) { return ''; }
- return roomData.announcement;
+ const { room } = Template.instance();
+ if (!room) { return ''; }
+ return room.announcement;
},
getAnnouncementStyle() {
- const roomData = Session.get(`roomData${ this._id }`);
- if (!roomData) { return ''; }
- return roomData.announcementDetails && roomData.announcementDetails.style !== undefined ? roomData.announcementDetails.style : '';
+ const { room } = Template.instance();
+ if (!room) { return ''; }
+ return room.announcementDetails && room.announcementDetails.style !== undefined ? room.announcementDetails.style : '';
},
roomIcon() {
- const roomData = Session.get(`roomData${ this._id }`);
- if (!(roomData != null ? roomData.t : undefined)) { return ''; }
+ const { room } = Template.instance();
+ if (!(room != null ? room.t : undefined)) { return ''; }
- const roomIcon = roomTypes.getIcon(roomData);
+ const roomIcon = roomTypes.getIcon(room);
// Remove this 'codegueira' on header redesign
if (!roomIcon) {
@@ -350,8 +365,8 @@ Template.room.helpers({
},
userStatus() {
- const roomData = Session.get(`roomData${ this._id }`);
- return roomTypes.getUserStatus(roomData.t, this._id) || 'offline';
+ const { room } = Template.instance();
+ return roomTypes.getUserStatus(room.t, this._id) || 'offline';
},
maxMessageLength() {
@@ -363,8 +378,8 @@ Template.room.helpers({
{ count: RoomHistoryManager.getRoom(this._id).unreadNotLoaded.get() + Template.instance().unreadCount.get() };
const room = RoomManager.getOpenedRoomByRid(this._id);
- if (room != null) {
- data.since = room.unreadSince != null ? room.unreadSince.get() : undefined;
+ if (room) {
+ data.since = room.unreadSince ? room.unreadSince.get() : undefined;
}
return data;
@@ -380,7 +395,9 @@ Template.room.helpers({
},
formatUnreadSince() {
- if ((this.since == null)) { return; }
+ if (!this.since) {
+ return;
+ }
return moment(this.since).calendar(null, { sameDay: 'LT' });
},
@@ -403,7 +420,8 @@ Template.room.helpers({
},
showToggleFavorite() {
- if (isSubscribed(this._id) && favoritesEnabled()) { return true; }
+ const { subscription } = Template.instace();
+ return subscription && favoritesEnabled();
},
messageViewMode() {
@@ -438,7 +456,8 @@ Template.room.helpers({
},
canPreview() {
- const room = Session.get(`roomData${ this._id }`);
+ const { room, subscription } = Template.instance();
+
if (room && room.t !== 'c') {
return true;
}
@@ -451,8 +470,7 @@ Template.room.helpers({
return true;
}
- return (Subscriptions.findOne({ rid: this._id }) != null);
-
+ return !subscription;
},
hideLeaderHeader() {
return Template.instance().hideLeaderHeader.get() ? 'animated-hidden' : '';
@@ -463,15 +481,19 @@ Template.room.helpers({
}
},
hasPurge() {
- return roomHasPurge(Session.get(`roomData${ this._id }`));
+ const { room } = Template.instance();
+ return roomHasPurge(room);
},
filesOnly() {
- return roomFilesOnly(Session.get(`roomData${ this._id }`));
+ const { room } = Template.instance();
+ return roomFilesOnly(room);
},
excludePinned() {
- return roomExcludePinned(Session.get(`roomData${ this._id }`));
+ const { room } = Template.instance();
+ return roomExcludePinned(room);
},
purgeTimeout() {
+ const { room } = Template.instance();
moment.relativeTimeThreshold('s', 60);
moment.relativeTimeThreshold('ss', 0);
moment.relativeTimeThreshold('m', 60);
@@ -479,8 +501,9 @@ Template.room.helpers({
moment.relativeTimeThreshold('d', 31);
moment.relativeTimeThreshold('M', 12);
- return moment.duration(roomMaxAge(Session.get(`roomData${ this._id }`)) * 1000 * 60 * 60 * 24).humanize();
+ return moment.duration(roomMaxAge(room) * 1000 * 60 * 60 * 24).humanize();
},
+ messageContext,
});
let isSocialSharingOpen = false;
@@ -490,12 +513,29 @@ let lastTouchY = null;
let lastScrollTop;
Template.room.events({
+ 'click .js-open-thread'() {
+ const { tabBar } = Template.instance();
+
+ const { msg, msg: { rid, _id, tmid } } = messageArgs(this);
+ const $flexTab = $('.flex-tab-container .flex-tab');
+ $flexTab.attr('template', 'thread');
+
+ tabBar.setData({
+ msg,
+ rid,
+ mid: tmid || _id,
+ label: 'Threads',
+ icon: 'thread',
+ });
+
+ tabBar.open('thread');
+ },
'click .js-reply-broadcast'() {
- const message = this._arguments[1];
- roomTypes.openRouteLink('d', { name: this._arguments[1].u.username }, { ...FlowRouter.current().queryParams, reply: message._id });
+ const { msg } = messageArgs(this);
+ roomTypes.openRouteLink('d', { name: msg.u.username }, { ...FlowRouter.current().queryParams, reply: msg._id });
},
'click, touchend'(e, t) {
- Meteor.setTimeout(() => t.sendToBottomIfNecessaryDebounced && t.sendToBottomIfNecessaryDebounced(), 100);
+ setTimeout(() => t.sendToBottomIfNecessaryDebounced && t.sendToBottomIfNecessaryDebounced(), 100);
},
'click .messages-container-main'() {
@@ -524,22 +564,21 @@ Template.room.events({
return;
}
- if (e.target && (e.target.nodeName === 'A') && /^https?:\/\/.+/.test(e.target.getAttribute('href'))) {
+ if (e.target && (e.target.nodeName === 'A') && isURL(e.target.getAttribute('href'))) {
e.preventDefault();
e.stopPropagation();
}
const doLongTouch = () => {
- console.log('long press');
mountPopover(e, t, this);
};
- Meteor.clearTimeout(t.touchtime);
- t.touchtime = Meteor.setTimeout(doLongTouch, 500);
+ clearTimeout(t.touchtime);
+ t.touchtime = setTimeout(doLongTouch, 500);
},
'click .message img'(e, t) {
- Meteor.clearTimeout(t.touchtime);
+ clearTimeout(t.touchtime);
if ((isSocialSharingOpen === true) || (touchMoved === true)) {
e.preventDefault();
e.stopPropagation();
@@ -547,14 +586,14 @@ Template.room.events({
},
'touchend .message'(e, t) {
- Meteor.clearTimeout(t.touchtime);
+ clearTimeout(t.touchtime);
if (isSocialSharingOpen === true) {
e.preventDefault();
e.stopPropagation();
return;
}
- if (e.target && (e.target.nodeName === 'A') && /^https?:\/\/.+/.test(e.target.getAttribute('href'))) {
+ if (e.target && (e.target.nodeName === 'A') && isURL(e.target.getAttribute('href'))) {
if (touchMoved === true) {
e.preventDefault();
e.stopPropagation();
@@ -574,11 +613,11 @@ Template.room.events({
touchMoved = true;
}
}
- Meteor.clearTimeout(t.touchtime);
+ clearTimeout(t.touchtime);
},
'touchcancel .message'(e, t) {
- Meteor.clearTimeout(t.touchtime);
+ clearTimeout(t.touchtime);
},
'click .upload-progress-close'(e) {
@@ -597,7 +636,7 @@ Template.room.events({
if (message) {
RoomHistoryManager.getSurroundingMessages(message, 50);
} else {
- const subscription = ChatSubscription.findOne({ rid: _id });
+ const subscription = Subscriptions.findOne({ rid: _id });
message = ChatMessage.find({ rid: _id, ts: { $gt: (subscription != null ? subscription.ls : undefined) } }, { sort: { ts: 1 }, limit: 1 }).fetch()[0];
RoomHistoryManager.getSurroundingMessages(message, 50);
}
@@ -613,13 +652,6 @@ Template.room.events({
});
},
- 'click .edit-room-title'(event) {
- event.preventDefault();
- Session.set('editRoomTitle', true);
- $('.fixed-title').addClass('visible');
- Meteor.setTimeout(() => $('#room-title-field').focus().select(), 10);
- },
-
'click .user-image, click .rc-member-list__user'(e, instance) {
if (!Meteor.userId()) {
return;
@@ -629,11 +661,12 @@ Template.room.events({
},
'click .user-card-message'(e, instance) {
- if (!Meteor.userId() || !this._arguments) {
+ const { msg } = messageArgs(this);
+ if (!Meteor.userId()) {
return;
}
- const { username } = this._arguments[1].u;
+ const { username } = msg.u;
openProfileTabOrOpenDM(e, instance, username);
},
@@ -645,36 +678,35 @@ Template.room.events({
if ($roomLeader.length) {
if (e.target.scrollTop < lastScrollTop) {
t.hideLeaderHeader.set(false);
- } else if (t.isAtBottom(100) === false && e.target.scrollTop > $('.room-leader').height()) {
+ } else if (t.isAtBottom(100) === false && e.target.scrollTop > $roomLeader.height()) {
t.hideLeaderHeader.set(true);
}
}
lastScrollTop = e.target.scrollTop;
-
+ const height = e.target.clientHeight;
const isLoading = RoomHistoryManager.isLoading(this._id);
const hasMore = RoomHistoryManager.hasMore(this._id);
const hasMoreNext = RoomHistoryManager.hasMoreNext(this._id);
if ((isLoading === false && hasMore === true) || hasMoreNext === true) {
- if (hasMore === true && e.target.scrollTop === 0) {
+ if (hasMore === true && lastScrollTop <= height / 3) {
RoomHistoryManager.getMore(this._id);
- } else if (hasMoreNext === true && Math.ceil(e.target.scrollTop) >= e.target.scrollHeight - e.target.clientHeight) {
+ } else if (hasMoreNext === true && Math.ceil(lastScrollTop) >= e.target.scrollHeight - height) {
RoomHistoryManager.getMoreNext(this._id);
}
}
- }, 200),
+ }, 500),
- 'click .new-message'() {
- Template.instance().atBottom = true;
+ 'click .new-message'(event, instance) {
+ instance.atBottom = true;
chatMessages[RoomManager.openedRoom].input.focus();
},
'click .message-actions__menu'(e, i) {
- let context = $(e.target).parents('.message').data('context');
- if (!context) {
- context = 'message';
- }
- const [, message] = this._arguments;
+ const { msg: message, context: ctx } = messageArgs(this);
+
+ const context = ctx || message.actionContext || 'message';
+
const allItems = MessageAction.getButtons(message, context, 'menu').map((item) => ({
icon: item.icon,
name: t(item.label),
@@ -706,7 +738,8 @@ Template.room.events({
},
'click .time a'(e) {
e.preventDefault();
- const repliedMessageId = this._arguments[1].attachments[0].message_link.split('?msg=')[1];
+ const { msg } = messageArgs(this);
+ const repliedMessageId = msg.attachments[0].message_link.split('?msg=')[1];
FlowRouter.go(FlowRouter.current().context.pathname, null, { msg: repliedMessageId, hash: Random.id() });
},
'click .mention-link'(e, instance) {
@@ -727,20 +760,22 @@ Template.room.events({
},
'click .image-to-download'(event) {
- ChatMessage.update({ _id: this._arguments[1]._id, 'urls.url': $(event.currentTarget).data('url') }, { $set: { 'urls.$.downloadImages': true } });
- ChatMessage.update({ _id: this._arguments[1]._id, 'attachments.image_url': $(event.currentTarget).data('url') }, { $set: { 'attachments.$.downloadImages': true } });
+ const { msg } = messageArgs(this);
+ ChatMessage.update({ _id: msg._id, 'urls.url': $(event.currentTarget).data('url') }, { $set: { 'urls.$.downloadImages': true } });
+ ChatMessage.update({ _id: msg._id, 'attachments.image_url': $(event.currentTarget).data('url') }, { $set: { 'attachments.$.downloadImages': true } });
},
'click .collapse-switch'(e) {
+ const { msg } = messageArgs(this);
const index = $(e.currentTarget).data('index');
const collapsed = $(e.currentTarget).data('collapsed');
- const id = this._arguments[1]._id;
+ const id = msg._id;
- if ((this._arguments[1] != null ? this._arguments[1].attachments : undefined) != null) {
+ if ((msg != null ? msg.attachments : undefined) != null) {
ChatMessage.update({ _id: id }, { $set: { [`attachments.${ index }.collapsed`]: !collapsed } });
}
- if ((this._arguments[1] != null ? this._arguments[1].urls : undefined) != null) {
+ if ((msg != null ? msg.urls : undefined) != null) {
ChatMessage.update({ _id: id }, { $set: { [`urls.${ index }.collapsed`]: !collapsed } });
}
},
@@ -781,9 +816,7 @@ Template.room.events({
});
}
- const { input } = chatMessages[RoomManager.openedRoom];
-
- fileUpload(filesToUpload, input);
+ fileUpload(filesToUpload, chatMessages[RoomManager.openedRoom].input, { rid: RoomManager.openedRoom });
},
'load img'(e, template) {
@@ -800,7 +833,7 @@ Template.room.events({
if (template.selectable.get()) {
(document.selection != null ? document.selection.empty() : undefined) || (typeof window.getSelection === 'function' ? window.getSelection().removeAllRanges() : undefined);
const data = Blaze.getData(e.currentTarget);
- const _id = data && data._arguments && data._arguments[1] && data._arguments[1]._id;
+ const { msg: { _id } } = messageArgs(data);
if (!template.selectablePointer) {
template.selectablePointer = _id;
@@ -829,7 +862,8 @@ Template.room.events({
} else {
modal.open({
title: t('Announcement'),
- text: roomData.announcement,
+ text: callbacks.run('renderMessage', { html: roomData.announcement }).html,
+ html: true,
showConfirmButton: false,
showCancelButton: true,
cancelButtonText: t('Close'),
@@ -840,35 +874,37 @@ Template.room.events({
const id = e.currentTarget.dataset.message;
document.querySelector(`#${ id }`).classList.toggle('message--ignored');
},
- 'click .js-actionButton-sendMessage'(event, instance) {
+ async 'click .js-actionButton-sendMessage'(event, instance) {
const rid = instance.data._id;
const msg = event.currentTarget.value;
- const msgObject = { _id: Random.id(), rid, msg };
+ let msgObject = { _id: Random.id(), rid, msg };
if (!msg) {
return;
}
- promises.run('onClientBeforeSendMessage', msgObject).then((msgObject) => {
- const _chatMessages = chatMessages[rid];
- if (_chatMessages && _chatMessages.processSlashCommand(msgObject)) {
- return;
- }
- Meteor.call('sendMessage', msgObject);
- });
+ msgObject = await promises.run('onClientBeforeSendMessage', msgObject);
+
+ const _chatMessages = chatMessages[rid];
+ if (_chatMessages && await _chatMessages.processSlashCommand(msgObject)) {
+ return;
+ }
+
+ await call('sendMessage', msgObject);
},
- 'click .js-actionButton-respondWithMessage'(event) {
+ 'click .js-actionButton-respondWithMessage'(event, instance) {
+ const rid = instance.data._id;
const msg = event.currentTarget.value;
if (!msg) {
return;
}
- const { input } = chatMessages[RoomManager.openedRoom];
+ const { input } = chatMessages[rid];
input.value = msg;
input.focus();
},
'click .js-navigate-to-discussion'(event) {
event.preventDefault();
- const [, { drid }] = this._arguments;
+ const { msg: { drid } } = messageArgs(this);
FlowRouter.goToRoomById(drid);
},
});
@@ -877,8 +913,11 @@ Template.room.events({
Template.room.onCreated(function() {
// this.scrollOnBottom = true
// this.typing = new msgTyping this.data._id
-
lazyloadtick();
+ const rid = this.data._id;
+ this.rid = rid;
+ this.subscription = Subscriptions.findOne({ rid });
+ this.room = Rooms.findOne({ _id: rid });
this.showUsersOffline = new ReactiveVar(false);
this.atBottom = !FlowRouter.getQueryParam('msg');
@@ -958,7 +997,8 @@ Template.room.onCreated(function() {
RoomRoles.upsert({ rid: record.rid, 'u._id': record.u._id }, record);
});
});
- RoomRoles.find({ rid: this.data._id }).observe({
+
+ this.rolesObserve = RoomRoles.find({ rid: this.data._id }).observe({
added: (role) => {
if (!role.u || !role.u._id) {
return;
@@ -986,17 +1026,23 @@ Template.room.onDestroyed(function() {
if (this.messageObserver) {
this.messageObserver.stop();
}
+ if (this.rolesObserve) {
+ this.rolesObserve.stop();
+ }
+
+ readMessage.off(this.data._id);
+
window.removeEventListener('resize', this.onWindowResize);
});
Template.room.onRendered(function() {
- // $(this.find('.messages-box .wrapper')).perfectScrollbar();
- const rid = Session.get('openedRoom');
+ const { _id: rid } = this.data;
+
if (!chatMessages[rid]) {
chatMessages[rid] = new ChatMessages;
}
- chatMessages[rid].init(this.firstNode);
- // ScrollListener.init()
+ chatMessages[rid].initializeWrapper(this.find('.wrapper'));
+ chatMessages[rid].initializeInput(this.find('.js-input-message'), { rid });
const wrapper = this.find('.wrapper');
const wrapperUl = this.find('.wrapper > ul');
@@ -1037,12 +1083,12 @@ Template.room.onRendered(function() {
template.sendToBottomIfNecessary();
- if ((window.MutationObserver == null)) {
- wrapperUl.addEventListener('DOMSubtreeModified', () => template.sendToBottomIfNecessaryDebounced());
- } else {
+ if (window.MutationObserver) {
const observer = new MutationObserver(() => template.sendToBottomIfNecessaryDebounced());
observer.observe(wrapperUl, { childList: true });
+ } else {
+ wrapperUl.addEventListener('DOMSubtreeModified', () => template.sendToBottomIfNecessaryDebounced());
}
// observer.disconnect()
@@ -1065,18 +1111,18 @@ Template.room.onRendered(function() {
wrapper.addEventListener('touchstart', () => template.atBottom = false);
wrapper.addEventListener('touchend', function() {
- Meteor.defer(() => template.checkIfScrollIsAtBottom());
- Meteor.setTimeout(() => template.checkIfScrollIsAtBottom(), 1000);
- Meteor.setTimeout(() => template.checkIfScrollIsAtBottom(), 2000);
+ template.checkIfScrollIsAtBottom();
+ setTimeout(() => template.checkIfScrollIsAtBottom(), 1000);
+ setTimeout(() => template.checkIfScrollIsAtBottom(), 2000);
});
wrapper.addEventListener('scroll', function() {
template.atBottom = false;
- Meteor.defer(() => template.checkIfScrollIsAtBottom());
+ template.checkIfScrollIsAtBottom();
});
$('.flex-tab-bar').on('click', (/* e, t*/) =>
- Meteor.setTimeout(() => template.sendToBottomIfNecessaryDebounced(), 50)
+ setTimeout(() => template.sendToBottomIfNecessaryDebounced(), 50)
);
lastScrollTop = $('.messages-box .wrapper').scrollTop();
@@ -1100,24 +1146,22 @@ Template.room.onRendered(function() {
const updateUnreadCount = _.throttle(function() {
const lastInvisibleMessageOnScreen = getElementFromPoint(0) || getElementFromPoint(20) || getElementFromPoint(40);
- if (lastInvisibleMessageOnScreen == null || lastInvisibleMessageOnScreen.id == null) {
+ if (!lastInvisibleMessageOnScreen || !lastInvisibleMessageOnScreen.id) {
return template.unreadCount.set(0);
}
const lastMessage = ChatMessage.findOne(lastInvisibleMessageOnScreen.id);
- if (lastMessage == null) {
+ if (!lastMessage) {
return template.unreadCount.set(0);
}
- const subscription = ChatSubscription.findOne({ rid: template.data._id }, { reactive: false });
+ const subscription = Subscriptions.findOne({ rid: template.data._id }, { reactive: false });
const count = ChatMessage.find({ rid: template.data._id, ts: { $lte: lastMessage.ts, $gt: subscription && subscription.ls } }).count();
template.unreadCount.set(count);
}, 300);
- readMessage.onRead(function(rid) {
- if (rid === template.data._id) {
- template.unreadCount.set(0);
- }
+ readMessage.on(template.data._id, () => {
+ template.unreadCount.set(0);
});
wrapper.addEventListener('scroll', () => updateUnreadCount());
@@ -1125,15 +1169,15 @@ Template.room.onRendered(function() {
$.data(this.firstNode, 'renderedAt', new Date);
const webrtc = WebRTC.getInstanceByRoomId(template.data._id);
- if (webrtc != null) {
- Tracker.autorun(() => {
+ if (webrtc) {
+ this.autorun(() => {
const remoteItems = webrtc.remoteItems.get();
if (remoteItems && remoteItems.length > 0) {
this.tabBar.setTemplate('membersList');
this.tabBar.open();
}
- if (webrtc.localUrl.get() != null) {
+ if (webrtc.localUrl.get()) {
this.tabBar.setTemplate('membersList');
this.tabBar.open();
}
diff --git a/app/utils/client/index.js b/app/utils/client/index.js
index f4874061faa0..4c06e111e219 100644
--- a/app/utils/client/index.js
+++ b/app/utils/client/index.js
@@ -19,3 +19,4 @@ export { getValidRoomName } from '../lib/getValidRoomName';
export { placeholders } from '../lib/placeholders';
export { templateVarHandler } from '../lib/templateVarHandler';
export { APIClient } from './lib/RestApiClient';
+export { canDeleteMessage } from './lib/canDeleteMessage';
diff --git a/app/utils/client/lib/RestApiClient.js b/app/utils/client/lib/RestApiClient.js
index de803712d691..43dcb68041fc 100644
--- a/app/utils/client/lib/RestApiClient.js
+++ b/app/utils/client/lib/RestApiClient.js
@@ -1,3 +1,5 @@
+import { Accounts } from 'meteor/accounts-base';
+
export const APIClient = {
delete(endpoint, params) {
return APIClient._jqueryCall('DELETE', endpoint, params);
@@ -47,8 +49,8 @@ export const APIClient = {
url: `${ document.baseURI }api/${ endpoint }${ query }`,
headers: {
'Content-Type': 'application/json',
- 'X-User-Id': localStorage['Meteor.userId'],
- 'X-Auth-Token': localStorage['Meteor.loginToken'],
+ 'X-User-Id': localStorage[Accounts.USER_ID_KEY],
+ 'X-Auth-Token': localStorage[Accounts.LOGIN_TOKEN_KEY],
},
data: JSON.stringify(body),
success: function _rlGetSuccess(result) {
@@ -74,8 +76,8 @@ export const APIClient = {
jQuery.ajax({
url: `${ document.baseURI }api/${ endpoint }${ query }`,
headers: {
- 'X-User-Id': localStorage['Meteor.userId'],
- 'X-Auth-Token': localStorage['Meteor.loginToken'],
+ 'X-User-Id': localStorage[Accounts.USER_ID_KEY],
+ 'X-Auth-Token': localStorage[Accounts.LOGIN_TOKEN_KEY],
},
data: formData,
processData: false,
diff --git a/app/utils/client/lib/canDeleteMessage.js b/app/utils/client/lib/canDeleteMessage.js
new file mode 100644
index 000000000000..9270d46a97a9
--- /dev/null
+++ b/app/utils/client/lib/canDeleteMessage.js
@@ -0,0 +1,42 @@
+import { Meteor } from 'meteor/meteor';
+import moment from 'moment';
+
+import { hasAtLeastOnePermission } from '../../../authorization/client';
+import { settings } from '../../../settings/client';
+
+export const canDeleteMessage = ({ rid, ts, uid }) => {
+ const userId = Meteor.userId();
+
+ const forceDelete = hasAtLeastOnePermission('force-delete-message', rid);
+ if (forceDelete) {
+ return true;
+ }
+
+ const isDeleteAllowed = settings.get('Message_AllowDeleting');
+ if (!isDeleteAllowed) {
+ return false;
+ }
+
+ const hasPermission = hasAtLeastOnePermission('delete-message', rid);
+ const deleteOwn = uid === userId;
+ if (!hasPermission && !deleteOwn) {
+ return false;
+ }
+
+ const blockDeleteInMinutes = settings.get('Message_AllowDeleting_BlockDeleteInMinutes');
+ if (blockDeleteInMinutes != null && blockDeleteInMinutes !== 0) {
+ let msgTs;
+ if (ts != null) {
+ msgTs = moment(ts);
+ }
+ let currentTsDiff;
+ if (msgTs != null) {
+ currentTsDiff = moment().diff(msgTs, 'minutes');
+ }
+ return currentTsDiff < blockDeleteInMinutes;
+ }
+
+ return true;
+};
+
+
diff --git a/app/utils/client/lib/roomTypes.js b/app/utils/client/lib/roomTypes.js
index 0237a0a66cad..f0ef0325ab60 100644
--- a/app/utils/client/lib/roomTypes.js
+++ b/app/utils/client/lib/roomTypes.js
@@ -38,7 +38,7 @@ export const roomTypes = new class RocketChatRoomTypes extends RoomTypesCommon {
rid: roomId,
}).count() > 0;
}
- readOnly(roomId, user) {
+ readOnly(rid, user) {
const fields = {
ro: 1,
};
@@ -46,7 +46,7 @@ export const roomTypes = new class RocketChatRoomTypes extends RoomTypesCommon {
fields.muted = 1;
}
const room = ChatRoom.findOne({
- _id: roomId,
+ _id: rid,
}, {
fields,
});
@@ -54,7 +54,7 @@ export const roomTypes = new class RocketChatRoomTypes extends RoomTypesCommon {
return room && room.ro;
}
const userOwner = RoomRoles.findOne({
- rid: roomId,
+ rid,
'u._id': user._id,
roles: 'owner',
}, {
diff --git a/app/utils/lib/RoomTypeConfig.js b/app/utils/lib/RoomTypeConfig.js
index f315a2a1941d..1c29a5b1d22b 100644
--- a/app/utils/lib/RoomTypeConfig.js
+++ b/app/utils/lib/RoomTypeConfig.js
@@ -1,8 +1,12 @@
import { Meteor } from 'meteor/meteor';
import { Random } from 'meteor/random';
-import { Users } from '../../models';
import { settings } from '../../settings';
+let Users;
+if (Meteor.isServer) {
+ Users = require('../../models/server/models/Users').default;
+}
+
export const RoomSettingsEnum = {
NAME: 'roomName',
TOPIC: 'roomTopic',
@@ -232,7 +236,10 @@ export class RoomTypeConfig {
* @return {object} Sender's object from db
*/
getMsgSender(senderId) {
- return Meteor.isServer ? Users.findOneById(senderId) : {};
+ if (Meteor.isServer && Users) {
+ return Users.findOneById(senderId);
+ }
+ return {};
}
/**
diff --git a/app/utils/lib/RoomTypesCommon.js b/app/utils/lib/RoomTypesCommon.js
index b02476a229e5..438a0b130686 100644
--- a/app/utils/lib/RoomTypesCommon.js
+++ b/app/utils/lib/RoomTypesCommon.js
@@ -60,19 +60,11 @@ export class RoomTypesCommon {
* @param {object} subData the user's subscription data
*/
getRouteLink(roomType, subData) {
- if (!this.roomTypes[roomType]) {
+ const routeData = this.getRouteData(roomType, subData);
+ if (!routeData) {
return false;
}
- let routeData = {};
- if (this.roomTypes[roomType] && this.roomTypes[roomType].route && this.roomTypes[roomType].route.link) {
- routeData = this.roomTypes[roomType].route.link(subData);
- } else if (subData && subData.name) {
- routeData = {
- name: subData.name,
- };
- }
-
return FlowRouter.path(this.roomTypes[roomType].route.name, routeData);
}
@@ -84,11 +76,33 @@ export class RoomTypesCommon {
return this.roomTypes[roomType];
}
- getURL(...args) {
- const path = this.getRouteLink(...args);
- if (!path) {
+ /**
+ * @param {string} roomType room type (e.g.: c (for channels), d (for direct channels))
+ * @param {object} subData the user's subscription data
+ */
+ getURL(roomType, subData) {
+ const routeData = this.getRouteData(roomType, subData);
+ if (!routeData) {
+ return false;
+ }
+
+ return FlowRouter.url(this.roomTypes[roomType].route.name, routeData);
+ }
+
+ getRouteData(roomType, subData) {
+ if (!this.roomTypes[roomType]) {
return false;
}
- return Meteor.absoluteUrl(path.replace(/^\//, ''));
+
+ let routeData = {};
+ if (this.roomTypes[roomType] && this.roomTypes[roomType].route && this.roomTypes[roomType].route.link) {
+ routeData = this.roomTypes[roomType].route.link(subData);
+ } else if (subData && subData.name) {
+ routeData = {
+ name: subData.name,
+ };
+ }
+
+ return routeData;
}
}
diff --git a/app/utils/lib/getURL.js b/app/utils/lib/getURL.js
index 08bfc55a72ea..3ffb8e1c88ad 100644
--- a/app/utils/lib/getURL.js
+++ b/app/utils/lib/getURL.js
@@ -1,8 +1,13 @@
import { Meteor } from 'meteor/meteor';
import { settings } from '../../settings';
import s from 'underscore.string';
+import { isURL } from './isURL';
export const getURL = (path, { cdn = true, full = false } = {}) => {
+ if (isURL(path)) {
+ return path;
+ }
+
const cdnPrefix = s.rtrim(s.trim(settings.get('CDN_PREFIX') || ''), '/');
const pathPrefix = s.rtrim(s.trim(__meteor_runtime_config__.ROOT_URL_PATH_PREFIX || ''), '/');
diff --git a/app/utils/lib/isURL.js b/app/utils/lib/isURL.js
new file mode 100644
index 000000000000..325c34b906c7
--- /dev/null
+++ b/app/utils/lib/isURL.js
@@ -0,0 +1 @@
+export const isURL = (str) => /^https?:\/\//.test(str);
diff --git a/app/utils/server/lib/composeMessageObjectWithUser.js b/app/utils/server/lib/composeMessageObjectWithUser.js
index 3cf88b88ae08..b2332e3096e0 100644
--- a/app/utils/server/lib/composeMessageObjectWithUser.js
+++ b/app/utils/server/lib/composeMessageObjectWithUser.js
@@ -1,22 +1,33 @@
import { Users } from '../../../models';
import { settings } from '../../../settings';
+import memoize from 'mem';
-const getUser = (userId) => Users.findOneById(userId);
+const maxAgeInMS = 1000;
+
+const getUserByUsername = (username) => Users.findOneByUsername(username, { fields: { name: 1 } });
+const getNameOfUser = memoize((username) => {
+ const user = getUserByUsername(username);
+ return user ? user.name : undefined;
+}, { maxAge: maxAgeInMS });
export const composeMessageObjectWithUser = function(message, userId) {
if (message) {
if (message.starred && Array.isArray(message.starred)) {
message.starred = message.starred.filter((star) => star._id === userId);
}
- if (message.u && message.u._id && settings.get('UI_Use_Real_Name')) {
- const user = getUser(message.u._id);
- message.u.name = user && user.name;
- }
- if (message.mentions && message.mentions.length && settings.get('UI_Use_Real_Name')) {
- message.mentions.forEach((mention) => {
- const user = getUser(mention._id);
- mention.name = user && user.name;
- });
+ if (settings.get('UI_Use_Real_Name')) {
+ if (message.u && message.u._id) {
+ message.u.name = getNameOfUser(message.u.username);
+ }
+ if (message.mentions && message.mentions.length) {
+ message.mentions.forEach((mention) => mention.name = getNameOfUser(mention.username));
+ }
+ if (message.reactions && Object.keys(message.reactions).length) {
+ Object.keys(message.reactions).forEach((reaction) => {
+ const names = message.reactions[reaction].usernames.map(getNameOfUser);
+ message.reactions[reaction].names = names;
+ });
+ }
}
}
return message;
diff --git a/app/version-check/server/functions/getNewUpdates.js b/app/version-check/server/functions/getNewUpdates.js
index f632bbce5eb4..b08df8538d40 100644
--- a/app/version-check/server/functions/getNewUpdates.js
+++ b/app/version-check/server/functions/getNewUpdates.js
@@ -1,5 +1,6 @@
import os from 'os';
import { HTTP } from 'meteor/http';
+import { check, Match } from 'meteor/check';
import { Settings } from '../../../models';
import { settings } from '../../../settings';
import { Info } from '../../../utils';
@@ -12,7 +13,7 @@ export default () => {
const { _oplogHandle } = MongoInternals.defaultRemoteCollectionDriver().mongo;
const oplogEnabled = _oplogHandle && _oplogHandle.onOplogEntry && settings.get('Force_Disable_OpLog_For_Cache') !== true;
- const data = {
+ const params = {
uniqueId: uniqueID.value,
installedAt: uniqueID.createdAt,
version: Info.version,
@@ -32,12 +33,24 @@ export default () => {
headers.Authorization = `Bearer ${ token }`;
}
- const result = HTTP.get('https://releases.rocket.chat/updates/check', {
- params: data,
+ const { data } = HTTP.get('https://releases.rocket.chat/updates/check', {
+ params,
headers,
});
- return result.data;
+ check(data, Match.ObjectIncluding({
+ versions: [String],
+ alerts: Match.Optional([Match.ObjectIncluding({
+ id: Match.Optional(String),
+ title: Match.Optional(String),
+ text: Match.Optional(String),
+ textArguments: Match.Optional([Match.Any]),
+ modifiers: Match.Optional([String]),
+ infoUrl: Match.Optional(String),
+ })]),
+ }));
+
+ return data;
} catch (error) {
// There's no need to log this error
// as it's pointless and the user
diff --git a/app/webdav/client/actionButton.js b/app/webdav/client/actionButton.js
index 1463efd15605..62656975cc59 100644
--- a/app/webdav/client/actionButton.js
+++ b/app/webdav/client/actionButton.js
@@ -3,7 +3,7 @@ import { t, getURL } from '../../utils';
import { Subscriptions, WebdavAccounts } from '../../models';
import { settings } from '../../settings';
import { MessageAction, modal } from '../../ui-utils';
-
+import { messageArgs } from '../../ui-utils/client/lib/messageArgs';
Meteor.startup(function() {
MessageAction.addButton({
@@ -24,7 +24,7 @@ Meteor.startup(function() {
return settings.get('Webdav_Integration_Enabled');
},
action() {
- const [, message] = this._arguments;
+ const { msg: message } = messageArgs(this);
const [attachment] = message.attachments;
const { file } = message;
const url = getURL(attachment.title_link, { full: true });
diff --git a/client/importPackages.js b/client/importPackages.js
index 6218bde36789..32e492e5d21d 100644
--- a/client/importPackages.js
+++ b/client/importPackages.js
@@ -95,6 +95,7 @@ import '../app/search/client';
import '../app/chatpal-search/client';
import '../app/lazy-load';
import '../app/discussion/client';
+import '../app/threads/client';
import '../app/mail-messages/client';
import '../app/utils';
import '../app/settings';
diff --git a/client/methods/deleteMessage.js b/client/methods/deleteMessage.js
index 79314f8661b2..f2ae46a1bedd 100644
--- a/client/methods/deleteMessage.js
+++ b/client/methods/deleteMessage.js
@@ -1,47 +1,27 @@
import { Meteor } from 'meteor/meteor';
-import { Tracker } from 'meteor/tracker';
-import { ChatMessage } from '../../app/models';
-import { hasAtLeastOnePermission } from '../../app/authorization';
-import { settings } from '../../app/settings';
-import _ from 'underscore';
-import moment from 'moment';
+import { ChatMessage } from '../../app/models/client';
+import { canDeleteMessage } from '../../app/utils/client';
Meteor.methods({
- deleteMessage(message) {
+ deleteMessage(msg) {
if (!Meteor.userId()) {
return false;
}
// We're now only passed in the `_id` property to lower the amount of data sent to the server
- message = ChatMessage.findOne({ _id: message._id });
+ const message = ChatMessage.findOne({ _id: msg._id });
- const hasPermission = hasAtLeastOnePermission('delete-message', message.rid);
- const forceDelete = hasAtLeastOnePermission('force-delete-message', message.rid);
- const deleteAllowed = settings.get('Message_AllowDeleting');
- let deleteOwn = false;
-
- if (message && message.u && message.u._id) {
- deleteOwn = message.u._id === Meteor.userId();
- }
- if (!(forceDelete || hasPermission || (deleteAllowed && deleteOwn))) {
+ if (!canDeleteMessage({
+ rid: message.rid,
+ ts: message.ts,
+ uid: message.u._id,
+ })) {
return false;
}
- const blockDeleteInMinutes = settings.get('Message_AllowDeleting_BlockDeleteInMinutes');
- if (!forceDelete && _.isNumber(blockDeleteInMinutes) && blockDeleteInMinutes !== 0) {
- const msgTs = moment(message.ts);
- const currentTsDiff = moment().diff(msgTs, 'minutes');
- if (currentTsDiff > blockDeleteInMinutes) {
- return false;
- }
-
-
- }
- Tracker.nonreactive(function() {
- ChatMessage.remove({
- _id: message._id,
- 'u._id': Meteor.userId(),
- });
+ ChatMessage.remove({
+ _id: message._id,
+ 'u._id': Meteor.userId(),
});
},
});
diff --git a/client/methods/updateMessage.js b/client/methods/updateMessage.js
index 4e4811d81d44..904cd9b9dc37 100644
--- a/client/methods/updateMessage.js
+++ b/client/methods/updateMessage.js
@@ -67,7 +67,7 @@ Meteor.methods({
if (originalMessage.attachments) {
if (originalMessage.attachments[0].description !== undefined) {
- delete messageObject.$set.msg;
+ delete messageObject.msg;
}
}
ChatMessage.update({
diff --git a/imports/message-read-receipt/client/message.js b/imports/message-read-receipt/client/message.js
index bfbe30624aac..2b406ba32bce 100644
--- a/imports/message-read-receipt/client/message.js
+++ b/imports/message-read-receipt/client/message.js
@@ -8,7 +8,7 @@ Template.message.helpers({
}
return {
- readByEveryone: (!this.unread && 'read') || 'color-component-color',
+ readByEveryone: (!this.msg.unread && 'read') || 'color-component-color',
};
},
});
diff --git a/imports/message-read-receipt/client/room.js b/imports/message-read-receipt/client/room.js
index 97641cc1202a..71357b3b58ac 100644
--- a/imports/message-read-receipt/client/room.js
+++ b/imports/message-read-receipt/client/room.js
@@ -1,5 +1,6 @@
import { t } from '../../../app/utils';
import { modal, MessageAction } from '../../../app/ui-utils';
+import { messageArgs } from '../../../app/ui-utils/client/lib/messageArgs';
import { settings } from '../../../app/settings';
MessageAction.addButton({
@@ -8,7 +9,7 @@ MessageAction.addButton({
label: 'Message_info',
context: ['starred', 'message', 'message-mobile'],
action() {
- const message = this._arguments[1];
+ const { msg: message } = messageArgs(this);
modal.open({
title: t('Message_info'),
content: 'readReceipts',
diff --git a/imports/message-read-receipt/server/hooks.js b/imports/message-read-receipt/server/hooks.js
index 24c0e6e94795..29e8388868a9 100644
--- a/imports/message-read-receipt/server/hooks.js
+++ b/imports/message-read-receipt/server/hooks.js
@@ -9,8 +9,10 @@ callbacks.add('afterSaveMessage', (message, room) => {
return message;
}
- // set subscription as read right after message was sent
- Subscriptions.setAsReadByRoomIdAndUserId(room._id, message.u._id);
+ if (room && !room.closedAt) {
+ // set subscription as read right after message was sent
+ Subscriptions.setAsReadByRoomIdAndUserId(room._id, message.u._id);
+ }
// mark message as read as well
ReadReceipt.markMessageAsReadBySender(message, room._id, message.u._id);
diff --git a/mocha.opts b/mocha.opts
index 88725803b547..b02109dca970 100644
--- a/mocha.opts
+++ b/mocha.opts
@@ -1,3 +1,3 @@
---compilers js:babel-mocha-es6-compiler
+--require babel-mocha-es6-compiler
--reporter spec
--ui bdd
diff --git a/package-lock.json b/package-lock.json
index 782fb7b71434..92928ae5576f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -758,7 +758,7 @@
},
"@types/events": {
"version": "1.2.0",
- "resolved": "http://registry.npmjs.org/@types/events/-/events-1.2.0.tgz",
+ "resolved": "https://registry.npmjs.org/@types/events/-/events-1.2.0.tgz",
"integrity": "sha512-KEIlhXnIutzKwRbQkGWb/I4HFqBuUykAdHgDED6xqwXJfONCjF5VoE0cXEiurh3XauygxzeDzgtXUqvLkxFzzA=="
},
"@types/express": {
@@ -1660,7 +1660,7 @@
},
"axios": {
"version": "0.18.0",
- "resolved": "http://registry.npmjs.org/axios/-/axios-0.18.0.tgz",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-0.18.0.tgz",
"integrity": "sha1-MtU+SFHv3AoRmTts0AB4nXDAUQI=",
"requires": {
"follow-redirects": "^1.3.0",
@@ -1998,7 +1998,7 @@
},
"babel-plugin-add-module-exports": {
"version": "0.2.1",
- "resolved": "http://registry.npmjs.org/babel-plugin-add-module-exports/-/babel-plugin-add-module-exports-0.2.1.tgz",
+ "resolved": "https://registry.npmjs.org/babel-plugin-add-module-exports/-/babel-plugin-add-module-exports-0.2.1.tgz",
"integrity": "sha1-mumh9KjcZ/DN7E9K7aHkOl/2XiU=",
"dev": true
},
@@ -2019,79 +2019,79 @@
},
"babel-plugin-syntax-async-functions": {
"version": "6.13.0",
- "resolved": "http://registry.npmjs.org/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz",
+ "resolved": "https://registry.npmjs.org/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz",
"integrity": "sha1-ytnK0RkbWtY0vzCuCHI5HgZHvpU=",
"dev": true
},
"babel-plugin-syntax-async-generators": {
"version": "6.13.0",
- "resolved": "http://registry.npmjs.org/babel-plugin-syntax-async-generators/-/babel-plugin-syntax-async-generators-6.13.0.tgz",
+ "resolved": "https://registry.npmjs.org/babel-plugin-syntax-async-generators/-/babel-plugin-syntax-async-generators-6.13.0.tgz",
"integrity": "sha1-a8lj67FuzLrmuStZbrfzXDQqi5o=",
"dev": true
},
"babel-plugin-syntax-class-constructor-call": {
"version": "6.18.0",
- "resolved": "http://registry.npmjs.org/babel-plugin-syntax-class-constructor-call/-/babel-plugin-syntax-class-constructor-call-6.18.0.tgz",
+ "resolved": "https://registry.npmjs.org/babel-plugin-syntax-class-constructor-call/-/babel-plugin-syntax-class-constructor-call-6.18.0.tgz",
"integrity": "sha1-nLnTn+Q8hgC+yBRkVt3L1OGnZBY=",
"dev": true
},
"babel-plugin-syntax-class-properties": {
"version": "6.13.0",
- "resolved": "http://registry.npmjs.org/babel-plugin-syntax-class-properties/-/babel-plugin-syntax-class-properties-6.13.0.tgz",
+ "resolved": "https://registry.npmjs.org/babel-plugin-syntax-class-properties/-/babel-plugin-syntax-class-properties-6.13.0.tgz",
"integrity": "sha1-1+sjt5oxf4VDlixQW4J8fWysJ94=",
"dev": true
},
"babel-plugin-syntax-decorators": {
"version": "6.13.0",
- "resolved": "http://registry.npmjs.org/babel-plugin-syntax-decorators/-/babel-plugin-syntax-decorators-6.13.0.tgz",
+ "resolved": "https://registry.npmjs.org/babel-plugin-syntax-decorators/-/babel-plugin-syntax-decorators-6.13.0.tgz",
"integrity": "sha1-MSVjtNvePMgGzuPkFszurd0RrAs=",
"dev": true
},
"babel-plugin-syntax-do-expressions": {
"version": "6.13.0",
- "resolved": "http://registry.npmjs.org/babel-plugin-syntax-do-expressions/-/babel-plugin-syntax-do-expressions-6.13.0.tgz",
+ "resolved": "https://registry.npmjs.org/babel-plugin-syntax-do-expressions/-/babel-plugin-syntax-do-expressions-6.13.0.tgz",
"integrity": "sha1-V0d1YTmqJtOQ0JQQsDdEugfkeW0=",
"dev": true
},
"babel-plugin-syntax-dynamic-import": {
"version": "6.18.0",
- "resolved": "http://registry.npmjs.org/babel-plugin-syntax-dynamic-import/-/babel-plugin-syntax-dynamic-import-6.18.0.tgz",
+ "resolved": "https://registry.npmjs.org/babel-plugin-syntax-dynamic-import/-/babel-plugin-syntax-dynamic-import-6.18.0.tgz",
"integrity": "sha1-jWomIpyDdFqZgqRBBRVyyqF5sdo=",
"dev": true
},
"babel-plugin-syntax-exponentiation-operator": {
"version": "6.13.0",
- "resolved": "http://registry.npmjs.org/babel-plugin-syntax-exponentiation-operator/-/babel-plugin-syntax-exponentiation-operator-6.13.0.tgz",
+ "resolved": "https://registry.npmjs.org/babel-plugin-syntax-exponentiation-operator/-/babel-plugin-syntax-exponentiation-operator-6.13.0.tgz",
"integrity": "sha1-nufoM3KQ2pUoggGmpX9BcDF4MN4=",
"dev": true
},
"babel-plugin-syntax-export-extensions": {
"version": "6.13.0",
- "resolved": "http://registry.npmjs.org/babel-plugin-syntax-export-extensions/-/babel-plugin-syntax-export-extensions-6.13.0.tgz",
+ "resolved": "https://registry.npmjs.org/babel-plugin-syntax-export-extensions/-/babel-plugin-syntax-export-extensions-6.13.0.tgz",
"integrity": "sha1-cKFITw+QiaToStRLrDU8lbmxJyE=",
"dev": true
},
"babel-plugin-syntax-flow": {
"version": "6.18.0",
- "resolved": "http://registry.npmjs.org/babel-plugin-syntax-flow/-/babel-plugin-syntax-flow-6.18.0.tgz",
+ "resolved": "https://registry.npmjs.org/babel-plugin-syntax-flow/-/babel-plugin-syntax-flow-6.18.0.tgz",
"integrity": "sha1-TDqyCiryaqIM0lmVw5jE63AxDI0=",
"dev": true
},
"babel-plugin-syntax-function-bind": {
"version": "6.13.0",
- "resolved": "http://registry.npmjs.org/babel-plugin-syntax-function-bind/-/babel-plugin-syntax-function-bind-6.13.0.tgz",
+ "resolved": "https://registry.npmjs.org/babel-plugin-syntax-function-bind/-/babel-plugin-syntax-function-bind-6.13.0.tgz",
"integrity": "sha1-SMSV8Xe98xqYHnMvVa3AvdJgH0Y=",
"dev": true
},
"babel-plugin-syntax-jsx": {
"version": "6.18.0",
- "resolved": "http://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz",
+ "resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz",
"integrity": "sha1-CvMqmm4Tyno/1QaeYtew9Y0NiUY=",
"dev": true
},
"babel-plugin-syntax-object-rest-spread": {
"version": "6.13.0",
- "resolved": "http://registry.npmjs.org/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz",
+ "resolved": "https://registry.npmjs.org/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz",
"integrity": "sha1-/WU28rzhODb/o6VFjEkDpZe7O/U=",
"dev": true
},
@@ -2488,7 +2488,7 @@
},
"babel-preset-es2015": {
"version": "6.3.13",
- "resolved": "http://registry.npmjs.org/babel-preset-es2015/-/babel-preset-es2015-6.3.13.tgz",
+ "resolved": "https://registry.npmjs.org/babel-preset-es2015/-/babel-preset-es2015-6.3.13.tgz",
"integrity": "sha1-l9zn7ykuGMubK3VF2AxZPCjZUX8=",
"dev": true,
"requires": {
@@ -2516,7 +2516,7 @@
},
"babel-preset-react": {
"version": "6.3.13",
- "resolved": "http://registry.npmjs.org/babel-preset-react/-/babel-preset-react-6.3.13.tgz",
+ "resolved": "https://registry.npmjs.org/babel-preset-react/-/babel-preset-react-6.3.13.tgz",
"integrity": "sha1-E9VeBqZfqqoHw5v2Op2DbgMhFvo=",
"dev": true,
"requires": {
@@ -2530,7 +2530,7 @@
},
"babel-preset-stage-0": {
"version": "6.3.13",
- "resolved": "http://registry.npmjs.org/babel-preset-stage-0/-/babel-preset-stage-0-6.3.13.tgz",
+ "resolved": "https://registry.npmjs.org/babel-preset-stage-0/-/babel-preset-stage-0-6.3.13.tgz",
"integrity": "sha1-eKN8VvCzmI8qeZMtywzrj/N3sNE=",
"dev": true,
"requires": {
@@ -3425,7 +3425,7 @@
},
"bl": {
"version": "1.2.2",
- "resolved": "http://registry.npmjs.org/bl/-/bl-1.2.2.tgz",
+ "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.2.tgz",
"integrity": "sha512-e8tQYnZodmebYDWGH7KMRvtzKXaJHx3BbilrgZCfvyLUYdKpK1t5PSPmpkny/SgiTSCnjfLW7v5rlONXVFkQEA==",
"requires": {
"readable-stream": "^2.3.5",
@@ -3803,7 +3803,7 @@
},
"readable-stream": {
"version": "1.1.14",
- "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
"integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=",
"requires": {
"core-util-is": "~1.0.0",
@@ -4140,7 +4140,7 @@
},
"chalk": {
"version": "1.1.3",
- "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
"integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
"requires": {
"ansi-styles": "^2.2.1",
@@ -4243,7 +4243,7 @@
},
"chimp": {
"version": "0.51.1",
- "resolved": "http://registry.npmjs.org/chimp/-/chimp-0.51.1.tgz",
+ "resolved": "https://registry.npmjs.org/chimp/-/chimp-0.51.1.tgz",
"integrity": "sha1-6hIbzfJsidV/jvNBlUDPPCeaPMU=",
"dev": true,
"requires": {
@@ -4289,7 +4289,7 @@
"dependencies": {
"async": {
"version": "0.9.2",
- "resolved": "http://registry.npmjs.org/async/-/async-0.9.2.tgz",
+ "resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz",
"integrity": "sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0=",
"dev": true
},
@@ -4361,7 +4361,7 @@
},
"minimist": {
"version": "1.2.0",
- "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
"dev": true
},
@@ -4413,7 +4413,7 @@
},
"progress": {
"version": "1.1.8",
- "resolved": "http://registry.npmjs.org/progress/-/progress-1.1.8.tgz",
+ "resolved": "https://registry.npmjs.org/progress/-/progress-1.1.8.tgz",
"integrity": "sha1-4mDHj2Fhzdmw5WzD4Khd4Xx6V74=",
"dev": true
},
@@ -4436,7 +4436,7 @@
},
"chokidar": {
"version": "1.6.1",
- "resolved": "http://registry.npmjs.org/chokidar/-/chokidar-1.6.1.tgz",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-1.6.1.tgz",
"integrity": "sha1-L0RHq16W5Q+z14n9kNTHLg5McMI=",
"dev": true,
"requires": {
@@ -4688,7 +4688,7 @@
},
"colors": {
"version": "1.1.2",
- "resolved": "http://registry.npmjs.org/colors/-/colors-1.1.2.tgz",
+ "resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz",
"integrity": "sha1-FopHAXVran9RoSzgyXv6KMCE7WM=",
"dev": true
},
@@ -5154,7 +5154,7 @@
"dependencies": {
"core-js": {
"version": "1.2.7",
- "resolved": "http://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz",
+ "resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz",
"integrity": "sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY="
}
}
@@ -5403,7 +5403,7 @@
"dependencies": {
"pify": {
"version": "2.3.0",
- "resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
+ "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
"integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
"dev": true
}
@@ -5433,7 +5433,7 @@
},
"deprecate": {
"version": "1.0.0",
- "resolved": "http://registry.npmjs.org/deprecate/-/deprecate-1.0.0.tgz",
+ "resolved": "https://registry.npmjs.org/deprecate/-/deprecate-1.0.0.tgz",
"integrity": "sha1-ZhSQ7SQokWpsiIPYg05WRvTkpKg="
},
"deprecated-decorator": {
@@ -5485,7 +5485,7 @@
},
"readable-stream": {
"version": "1.1.14",
- "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
"integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=",
"requires": {
"core-util-is": "~1.0.0",
@@ -5533,7 +5533,7 @@
},
"doctrine": {
"version": "1.5.0",
- "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz",
+ "resolved": "http://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz",
"integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=",
"dev": true,
"requires": {
@@ -5552,7 +5552,7 @@
"dependencies": {
"domelementtype": {
"version": "1.1.3",
- "resolved": "http://registry.npmjs.org/domelementtype/-/domelementtype-1.1.3.tgz",
+ "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.1.3.tgz",
"integrity": "sha1-vSh3PiZCiBrsUVRJJCmcXNgiGFs="
}
}
@@ -5869,7 +5869,7 @@
},
"es6-promisify": {
"version": "5.0.0",
- "resolved": "http://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz",
+ "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz",
"integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=",
"requires": {
"es6-promise": "^4.0.3"
@@ -6280,7 +6280,7 @@
},
"events": {
"version": "1.1.1",
- "resolved": "http://registry.npmjs.org/events/-/events-1.1.1.tgz",
+ "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz",
"integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ="
},
"evp_bytestokey": {
@@ -6675,7 +6675,7 @@
},
"external-editor": {
"version": "2.2.0",
- "resolved": "http://registry.npmjs.org/external-editor/-/external-editor-2.2.0.tgz",
+ "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.2.0.tgz",
"integrity": "sha512-bSn6gvGxKt+b7+6TKEv1ZycHleA7aHhRHyAqJyp5pbUFuYYNIzpZnQDk7AsYckyWdEnTeAnay0aCy2aV6iTk9A==",
"dev": true,
"requires": {
@@ -6818,13 +6818,13 @@
"dependencies": {
"lodash": {
"version": "2.4.2",
- "resolved": "http://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz",
"integrity": "sha1-+t2DS5aDBz2hebPq5tnA0VBT9z4=",
"dev": true
},
"underscore.string": {
"version": "2.3.3",
- "resolved": "http://registry.npmjs.org/underscore.string/-/underscore.string-2.3.3.tgz",
+ "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.3.3.tgz",
"integrity": "sha1-ccCL9rQosRM/N+ePo6Icgvcymw0=",
"dev": true
}
@@ -7751,7 +7751,7 @@
},
"get-stream": {
"version": "3.0.0",
- "resolved": "http://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz",
+ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz",
"integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ="
},
"get-value": {
@@ -7923,7 +7923,7 @@
"dependencies": {
"minimist": {
"version": "1.1.3",
- "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.1.3.tgz",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.1.3.tgz",
"integrity": "sha1-O+39kaktOQFvz6ocaB6Pqhoe/ag=",
"dev": true
}
@@ -8772,7 +8772,7 @@
},
"hapi": {
"version": "8.8.0",
- "resolved": "http://registry.npmjs.org/hapi/-/hapi-8.8.0.tgz",
+ "resolved": "https://registry.npmjs.org/hapi/-/hapi-8.8.0.tgz",
"integrity": "sha1-h+N6Bum0meiXkOLcERqpZotuYX8=",
"dev": true,
"requires": {
@@ -8842,7 +8842,7 @@
},
"catbox": {
"version": "4.3.0",
- "resolved": "http://registry.npmjs.org/catbox/-/catbox-4.3.0.tgz",
+ "resolved": "https://registry.npmjs.org/catbox/-/catbox-4.3.0.tgz",
"integrity": "sha1-IiN3vWfxKRrA4l0AAC0GWp3385o=",
"dev": true,
"requires": {
@@ -8939,7 +8939,7 @@
},
"joi": {
"version": "6.4.1",
- "resolved": "http://registry.npmjs.org/joi/-/joi-6.4.1.tgz",
+ "resolved": "https://registry.npmjs.org/joi/-/joi-6.4.1.tgz",
"integrity": "sha1-9Q9CRTVgBo5jg9oVrC0w3Xzra24=",
"dev": true,
"requires": {
@@ -8951,7 +8951,7 @@
"dependencies": {
"isemail": {
"version": "1.1.1",
- "resolved": "http://registry.npmjs.org/isemail/-/isemail-1.1.1.tgz",
+ "resolved": "https://registry.npmjs.org/isemail/-/isemail-1.1.1.tgz",
"integrity": "sha1-4Mj23D9HCX53dzlcaJYnGqJWw7U=",
"dev": true
},
@@ -8984,7 +8984,7 @@
"dependencies": {
"mime-db": {
"version": "1.14.0",
- "resolved": "http://registry.npmjs.org/mime-db/-/mime-db-1.14.0.tgz",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.14.0.tgz",
"integrity": "sha1-1WHxC27mbbUflK5leilRp0IX7YM=",
"dev": true
}
@@ -9584,7 +9584,7 @@
},
"readable-stream": {
"version": "1.1.14",
- "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
"integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=",
"requires": {
"core-util-is": "~1.0.0",
@@ -9920,7 +9920,7 @@
},
"is-builtin-module": {
"version": "1.0.0",
- "resolved": "http://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz",
+ "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz",
"integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=",
"dev": true,
"requires": {
@@ -10075,7 +10075,7 @@
},
"is-obj": {
"version": "1.0.1",
- "resolved": "http://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz",
+ "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz",
"integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8="
},
"is-object": {
@@ -10230,7 +10230,7 @@
},
"isemail": {
"version": "1.2.0",
- "resolved": "http://registry.npmjs.org/isemail/-/isemail-1.2.0.tgz",
+ "resolved": "https://registry.npmjs.org/isemail/-/isemail-1.2.0.tgz",
"integrity": "sha1-vgPfjMPineTSxd9lASY/H6RZXpo="
},
"isexe": {
@@ -10266,7 +10266,7 @@
},
"jasmine-core": {
"version": "2.99.1",
- "resolved": "http://registry.npmjs.org/jasmine-core/-/jasmine-core-2.99.1.tgz",
+ "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-2.99.1.tgz",
"integrity": "sha1-5kAN8ea1bhMLYcS80JPap/boyhU=",
"dev": true
},
@@ -10418,7 +10418,7 @@
},
"jsonfile": {
"version": "2.4.0",
- "resolved": "http://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz",
+ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz",
"integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=",
"dev": true,
"requires": {
@@ -10701,7 +10701,7 @@
},
"promise": {
"version": "6.1.0",
- "resolved": "http://registry.npmjs.org/promise/-/promise-6.1.0.tgz",
+ "resolved": "https://registry.npmjs.org/promise/-/promise-6.1.0.tgz",
"integrity": "sha1-LOcp9rlLRcJoka0GAsXJDgTG7vY=",
"optional": true,
"requires": {
@@ -10870,7 +10870,7 @@
},
"load-json-file": {
"version": "2.0.0",
- "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz",
+ "resolved": "http://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz",
"integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=",
"dev": true,
"requires": {
@@ -10882,7 +10882,7 @@
"dependencies": {
"pify": {
"version": "2.3.0",
- "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
+ "resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
"integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
"dev": true
}
@@ -11326,7 +11326,7 @@
},
"media-typer": {
"version": "0.3.0",
- "resolved": "http://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
+ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
"integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g="
},
"mem": {
@@ -11630,7 +11630,7 @@
},
"readable-stream": {
"version": "2.3.6",
- "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
"integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
"requires": {
"core-util-is": "~1.0.0",
@@ -11652,7 +11652,7 @@
},
"string_decoder": {
"version": "1.1.1",
- "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"requires": {
"safe-buffer": "~5.1.0"
@@ -11794,14 +11794,14 @@
"minimatch": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
- "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
+ "integrity": "sha1-UWbihkV/AzBgZL5Ul+jbsMPTIIM=",
"requires": {
"brace-expansion": "^1.1.7"
}
},
"minimist": {
"version": "0.0.8",
- "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
"integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0="
},
"minimist-options": {
@@ -11889,7 +11889,7 @@
},
"mkdirp": {
"version": "0.5.1",
- "resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
+ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
"integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
"requires": {
"minimist": "0.0.8"
@@ -12175,7 +12175,7 @@
},
"ncp": {
"version": "2.0.0",
- "resolved": "http://registry.npmjs.org/ncp/-/ncp-2.0.0.tgz",
+ "resolved": "https://registry.npmjs.org/ncp/-/ncp-2.0.0.tgz",
"integrity": "sha1-GVoh1sRuNh0vsSgbo4uR6d9727M=",
"optional": true
},
@@ -12431,7 +12431,7 @@
},
"npm-install-package": {
"version": "2.1.0",
- "resolved": "http://registry.npmjs.org/npm-install-package/-/npm-install-package-2.1.0.tgz",
+ "resolved": "https://registry.npmjs.org/npm-install-package/-/npm-install-package-2.1.0.tgz",
"integrity": "sha1-1+/jz816sAYUuJbqUxGdyaslkSU=",
"dev": true
},
@@ -12446,7 +12446,7 @@
"npmlog": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz",
- "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==",
+ "integrity": "sha1-CKfyqL9zRgR3mp76StXMcXq7lUs=",
"requires": {
"are-we-there-yet": "~1.1.2",
"console-control-strings": "~1.1.0",
@@ -12676,7 +12676,7 @@
},
"os-locale": {
"version": "1.4.0",
- "resolved": "http://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz",
+ "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz",
"integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=",
"requires": {
"lcid": "^1.0.0"
@@ -13003,7 +13003,7 @@
},
"es6-promise": {
"version": "4.0.5",
- "resolved": "http://registry.npmjs.org/es6-promise/-/es6-promise-4.0.5.tgz",
+ "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.0.5.tgz",
"integrity": "sha1-eILzCt3lskDM+n99eMVIMwlRrkI=",
"dev": true
},
@@ -13059,7 +13059,7 @@
},
"progress": {
"version": "1.1.8",
- "resolved": "http://registry.npmjs.org/progress/-/progress-1.1.8.tgz",
+ "resolved": "https://registry.npmjs.org/progress/-/progress-1.1.8.tgz",
"integrity": "sha1-4mDHj2Fhzdmw5WzD4Khd4Xx6V74=",
"dev": true
},
@@ -13107,7 +13107,7 @@
},
"tough-cookie": {
"version": "2.3.4",
- "resolved": "http://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.4.tgz",
+ "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.4.tgz",
"integrity": "sha512-TZ6TTfI5NtZnuyy/Kecv+CnoROnyXn2DN97LontgQpCwsX2XyLYCC0ENhYkehSOwAp8rTQKc/NUIF7BkQ5rKLA==",
"dev": true,
"requires": {
@@ -14159,7 +14159,7 @@
"dependencies": {
"minimist": {
"version": "1.2.0",
- "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ="
}
}
@@ -14175,7 +14175,7 @@
"dependencies": {
"pify": {
"version": "2.3.0",
- "resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
+ "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
"integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
"dev": true
}
@@ -14203,7 +14203,7 @@
},
"pify": {
"version": "2.3.0",
- "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
+ "resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
"integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
"dev": true
}
@@ -14266,7 +14266,7 @@
},
"readable-stream": {
"version": "2.3.6",
- "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
"integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
"requires": {
"core-util-is": "~1.0.0",
@@ -14378,7 +14378,7 @@
},
"regjsgen": {
"version": "0.2.0",
- "resolved": "http://registry.npmjs.org/regjsgen/-/regjsgen-0.2.0.tgz",
+ "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.2.0.tgz",
"integrity": "sha1-bAFq3qxVT3WCP+N6wFuS1aTtsfc=",
"dev": true
},
@@ -14541,7 +14541,7 @@
},
"requestretry": {
"version": "1.5.0",
- "resolved": "http://registry.npmjs.org/requestretry/-/requestretry-1.5.0.tgz",
+ "resolved": "https://registry.npmjs.org/requestretry/-/requestretry-1.5.0.tgz",
"integrity": "sha1-7RV7ulNSbt6z7DKo5wSkmYvs5ic=",
"dev": true,
"requires": {
@@ -14667,7 +14667,7 @@
},
"rimraf": {
"version": "2.4.5",
- "resolved": "http://registry.npmjs.org/rimraf/-/rimraf-2.4.5.tgz",
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.4.5.tgz",
"integrity": "sha1-7nEM5dk6j9uFb7Xqj/Di11k0sto=",
"requires": {
"glob": "^6.0.1"
@@ -14762,7 +14762,7 @@
},
"safe-regex": {
"version": "1.1.0",
- "resolved": "http://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz",
+ "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz",
"integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=",
"requires": {
"ret": "~0.1.10"
@@ -14784,7 +14784,7 @@
},
"sax": {
"version": "1.2.1",
- "resolved": "http://registry.npmjs.org/sax/-/sax-1.2.1.tgz",
+ "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz",
"integrity": "sha1-e45lYZCyKOgaZq6nSEgNgozS03o="
},
"schema-inspector": {
@@ -14797,7 +14797,7 @@
"dependencies": {
"async": {
"version": "1.5.2",
- "resolved": "http://registry.npmjs.org/async/-/async-1.5.2.tgz",
+ "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz",
"integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo="
}
}
@@ -14862,7 +14862,7 @@
},
"minimist": {
"version": "1.2.0",
- "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
"dev": true
},
@@ -15556,7 +15556,7 @@
},
"strip-ansi": {
"version": "3.0.1",
- "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
"integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
"requires": {
"ansi-regex": "^2.0.0"
@@ -16105,7 +16105,7 @@
},
"through": {
"version": "2.3.8",
- "resolved": "http://registry.npmjs.org/through/-/through-2.3.8.tgz",
+ "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
"integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=",
"dev": true
},
@@ -16757,7 +16757,7 @@
"dependencies": {
"semver": {
"version": "5.3.0",
- "resolved": "http://registry.npmjs.org/semver/-/semver-5.3.0.tgz",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz",
"integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8="
}
}
@@ -17365,7 +17365,7 @@
},
"wrap-ansi": {
"version": "2.1.0",
- "resolved": "http://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz",
"integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=",
"requires": {
"string-width": "^1.0.1",
@@ -17469,7 +17469,7 @@
},
"xolvio-ddp": {
"version": "0.12.3",
- "resolved": "http://registry.npmjs.org/xolvio-ddp/-/xolvio-ddp-0.12.3.tgz",
+ "resolved": "https://registry.npmjs.org/xolvio-ddp/-/xolvio-ddp-0.12.3.tgz",
"integrity": "sha1-NqarlhKyQLWg0cCoNJCK8XwLjwI=",
"dev": true,
"requires": {
@@ -17494,7 +17494,7 @@
},
"async": {
"version": "0.9.2",
- "resolved": "http://registry.npmjs.org/async/-/async-0.9.2.tgz",
+ "resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz",
"integrity": "sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0=",
"dev": true
},
@@ -17506,7 +17506,7 @@
},
"bl": {
"version": "0.9.5",
- "resolved": "http://registry.npmjs.org/bl/-/bl-0.9.5.tgz",
+ "resolved": "https://registry.npmjs.org/bl/-/bl-0.9.5.tgz",
"integrity": "sha1-wGt5evCF6gC8Unr8jvzxHeIjIFQ=",
"dev": true,
"requires": {
@@ -17515,7 +17515,7 @@
},
"bluebird": {
"version": "2.11.0",
- "resolved": "http://registry.npmjs.org/bluebird/-/bluebird-2.11.0.tgz",
+ "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-2.11.0.tgz",
"integrity": "sha1-U0uQM8AiyVecVro7Plpcqvu2UOE=",
"dev": true
},
@@ -17527,7 +17527,7 @@
},
"combined-stream": {
"version": "0.0.7",
- "resolved": "http://registry.npmjs.org/combined-stream/-/combined-stream-0.0.7.tgz",
+ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-0.0.7.tgz",
"integrity": "sha1-ATfmV7qlp1QcV6w3rF/AfXO03B8=",
"dev": true,
"requires": {
@@ -17548,7 +17548,7 @@
},
"form-data": {
"version": "0.2.0",
- "resolved": "http://registry.npmjs.org/form-data/-/form-data-0.2.0.tgz",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-0.2.0.tgz",
"integrity": "sha1-Jvi8JtpkQOKZy9z7aQNcT3em5GY=",
"dev": true,
"requires": {
@@ -17588,13 +17588,13 @@
},
"mime-db": {
"version": "1.12.0",
- "resolved": "http://registry.npmjs.org/mime-db/-/mime-db-1.12.0.tgz",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.12.0.tgz",
"integrity": "sha1-PQxjGA9FjrENMlqqN9fFiuMS6dc=",
"dev": true
},
"mime-types": {
"version": "2.0.14",
- "resolved": "http://registry.npmjs.org/mime-types/-/mime-types-2.0.14.tgz",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.0.14.tgz",
"integrity": "sha1-MQ4VnbI+B3+Lsit0jav6SVcUCqY=",
"dev": true,
"requires": {
@@ -17621,7 +17621,7 @@
},
"readable-stream": {
"version": "1.0.34",
- "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz",
"integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=",
"dev": true,
"requires": {
@@ -17633,7 +17633,7 @@
},
"request": {
"version": "2.53.0",
- "resolved": "http://registry.npmjs.org/request/-/request-2.53.0.tgz",
+ "resolved": "https://registry.npmjs.org/request/-/request-2.53.0.tgz",
"integrity": "sha1-GAo66St7Y5gC5PlUXdj83rcddgw=",
"dev": true,
"requires": {
@@ -17672,7 +17672,7 @@
},
"xolvio-fiber-utils": {
"version": "2.0.3",
- "resolved": "http://registry.npmjs.org/xolvio-fiber-utils/-/xolvio-fiber-utils-2.0.3.tgz",
+ "resolved": "https://registry.npmjs.org/xolvio-fiber-utils/-/xolvio-fiber-utils-2.0.3.tgz",
"integrity": "sha1-vsjXDHQGGjFjFbun0w0lyz6C3FA=",
"dev": true,
"requires": {
@@ -17690,7 +17690,7 @@
},
"xolvio-jasmine-expect": {
"version": "1.1.0",
- "resolved": "http://registry.npmjs.org/xolvio-jasmine-expect/-/xolvio-jasmine-expect-1.1.0.tgz",
+ "resolved": "https://registry.npmjs.org/xolvio-jasmine-expect/-/xolvio-jasmine-expect-1.1.0.tgz",
"integrity": "sha1-vCud1ghCMR8EV59agtzqaisxnH0=",
"dev": true,
"requires": {
@@ -17751,7 +17751,7 @@
},
"yargs": {
"version": "3.32.0",
- "resolved": "http://registry.npmjs.org/yargs/-/yargs-3.32.0.tgz",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.32.0.tgz",
"integrity": "sha1-AwiOnr+edWtpdRYR0qXvWRSCyZU=",
"requires": {
"camelcase": "^2.0.1",
diff --git a/package.json b/package.json
index 945295c705ee..6e9ab0cee99f 100644
--- a/package.json
+++ b/package.json
@@ -127,7 +127,7 @@
"@google-cloud/language": "^2.0.0",
"@google-cloud/storage": "^2.3.1",
"@google-cloud/vision": "^0.23.0",
- "@rocket.chat/apps-engine": "1.4.0",
+ "@rocket.chat/apps-engine": "1.4.1",
"@slack/client": "^4.8.0",
"adm-zip": "^0.4.13",
"apollo-server-express": "^1.3.6",
diff --git a/packages/rocketchat-i18n/i18n/da.i18n.json b/packages/rocketchat-i18n/i18n/da.i18n.json
index 26697505c17b..588e8993d193 100644
--- a/packages/rocketchat-i18n/i18n/da.i18n.json
+++ b/packages/rocketchat-i18n/i18n/da.i18n.json
@@ -585,11 +585,11 @@
"Closed_by_visitor": "Lukket af besøgende",
"Closing_chat": "Lukning af chat",
"Cloud": "Cloud",
+ "Cloud_workspace_connected_plus_account": "Din instans er nu forbundet til Rocket.Chat Cloud og en konto er tilknyttet.",
"Cloud_connect": "Rocket.Chat Cloud-forbindelse",
+ "Cloud_workspace_connected_without_account": "Din instans er nu forbundet til Rocket.Chat Cloud. Du kan logge ind i Rocket.Chat-skyen og forbinde din instans med din Cloud-konto, hvis du har lyst.",
"Cloud_what_is_it": "Hvad er det her?",
"Cloud_what_is_it_description": "Rocket.Chat Cloud-forbindelse lader dig forbinde en Rocket.Chat-instans, som du selv er vært for, til vores sky. Hvis du gør det, kan du administrere dine licenser, regninger og support i Rocket.Chat Cloud.",
- "Cloud_workspace_connected_plus_account": "Din instans er nu forbundet til Rocket.Chat Cloud og en konto er tilknyttet.",
- "Cloud_workspace_connected_without_account": "Din instans er nu forbundet til Rocket.Chat Cloud. Du kan logge ind i Rocket.Chat-skyen og forbinde din instans med din Cloud-konto, hvis du har lyst.",
"Cloud_login_to_cloud": "Log ind i Rocket.Chat Cloud",
"Cloud_address_to_send_registration_to": "Den e-mailadresse, din Cloud-tilmelding skal sendes til.",
"Cloud_update_email": "Opdatér e-mail",
diff --git a/packages/rocketchat-i18n/i18n/de.i18n.json b/packages/rocketchat-i18n/i18n/de.i18n.json
index 52bfba580e15..9cb04d65f0ba 100644
--- a/packages/rocketchat-i18n/i18n/de.i18n.json
+++ b/packages/rocketchat-i18n/i18n/de.i18n.json
@@ -328,6 +328,8 @@
"App_status_manually_enabled": "Aktiviert",
"App_status_unknown": "Unbekannt",
"App_support_url": "Support-URL",
+ "App_Url_to_Install_From": "Von URL installieren",
+ "App_Url_to_Install_From_File": "Aus Datei installieren",
"Appearance": "Erscheinungsbild",
"Application_added": "Die Anwendung wurde hinzugefügt.",
"Application_Name": "Name der Anwendung",
@@ -335,6 +337,8 @@
"Apply": "Anwenden",
"Apply_and_refresh_all_clients": "Anwenden und alle Clients aktualisieren",
"Apps": "Apps",
+ "Apps_Engine_Version": "Version er Anwendungsumgebung",
+ "Apps_Framework_Development_Mode": "Entwicklungsmodus aktivieren",
"Apps_Framework_enabled": "Das App Framework aktivieren",
"Apps_Settings": "App-Einstellungen",
"Apps_WhatIsIt": "Apps: Was ist das?",
@@ -354,6 +358,7 @@
"assign-admin-role": "Administratorrolle zuordnen",
"assign-admin-role_description": "Berechtigung, andere Benutzer zu Administratoren zu machen",
"Assign_admin": "Admin zuweisen",
+ "assign-roles": "Rollen zuweisen",
"at": "am",
"At_least_one_added_token_is_required_by_the_user": "Mindestens eines der hinzugefügten Tokens wird vom Benutzer verlangt werden",
"AtlassianCrowd": "Atlassian Crowd",
@@ -440,6 +445,7 @@
"Broadcasting_client_secret": "Broadcasting Client Secret",
"Broadcasting_enabled": "Broadcasting Aktiviert",
"Broadcasting_media_server_url": "Broadcasting Media Server URL",
+ "Browse_Files": "Dateien durchsuchen",
"Bugsnag_api_key": "Bugsnag API-Schlüssel",
"Build_Environment": "Buildumgebung",
"bulk-create-c": "Massen-Anlage von Kanälen",
@@ -504,6 +510,7 @@
"Channels_list": "Liste der öffentlichen Kanäle",
"Chat_button": "Chat-Button",
"Chat_closed": "Chat geschlossen",
+ "Chat_closed_by_agent": "Chat vom Agent geschlossen",
"Chat_closed_successfully": "Chat erfolgreich geschlossen",
"Chat_Now": "Jetzt chatten",
"Chat_window": "Chatfenster",
@@ -542,7 +549,7 @@
"Chatpal_More": "Mehr",
"Chatpal_No_Results": "Kein Ergebnis",
"Chatpal_no_search_results": "Kein Ergebnis",
- "Chatpal_one_search_result": "1 Ergebnis",
+ "Chatpal_one_search_result": "1 Ergebnis gefunden",
"Chatpal_Rooms": "Räume",
"Chatpal_run_search": "Suche",
"Chatpal_search_page_of": "Seite %s von %s",
@@ -586,9 +593,10 @@
"Closed": "Geschlossen",
"Closed_by_visitor": "Durch Besucher geschlossen",
"Closing_chat": "Schließe Chat",
+ "Cloud": "Cloud",
+ "Cloud_workspace_connected_plus_account": "Ihr Arbeitsbereich ist jetzt mit der Rocket.Chat-Cloud verbunden und ein Konto wurde zugeordnet.",
"Cloud_connect": "Rocket.Chat-Cloud-Connector",
"Cloud_what_is_it": "Was ist das?",
- "Cloud_workspace_connected_plus_account": "Ihr Arbeitsbereich ist jetzt mit der Rocket.Chat-Cloud verbunden und ein Konto wurde zugeordnet.",
"Cloud_update_email": "E-Mail aktualisieren",
"Cloud_registration_required": "Registrierung erforderlich",
"Cloud_error_code": "Code:",
@@ -1009,12 +1017,18 @@
"Disabled": "deaktiviert",
"Disallow_reacting": "Reaktionen verbieten",
"Disallow_reacting_Description": "Verhindert, dass ein Benutzer auf eine Nachricht mit Emojis reagiert",
+ "Disconnect": "Verbindung trennen",
"Display_offline_form": "Formular für Offline-Kontakt anzeigen",
"Display_unread_counter": "Anzahl der ungelesenen Nachrichten anzeigen",
"Displays_action_text": "Zeigt den Aktionstext",
+ "Discussion_name": "Name der Unterhaltung",
+ "Discussions": "Unterhaltungen",
+ "Discussion_start": "Eine Unterhaltung beginnen",
"Discussion_target_channel_description": "Wähle einen Kanal oder eine Gruppe aus, die zu Deinem Anliegen passt",
+ "Discussion_target_channel_prefix": "Es wird eine Unterhaltung erstellt in...",
"Discussion_target_channel": "Übergeordneter Kanal oder Gruppe",
"Discussion_first_message_title": "Deine Nachricht",
+ "Discussion_title": "Neue Unterhaltung erstellen",
"Dont_ask_me_again": "Nicht noch einmal fragen!",
"Dont_ask_me_again_list": "Frag mich nicht nochmal Liste",
"Do_not_display_unread_counter": "Keinerlei Zähler für diesen Kanal anzeigen",
@@ -1040,6 +1054,7 @@
"Duplicate_private_group_name": "Eine private Gruppe mit dem Namen '%s' existiert bereits.",
"Duration": "Dauer",
"E2E Encryption": "Ende-zu-Ende-Verschlüsselung",
+ "E2E_Enabled": "E2E aktiviert",
"E2E_Enable_alert": "Dieses Feature ist derzeit im Beta-Test. Bitte melden Sie Fehler unter github.com/RocketChat/Rocket.Chat/issues und beachten Sie folgende Auswirkungen: - Verschlüsselte Nachrichten werden durch die Suche nicht gefunden werden. - Die mobilen Apps unterstützen derzeit noch keine verschlüsselten Nachrichten. - Bots werden wahrscheinlich keine verschlüsselten Nachrichten verarbeiten können. - Uploads werden in dieser Version nicht verschlüsselt werden.",
"E2E_Enable_description": "Aktivieren Sie diese Option, um Direktnachrichten und private Gruppen verschlüsseln zu können.",
"E2E_Encryption_Password_Explanation": "Sie können jetzt verschlüsselte private Gruppen und Direktnachrichten erstellen. Sie können auch vorhandene private Gruppen oder Direktnachrichten verschlüsseln. Bitte bewahren Sie Ihr Passwort an einem sicheren Ort auf - Sie müssen ihn auf anderen Geräten eingeben, auf denen Sie die Ende-zu-Ende-Verschlüsselung verwenden möchten.",
@@ -1241,6 +1256,7 @@
"Export_My_Data": "Meine Daten exportieren",
"expression": "Ausdruck",
"Extended": "Erweitert",
+ "External_Domains": "Externe Domains",
"External_Queue_Service_URL": "URL der Queue des externen Dienstes",
"External_Service": "Externer Dienst",
"Facebook_Page": "Facebook Seite",
@@ -1250,7 +1266,12 @@
"Favorites": "Favoriten",
"Feature_Depends_on_Livechat_Visitor_navigation_as_a_message_to_be_enabled": "Diese Funktion hängt davon ab, ob \"Besucher-Navigationsprotokoll als Nachricht senden\" aktiviert sein soll.",
"Features_Enabled": "Aktivierte Funktionen",
+ "FEDERATION_Discovery_Method": "Prüfmethode",
"FEDERATION_Domain": "Domain",
+ "FEDERATION_Domain_Alert": "Nach dem Aktivieren dieser Funktion darf dieser Wert nicht geändert werden. Änderungen an der Domain können wir noch nicht verarbeiten.",
+ "FEDERATION_Public_Key": "Öffentlicher Schüssel",
+ "FEDERATION_Public_Key_Description": "Dies ist der Schlüssel, der mit den Peers geteilt werden muss.",
+ "FEDERATION_Hub_URL": "HUB-URL",
"FEDERATION_Status": "Status",
"Field": "Feld",
"Field_removed": "Feld entfernt",
@@ -1948,10 +1969,12 @@
"Message_TimeFormat": "Zeitformat",
"Message_TimeFormat_Description": "Für mögliche Formate s. Dokumentation von Moment.js ",
"Message_too_long": "Die Nachricht ist zu lang",
+ "Message_too_long_as_an_attachment_question": "Die Nachricht ist zu lang. Soll sie stattdessen als Anhang gesendet werden?",
"Message_UserId": "Benutze-ID",
"Message_VideoRecorderEnabled": "Videoaufnahme eingeschaltet",
"Message_VideoRecorderEnabledDescription": "Erfordert, dass der Medientyp 'video/webm' in den \"Datei-Upload\"-Einstellungen als Medientyp akzeptiert wird",
"Message_view_mode_info": "Dadurch ändert sich der Platzbedarf für Nachrichten auf dem Bildschirm",
+ "message": "Nachricht",
"Message": "Nachricht",
"messages": "Nachrichten",
"Messages": "Nachrichten",
@@ -2152,6 +2175,7 @@
"Permalink": "Permalink",
"Permissions": "Berechtigungen",
"Personal_Access_Tokens": "Persönlicher Zugangsschlüssel ",
+ "Phone_number": "Telefonnummer",
"pin-message": "Nachricht anheften",
"pin-message_description": "Berechtigung, eine Nachricht in einem Kanal anzuheften",
"Pin_Message": "Nachricht anheften",
@@ -2370,6 +2394,8 @@
"RetentionPolicyRoom_OverrideGlobal": "Globale Aufbewahrungsrichtlinie außer Kraft setzen",
"RetentionPolicyRoom_ReadTheDocs": "Achtung! Eine fehlerhafte Anpassung dieser Einstellungen kann den gesamten Nachrichtenverlauf zerstören. Bitte lesen Sie die Dokumentation, bevor Sie das Feature hier aktivieren.",
"Retry_Count": "Anzahl der Wiederholungsversuche",
+ "Return_to_home": "Zurück zur Startseite",
+ "Return_to_previous_page": "zur vorherigen Seite zurückkehren",
"Robot_Instructions_File_Content": "Inhalt von Robots.txt",
"Rocket_Chat_Alert": "Rocket.Chat-Alarm",
"Role": "Rolle",
@@ -2447,6 +2473,7 @@
"Screen_Share": "Bildschirmübertragung",
"Script_Enabled": "Das Script wurde aktiviert",
"Search": "Suche",
+ "Search_Apps": "Apps suchen",
"Search_by_file_name": "Suche nach Dateiname",
"Search_by_username": "Anhand des Nutzernamens suchen",
"Search_Channels": "Kanäle suchen",
@@ -2531,6 +2558,7 @@
"show_offline_users": "Zeige Benutzer an, die offline sind",
"Show_on_registration_page": "Auf der Registrierungsseite anzeigen",
"Show_only_online": "Nur Benutzer anzeigen welche Online sind",
+ "Show_on_offline_page": "Auf Offline-Seite anzeigen",
"Show_preregistration_form": "Vorregistrierungsformular anzeigen",
"Show_queue_list_to_all_agents": "Die Warteschlange allen Agenten anzeigen",
"Show_room_counter_on_sidebar": "Zeige den Zähler des Raumes in der Seitenleiste an",
@@ -2651,6 +2679,7 @@
"Survey": "Umfrage",
"Survey_instructions": "Bewerten Sie jede Frage nach Ihrer Zufriedenheit. 1 bedeutet, dass Sie völlig frustriert sind. 5 bedeutet, dass Sie vollständig zufrieden sind.",
"Symbols": "Symbole",
+ "Sync": "Syncronisieren",
"Sync / Import": "Synchronisieren / Importieren",
"Sync_in_progress": "Eine Synchronisierung wird durchgeführt",
"Sync_Interval": "Synchronisierungsintervall",
@@ -2896,6 +2925,7 @@
"User_is_now_an_admin": "Der Benutzer ist jetzt ein Administrator",
"User_is_unblocked": "Benutzer ist nicht mehr geblockt",
"User_joined_channel": "Ist dem Kanal beigetreten",
+ "User_joined_conversation": "Ist der Unterhaltung beigetreten",
"User_joined_channel_female": "Ist dem Kanal beigetreten",
"User_joined_channel_male": "Ist dem Kanal beigetreten",
"User_left": "Benutzer __user_left__ hat den Kanal verlassen",
diff --git a/packages/rocketchat-i18n/i18n/en.i18n.json b/packages/rocketchat-i18n/i18n/en.i18n.json
index 1f037b8568c0..02a49d70dc44 100644
--- a/packages/rocketchat-i18n/i18n/en.i18n.json
+++ b/packages/rocketchat-i18n/i18n/en.i18n.json
@@ -343,7 +343,7 @@
"Apply_and_refresh_all_clients": "Apply and refresh all clients",
"Apps": "Apps",
"Apps_Engine_Version": "Apps Engine Version",
- "Apps_Framework_Development_Mode": "Eneble development mode",
+ "Apps_Framework_Development_Mode": "Enable development mode",
"Apps_Framework_Development_Mode_Description": "Development mode allows the installation of Apps that are not from the Rocket.Chat's Marketplace.",
"Apps_Framework_enabled": "Enable the App Framework",
"Apps_Settings": "App's Settings",
@@ -516,6 +516,7 @@
"Channels_list": "List of public channels",
"Chat_button": "Chat button",
"Chat_closed": "Chat closed",
+ "Chat_closed_by_agent": "Chat closed by agent",
"Chat_closed_successfully": "Chat closed successfully",
"Chat_Now": "Chat Now",
"Chat_window": "Chat window",
@@ -599,11 +600,14 @@
"Closed_by_visitor": "Closed by visitor",
"Closing_chat": "Closing chat",
"Cloud": "Cloud",
+ "Cloud_workspace_connected_plus_account": "Your workspace is now connected to the Rocket.Chat Cloud and an account is associated.",
"Cloud_connect": "Rocket.Chat Cloud Connect",
+ "Cloud_workspace_connected_without_account": "Your workspace is now connected to the Rocket.Chat Cloud. If you would like, you can login to the Rocket.Chat Cloud and associate your workspace with your Cloud account.",
"Cloud_what_is_it": "What is this?",
"Cloud_what_is_it_description": "Rocket.Chat Cloud Connect allows you to connect your self-hosted Rocket.Chat Workspace to our Cloud. Doing so enables you to manage your licenses, Billing and Support in Rocket.Chat Cloud.",
- "Cloud_workspace_connected_plus_account": "Your workspace is now connected to the Rocket.Chat Cloud and an account is associated.",
- "Cloud_workspace_connected_without_account": "Your workspace is now connected to the Rocket.Chat Cloud. If you would like, you can login to the Rocket.Chat Cloud and associate your workspace with your Cloud account.",
+ "Cloud_workspace_connected": "Your workspace has been successfully connected to Rocket.Chat Cloud. You can access the cloud to manage account information",
+ "Cloud_workspace_support": "If you have any trouble with a cloud service, please try to sync first. Should the issue persist, please open a support ticket in the Cloud Console.",
+ "Cloud_workspace_disconnect": "If you no longer wish to utilize cloud services you can disconnect your workspace from the Rocket.Chat Cloud.",
"Cloud_login_to_cloud": "Login to Rocket.Chat Cloud",
"Cloud_address_to_send_registration_to": "The address to send your Cloud registration email to.",
"Cloud_update_email": "Update Email",
@@ -1037,6 +1041,7 @@
"Disabled": "Disabled",
"Disallow_reacting": "Disallow Reacting",
"Disallow_reacting_Description": "Disallows reacting",
+ "Disconnect": "Disconnect",
"Display_offline_form": "Display Offline Form",
"Display_unread_counter": "Display number of unread messages",
"Displays_action_text": "Displays action text",
@@ -1086,6 +1091,8 @@
"edit-message_description": "Permission to edit a message within a room",
"edit-other-user-active-status": "Edit Other User Active Status",
"edit-other-user-active-status_description": "Permission to enable or disable other accounts",
+ "edit-other-user-avatar": "Edit Other User Avatar",
+ "edit-other-user-avatar_description": "Permission to change other user's avatar.",
"edit-other-user-info": "Edit Other User Information",
"edit-other-user-info_description": "Permission to change other user's name, username or email address.",
"edit-other-user-password": "Edit Other User Password",
@@ -1369,6 +1376,7 @@
"First_Channel_After_Login": "First Channel After Login",
"First_response_time": "First Response Time",
"Flags": "Flags",
+ "Follow_message": "Follow message",
"Follow_social_profiles": "Follow our social profiles, fork us on github and share your thoughts about the rocket.chat app on our trello board.",
"Fonts": "Fonts",
"Food_and_Drink": "Food & Drink",
@@ -2026,6 +2034,8 @@
"Mobile": "Mobile",
"Mobile_Notifications_Default_Alert": "Mobile Notifications Default Alert",
"Monday": "Monday",
+ "Mongo_version": "Mongo Version",
+ "Mongo_storageEngine": "Mongo Storage Engine",
"Monitor_history_for_changes_on": "Monitor History for Changes on",
"More": "More",
"More_channels": "More channels",
@@ -2099,6 +2109,7 @@
"No_starred_messages": "No starred messages",
"No_such_command": "No such command: `/__command__`",
"No_discussions_yet": "No discussions yet",
+ "No_Threads": "No threads found",
"No_user_with_username_%s_was_found": "No user with username \"%s\" was found!",
"Nobody_available": "Nobody available",
"Node_version": "Node Version",
@@ -2384,6 +2395,7 @@
"Remove_someone_from_room": "Remove someone from the room",
"Removed": "Removed",
"Removed_User": "Removed User",
+ "Replied_on": "Replied on",
"Replies": "Replies",
"Reply": "Reply",
"ReplyTo": "Reply-To",
@@ -2392,6 +2404,7 @@
"Report_sent": "Report sent",
"Report_this_message_question_mark": "Report this message?",
"Reporting": "Reporting",
+ "Request_comment_when_closing_conversation": "Request comment when closing conversation",
"Require_all_tokens": "Require all tokens",
"Require_any_token": "Require any token",
"Require_password_change": "Require password change",
@@ -2678,14 +2691,15 @@
"Statistics": "Statistics",
"Statistics_reporting": "Send Statistics to Rocket.Chat",
"Statistics_reporting_Description": "By sending your statistics, you'll help us identify how many instances of Rocket.Chat are deployed, as well as how good the system is behaving, so we can further improve it. Don't worry, as no user information is sent and all the information we receive is kept confidential.",
- "Stats_Active_Users": "Active Users",
+ "Stats_Active_Users": "Activated Users",
"Stats_Avg_Channel_Users": "Average Channel Users",
"Stats_Avg_Private_Group_Users": "Average Private Group Users",
"Stats_Away_Users": "Away Users",
"Stats_Max_Room_Users": "Max Rooms Users",
- "Stats_Non_Active_Users": "Inactive Users",
+ "Stats_Non_Active_Users": "Deactivated Users",
"Stats_Offline_Users": "Offline Users",
"Stats_Online_Users": "Online Users",
+ "Stats_Total_Connected_Users": "Total Connected Users",
"Stats_Total_Channels": "Total Channels",
"Stats_Total_Direct_Messages": "Total Direct Message Rooms",
"Stats_Total_Livechat_Rooms": "Total Livechat Rooms",
@@ -2719,6 +2733,7 @@
"Survey": "Survey",
"Survey_instructions": "Rate each question according to your satisfaction, 1 meaning you are completely unsatisfied and 5 meaning you are completely satisfied.",
"Symbols": "Symbols",
+ "Sync": "Sync",
"Sync / Import": "Sync / Import",
"Sync_in_progress": "Synchronization in progress",
"Sync_Interval": "Sync interval",
@@ -2820,6 +2835,7 @@
"This_room_has_been_unarchived_by__username_": "This room has been unarchived by __username__",
"thread": "thread",
"Threads": "Threads",
+ "Thread_message": "Commented on *__username__'s* message: _ __msg__ _",
"This_week": "This Week",
"Thursday": "Thursday",
"Time_in_seconds": "Time in seconds",
@@ -2852,7 +2868,9 @@
"Topic": "Topic",
"Total": "Total",
"Total_conversations": "Total Conversations",
+ "Total_Discussions": "Total Discussions",
"Total_messages": "Total Messages",
+ "Total_Threads": "Total Threads",
"Total_visitors": "Total Visitors",
"Tourism": "Tourism",
"Transcript_Enabled": "Ask Visitor if They Would Like a Transcript After Chat Closed",
@@ -2896,6 +2914,7 @@
"unarchive-room_description": "Permission to unarchive channels",
"Unblock_User": "Unblock User",
"Unfavorite": "Unfavorite",
+ "Unfollow_message": "Unfollow message",
"Unignore": "Unignore",
"Uninstall": "Uninstall",
"Unmute_someone_in_room": "Unmute someone in the room",
@@ -3180,4 +3199,4 @@
"Your_question": "Your question",
"Your_server_link": "Your server link",
"Your_workspace_is_ready": "Your workspace is ready to use 🎉"
-}
+}
\ No newline at end of file
diff --git a/packages/rocketchat-i18n/i18n/hr.i18n.json b/packages/rocketchat-i18n/i18n/hr.i18n.json
index 420d45de524d..26ab0c8d5d4c 100644
--- a/packages/rocketchat-i18n/i18n/hr.i18n.json
+++ b/packages/rocketchat-i18n/i18n/hr.i18n.json
@@ -596,11 +596,11 @@
"Closed_by_visitor": "Zatvorio posjetitelj",
"Closing_chat": "Zatvaranje chata",
"Cloud": "Cloud",
+ "Cloud_workspace_connected_plus_account": "Vaš radni prostor sada je spojen na Rocket.Chat Cloud i pridružen je račun.",
"Cloud_connect": "Rocket.Chat Cloud Connect",
+ "Cloud_workspace_connected_without_account": "Vaš radni prostor sada je povezan s oblakom Rocket.Chat. Ako želite, možete se prijaviti na Rocket.Chat Cloud i povezati vaš radni prostor s vašim Cloud računom.",
"Cloud_what_is_it": "Što je ovo?",
"Cloud_what_is_it_description": "Rocket.Chat Cloud Connect omogućuje vam povezivanje vašeg Rocket.Chat radnog prostora koje ste sami ugradili u naš Cloud. Na taj način možete upravljati licencama, naplatom i podrškom u Rocket.Chat Cloudu.",
- "Cloud_workspace_connected_plus_account": "Vaš radni prostor sada je spojen na Rocket.Chat Cloud i pridružen je račun.",
- "Cloud_workspace_connected_without_account": "Vaš radni prostor sada je povezan s oblakom Rocket.Chat. Ako želite, možete se prijaviti na Rocket.Chat Cloud i povezati vaš radni prostor s vašim Cloud računom.",
"Cloud_login_to_cloud": "Prijava u Rocket.Chat Cloud",
"Cloud_address_to_send_registration_to": "Adresa za slanje e-pošte za registraciju u Cloudu.",
"Cloud_update_email": "Ažuriraj e-poštu",
diff --git a/packages/rocketchat-i18n/i18n/hu.i18n.json b/packages/rocketchat-i18n/i18n/hu.i18n.json
index 86968dbb3786..99c752df2b0e 100644
--- a/packages/rocketchat-i18n/i18n/hu.i18n.json
+++ b/packages/rocketchat-i18n/i18n/hu.i18n.json
@@ -3,7 +3,9 @@
"500": "Szerver oldali hiba",
"#channel": "#csatorna",
"0_Errors_Only": "0 - Csak hibák",
+ "12_Hour": "12 órás óra",
"1_Errors_and_Information": "1 - Hibák és Információk",
+ "24_Hour": "24 órás óra",
"2_Erros_Information_and_Debug": "2 - Hibák, információk és hibakeresés",
"@username": "@felhasználónév",
"@username_message": "@felhasználónév ",
@@ -39,6 +41,7 @@
"Accounts_AvatarCacheTime_description": "Az a másodpercek száma, amikor a http protokollt az avatár képek elrejtésére használják.",
"Accounts_AvatarResize": "Profilképek átméretezése",
"Accounts_AvatarSize": "Profilkép mérete",
+ "Accounts_AvatarExternalProviderUrl_Description": "Például: \"https://acme.com/api/v1/{username}\"",
"Accounts_BlockedDomainsList": "Blokkolt domain-ek listája",
"Accounts_BlockedDomainsList_Description": "Blokkolt domain-ek listája (vesszővel elválasztva)",
"Accounts_BlockedUsernameList": "Blokkolt felhasználónevek listája",
@@ -70,7 +73,7 @@
"Accounts_iframe_enabled": "Engedélyezett",
"Accounts_iframe_url": "Iframe URL",
"Accounts_LoginExpiration": "Bejelentkezés lejárata Days",
- "Accounts_ManuallyApproveNewUsers": "Kézzel jóváhagyása Új felhasználók",
+ "Accounts_ManuallyApproveNewUsers": "Új felhasználók kézi jóváhagyása",
"Accounts_OAuth_Custom_Authorize_Path": "engedélyezheti Path",
"Accounts_OAuth_Custom_Button_Color": "Gomb színe",
"Accounts_OAuth_Custom_Button_Label_Color": "Gomb felirat színe",
@@ -85,7 +88,7 @@
"Accounts_OAuth_Custom_Secret": "Titok",
"Accounts_OAuth_Custom_Token_Path": "Token Path",
"Accounts_OAuth_Custom_Token_Sent_Via": "Token keresztül küldött",
- "Accounts_OAuth_Custom_Username_Field": "felhasználónév menző",
+ "Accounts_OAuth_Custom_Username_Field": "felhasználónév mező",
"Accounts_OAuth_Drupal": "Drupal bejelentkezés engedélyezve",
"Accounts_OAuth_Drupal_callback_url": "Drupal oAuth2 átirányító URI",
"Accounts_OAuth_Drupal_id": "Drupal oAuth2 Ügyfél azonosító",
@@ -172,6 +175,8 @@
"Accounts_RequireNameForSignUp": "Kötelező név mező",
"Accounts_RequirePasswordConfirmation": "Igényelt jelszó megerősítése",
"Accounts_SearchFields": "A keresés során figyelembe veendő mezők",
+ "Accounts_Send_Email_When_Activating": "E-mail küldése a felhasználónak, ha aktiválva van",
+ "Accounts_Send_Email_When_Deactivating": "E-mail küldése a felhasználónak, ha ki van kapcsolva",
"Accounts_SetDefaultAvatar": "alapértelmezett Avatar beállítása",
"Accounts_SetDefaultAvatar_Description": "alapértelmezett Avatar meghatározása az Oauth vagy a Gravatar alapján",
"Accounts_ShowFormLogin": "Űrlap alapú bejelentkezés megjelenítése",
@@ -225,6 +230,7 @@
"Alias_Format": "álnév formátum",
"Alias_Format_Description": "Üzenetek importálása lefékezésből álnévvel; %s helyére a felhasználó felhasználóneve kerül. Ha üres, nincs alias használata.",
"Alias_Set": "Alias Set",
+ "Aliases": "Álnevek",
"All": "Minden",
"All_added_tokens_will_be_required_by_the_user": "Minden hozzáadott zsetont a felhasználó igényel",
"All_channels": "Minden csatorna",
@@ -232,10 +238,11 @@
"All_messages": "Minden üzenet",
"All_users": "Minden felhasználó",
"All_users_in_the_channel_can_write_new_messages": "A csatornán minden felhasználó írhat új üzeneteket",
- "Allow_Invalid_SelfSigned_Certs": "Az érvénytelen Önaláíró Certs",
- "Allow_Invalid_SelfSigned_Certs_Description": "Az érvénytelen és önaláíró SSL tanúsítvány a kapcsolatot a hitelesítésre és előzetesek.",
+ "Allow_Invalid_SelfSigned_Certs": "Érvénytelen saját aláírású SSL-tanúsítványok engedélyezése",
+ "Allow_Invalid_SelfSigned_Certs_Description": "Érvénytelen és saját aláírású SSL-tanúsítvány engedélyezése a linkek érvényesítéséhez és előnézeteihez.",
"Allow_switching_departments": "Engedélyezze a látogatókat a kapcsolóosztályokba",
"Allow_Marketing_Emails": "Engedélyezze a marketing e-maileket",
+ "Almost_done": "Majdnem kész",
"Alphabetical": "ABC sorrendben",
"Always_open_in_new_window": "Mindig új ablakban nyissa meg",
"Analytics": "Analytics",
@@ -1901,13 +1908,13 @@
"Office_hours_enabled": "Az Office Hours engedélyezve van",
"Office_hours_updated": "Nyitvatartási idő frissítve",
"Offline": "Offline",
- "Offline_DM_Email": "Te már közvetlen üzenetben által __user__",
+ "Offline_DM_Email": "Közvetlen üzenet e-mail tárgya",
"Offline_Email_Subject_Description": "Az alkalmazás nevét, URL-jét, felhasználónevét és a helyiségnevet a [Site_Name], [Site_URL], [Felhasználó] és [Room] használhatja. ",
"Offline_form": "Offline nyomtatvány",
"Offline_form_unavailable_message": "Offline formában érhető el az üzenetet",
"Offline_Link_Message": "GO TO MESSAGE",
"Offline_Mention_All_Email": "Az összes e-mail tárgyának említése",
- "Offline_Mention_Email": "Meg kellett volna említeni a __user__ a #__room__",
+ "Offline_Mention_Email": "Megemlítés e-mail tárgya",
"Offline_message": "Offline üzenet",
"Offline_success_message": "Offline siker üzenet",
"Offline_unavailable": "Offline nem érhető el",
diff --git a/packages/rocketchat-i18n/i18n/ja.i18n.json b/packages/rocketchat-i18n/i18n/ja.i18n.json
index 9acc01c74555..af61cf2cf429 100644
--- a/packages/rocketchat-i18n/i18n/ja.i18n.json
+++ b/packages/rocketchat-i18n/i18n/ja.i18n.json
@@ -516,6 +516,7 @@
"Channels_list": "パブリックチャンネルの一覧",
"Chat_button": "チャットボタン",
"Chat_closed": "チャットを閉じました",
+ "Chat_closed_by_agent": "エージェントがチャットを閉じました",
"Chat_closed_successfully": "チャットが正常に閉じました",
"Chat_Now": "チャット",
"Chat_window": "チャットウィンドウ",
@@ -599,11 +600,14 @@
"Closed_by_visitor": "訪問者によって閉鎖される",
"Closing_chat": "閉じるチャット",
"Cloud": "クラウド",
+ "Cloud_workspace_connected_plus_account": "あなたのワークスペースは Rocket.Chat クラウドに接続され、アカウントに関連付けられました。",
"Cloud_connect": "Rocket.Chat クラウド接続",
+ "Cloud_workspace_connected_without_account": "あなたのワークスペースは Rocket.Chat クラウドに接続されました。Rocket.Chat クラウドにログインし、ワークスペースをクラウドアカウントに関連付けることができます。",
"Cloud_what_is_it": "これは何?",
"Cloud_what_is_it_description": "Rocket.Chat クラウド接続は、セルフホスティングされた Rocket.Chat ワークスペースをクラウドに接続することができます。これにより、ライセンス、支払いおよびサポートを Rocket.Chat クラウドで管理することができるようになります。",
- "Cloud_workspace_connected_plus_account": "あなたのワークスペースは Rocket.Chat クラウドに接続され、アカウントに関連付けられました。",
- "Cloud_workspace_connected_without_account": "あなたのワークスペースは Rocket.Chat クラウドに接続されました。Rocket.Chat クラウドにログインし、ワークスペースをクラウドアカウントに関連付けることができます。",
+ "Cloud_workspace_connected": "ワークスペースは Rocket.Chat Cloud に正常に接続されました。クラウドにアクセスしてアカウント情報を管理できます",
+ "Cloud_workspace_support": "クラウドサービスに問題がある場合は、まず同期を試みてください。問題が解決しない場合は、クラウドコンソールでサポートチケットを開いてください。",
+ "Cloud_workspace_disconnect": "クラウドサービスを利用したくない場合は、Rocket.Chat Cloud からワークスペースを切断することができます。",
"Cloud_login_to_cloud": "Rocket.Chat クラウドにログイン",
"Cloud_address_to_send_registration_to": "クラウド登録メールの送信先メールアドレス",
"Cloud_update_email": "メールアドレスを更新する",
@@ -1037,6 +1041,7 @@
"Disabled": "無効",
"Disallow_reacting": "反応しない",
"Disallow_reacting_Description": "反応しない",
+ "Disconnect": "切断",
"Display_offline_form": "オフラインフォームの表示",
"Display_unread_counter": "未読メッセージの数を表示する",
"Displays_action_text": "操作テキストを表示",
@@ -1086,6 +1091,8 @@
"edit-message_description": "ルーム内のメッセージを編集する権限",
"edit-other-user-active-status": "他のユーザーのアクティブなステータスを編集する",
"edit-other-user-active-status_description": "他のアカウントを有効または無効にする権限",
+ "edit-other-user-avatar": "他のユーザーのアバターを編集",
+ "edit-other-user-avatar_description": "他のユーザーのアバターを変更する権限。",
"edit-other-user-info": "他のユーザー情報を編集する",
"edit-other-user-info_description": "他のユーザーの名前、ユーザー名、電子メールアドレスを変更する権限。",
"edit-other-user-password": "他のユーザーのパスワードを編集する",
@@ -1369,6 +1376,7 @@
"First_Channel_After_Login": "ログイン後の最初のチャンネル",
"First_response_time": "初回応答時間",
"Flags": "国旗",
+ "Follow_message": "メッセージをフォロー",
"Follow_social_profiles": "私たちのソーシャルプロファイルをフォローしたり、GitHubでフォークしたり、私たちのTrelloのボード上でRocket.Chatアプリについてのあなたの考えを共有しましょう。",
"Fonts": "フォント",
"Food_and_Drink": "食べ物と飲み物",
@@ -2026,6 +2034,8 @@
"Mobile": "モバイル",
"Mobile_Notifications_Default_Alert": "モバイル通知のデフォルトアラート",
"Monday": "月曜日",
+ "Mongo_version": "Mongo バージョン",
+ "Mongo_storageEngine": "Mongo ストレージエンジン",
"Monitor_history_for_changes_on": "履歴の変更を監視する",
"More": "もっと",
"More_channels": "その他のチャンネル",
@@ -2099,6 +2109,7 @@
"No_starred_messages": "スターをつけたメッセージはありません",
"No_such_command": "そのようなコマンドはありません: `/__command__`",
"No_discussions_yet": "まだディスカッションはありません",
+ "No_Threads": "スレッドが見つかりません",
"No_user_with_username_%s_was_found": "\"%s\" というユーザーは、見つかりませんでした!",
"Nobody_available": "誰も利用できません",
"Node_version": "Node バージョン",
@@ -2384,6 +2395,7 @@
"Remove_someone_from_room": "ルームからユーザーを削除する",
"Removed": "削除しました",
"Removed_User": "削除されたユーザー",
+ "Replied_on": "返信:",
"Replies": "返信",
"Reply": "返信",
"ReplyTo": "に返信",
@@ -2392,6 +2404,7 @@
"Report_sent": "レポートの送信",
"Report_this_message_question_mark": "このメッセージを報告?",
"Reporting": "報告",
+ "Request_comment_when_closing_conversation": "会話を閉じるときにコメントを要求する",
"Require_all_tokens": "すべてのトークンを要求する",
"Require_any_token": "任意のトークンを要求する",
"Require_password_change": "パスワードの変更を要求",
@@ -2434,6 +2447,8 @@
"RetentionPolicyRoom_OverrideGlobal": "グローバル保持ポリシーを上書きする",
"RetentionPolicyRoom_ReadTheDocs": "気を付けて! これらの設定を細心の注意を払うことなく調整すると、すべてのメッセージ履歴が破棄されます。機能を有効にする前にここ でドキュメントをお読みください。",
"Retry_Count": "再試行回数",
+ "Return_to_home": "ホームに戻る",
+ "Return_to_previous_page": "前のページに戻る",
"Robot_Instructions_File_Content": "Robots.txtファイルの内容",
"Rocket_Chat_Alert": "Rocket.Chat 警告",
"Role": "ロール",
@@ -2511,6 +2526,7 @@
"Screen_Share": "画面共有",
"Script_Enabled": "スクリプトを有効にする",
"Search": "検索",
+ "Search_Apps": "アプリを検索",
"Search_by_file_name": "ファイル名で検索",
"Search_by_username": "ユーザー名で検索",
"Search_Channels": "チャンネルを検索",
@@ -2683,19 +2699,20 @@
"Stats_Non_Active_Users": "アクティブでないユーザー",
"Stats_Offline_Users": "オフラインのユーザー",
"Stats_Online_Users": "オンラインのユーザー",
- "Stats_Total_Channels": "すべてのチャンネル",
- "Stats_Total_Direct_Messages": "すべてのダイレクトメッセージ",
- "Stats_Total_Livechat_Rooms": "総ライブチャットルーム",
- "Stats_Total_Messages": "すべてのメッセージ",
- "Stats_Total_Messages_Channel": "チャンネルの合計メッセージ",
- "Stats_Total_Messages_Direct": "ダイレクトメッセージの合計メッセージ",
- "Stats_Total_Messages_Livechat": "ライブチャットの合計メッセージ",
- "Stats_Total_Messages_PrivateGroup": "プライベートグループの合計メッセージ",
- "Stats_Total_Private_Groups": "すべてのプライベートグループ",
- "Stats_Total_Rooms": "すべてのチャットルーム",
- "Stats_Total_Users": "すべてのユーザー",
- "Stats_Total_Uploads": "合計アップロード数",
- "Stats_Total_Uploads_Size": "合計アップロードサイズ",
+ "Stats_Total_Connected_Users": "総接続ユーザー数",
+ "Stats_Total_Channels": "総チャンネル数",
+ "Stats_Total_Direct_Messages": "総ダイレクトメッセージ数",
+ "Stats_Total_Livechat_Rooms": "総ライブチャットルーム数",
+ "Stats_Total_Messages": "総メッセージ数",
+ "Stats_Total_Messages_Channel": "チャンネルの総メッセージ数",
+ "Stats_Total_Messages_Direct": "ダイレクトメッセージの総メッセージ数",
+ "Stats_Total_Messages_Livechat": "ライブチャットの総メッセージ数",
+ "Stats_Total_Messages_PrivateGroup": "プライベートグループの総メッセージ数",
+ "Stats_Total_Private_Groups": "総ライベートグループ数",
+ "Stats_Total_Rooms": "総チャットルーム数",
+ "Stats_Total_Users": "総ユーザー数",
+ "Stats_Total_Uploads": "総アップロード数",
+ "Stats_Total_Uploads_Size": "総アップロードサイズ",
"Status": "状態",
"Step": "ステップ",
"Stop_Recording": "記録を停止",
@@ -2716,6 +2733,7 @@
"Survey": "アンケート",
"Survey_instructions": "それぞれの設問にご満足度を、全く不満 1 〜 大変満足5 で評価してください。",
"Symbols": "シンボル",
+ "Sync": "同期する",
"Sync / Import": "同期とインポート",
"Sync_in_progress": "同期の進行中",
"Sync_Interval": "同期間隔",
@@ -2817,6 +2835,7 @@
"This_room_has_been_unarchived_by__username_": "このルームは__username__によってアーカイブ解除されています",
"thread": "スレッド",
"Threads": "スレッド",
+ "Thread_message": "*__username__* のメッセージへのコメント:_ __msg__ _",
"This_week": "今週",
"Thursday": "木曜日",
"Time_in_seconds": "時間を秒数で指定",
@@ -2848,9 +2867,11 @@
"Tokens_Required_Input_Placeholder": "トークンアセット名",
"Topic": "トピック",
"Total": "合計",
- "Total_conversations": "会話の合計",
- "Total_messages": "すべてのメッセージ",
- "Total_visitors": "訪問者の合計",
+ "Total_conversations": "総会話数",
+ "Total_Discussions": "総ディスカッション数",
+ "Total_messages": "総メッセージ数",
+ "Total_Threads": "総スレッド数",
+ "Total_visitors": "総訪問者数",
"Tourism": "観光",
"Transcript_Enabled": "チャットが終了した後に彼らがトランスクリプトを好きになるかどうかを訪問者に尋ねる",
"Transcript_message": "トランスクリプトについて質問するときに表示するメッセージ",
@@ -2893,6 +2914,7 @@
"unarchive-room_description": "チャンネルをアーカイブ解除する権限",
"Unblock_User": "ユーザーをブロック解除する",
"Unfavorite": "お気に入り解除",
+ "Unfollow_message": "メッセージのフォローを解除",
"Unignore": "Unignore",
"Uninstall": "アンインストール",
"Unmute_someone_in_room": "ルームのユーザーのミュート解除する",
diff --git a/packages/rocketchat-i18n/i18n/no.i18n.json b/packages/rocketchat-i18n/i18n/no.i18n.json
index 5195af4c40ef..8aa18442281f 100644
--- a/packages/rocketchat-i18n/i18n/no.i18n.json
+++ b/packages/rocketchat-i18n/i18n/no.i18n.json
@@ -1122,7 +1122,7 @@
"External_Queue_Service_URL": "Ekstern køtjeneste-URL",
"External_Service": "Ekstern tjeneste",
"Facebook_Page": "Facebook-side",
- "False": "Falsk",
+ "False": "Nei",
"Favorite_Rooms": "Aktiver favorittlokaler",
"Favorite": "Favoritt",
"Favorites": "Favoritter",
@@ -1593,7 +1593,7 @@
"Leave_Group_Warning": "Er du sikker på at du vil forlate gruppen \"%s\"?",
"Leave_Livechat_Warning": "Er du sikker på at du vil forlate livechat med \"%s\"?",
"Leave_Private_Warning": "Er du sikker på at du vil legge diskusjonen med \"%s\"?",
- "Leave_room": "La rom",
+ "Leave_room": "Forlat rom",
"Leave_Room_Warning": "Er du sikker på at du vil forlate rommet \"%s\"?",
"Leave_the_current_channel": "La den nåværende kanalen gå",
"line": "linje",
@@ -2547,7 +2547,7 @@
"Trigger_removed": "Trigger fjernet",
"Trigger_Words": "Trigger Ord",
"Triggers": "Triggers",
- "True": "ekte",
+ "True": "Ja",
"Tuesday": "tirsdag",
"Turn_OFF": "Skru av",
"Turn_ON": "Slå på",
diff --git a/packages/rocketchat-i18n/i18n/pl.i18n.json b/packages/rocketchat-i18n/i18n/pl.i18n.json
index a71ea993d2de..8d814fa21f1b 100644
--- a/packages/rocketchat-i18n/i18n/pl.i18n.json
+++ b/packages/rocketchat-i18n/i18n/pl.i18n.json
@@ -594,11 +594,11 @@
"Closed_by_visitor": "Zamknięte przez odwiedzającego",
"Closing_chat": "Zamknięcie czat",
"Cloud": "Chmura",
+ "Cloud_workspace_connected_plus_account": "Twoja przestrzeń robocza jest teraz połączona z Chmurą Rocket.Chat, a konto powiązane.",
"Cloud_connect": "Połączenie z Chmurą Rocket.Chat",
+ "Cloud_workspace_connected_without_account": "Twoja przestrzeń robocza jest teraz połączona z Chmurą Rocket.Chat. Jeśli chcesz, możesz zalogować się do Chmury Rocket.Chat i powiązać tą przestrzeń z Twoim kontem w chmurze.",
"Cloud_what_is_it": "Co to jest?",
"Cloud_what_is_it_description": "Połączenie z Chmurą Rocket.Chat pozwala Ci na złączenie Twojej lokalnej instancji Rocket.Chat z naszą chmurą. Pozwoli Ci to na zarządzanie swoimi licencjami, rachunkami oraz wsparciem w Chmurze Rocket.Chat.",
- "Cloud_workspace_connected_plus_account": "Twoja przestrzeń robocza jest teraz połączona z Chmurą Rocket.Chat, a konto powiązane.",
- "Cloud_workspace_connected_without_account": "Twoja przestrzeń robocza jest teraz połączona z Chmurą Rocket.Chat. Jeśli chcesz, możesz zalogować się do Chmury Rocket.Chat i powiązać tą przestrzeń z Twoim kontem w chmurze.",
"Cloud_login_to_cloud": "Zaloguj się do Chmury Rocket.Chat",
"Cloud_address_to_send_registration_to": "Adres do wysyłki wiadomości rejestrującej w Chmurze",
"Cloud_update_email": "Zaktualizuj email",
diff --git a/packages/rocketchat-i18n/i18n/pt-BR.i18n.json b/packages/rocketchat-i18n/i18n/pt-BR.i18n.json
index 125491c0a21c..a7f0aa2aae26 100644
--- a/packages/rocketchat-i18n/i18n/pt-BR.i18n.json
+++ b/packages/rocketchat-i18n/i18n/pt-BR.i18n.json
@@ -28,7 +28,7 @@
"Accounts_Admin_Email_Approval_Needed_Subject_Default": "Um novo usuário se registrou e precisa de aprovação",
"Accounts_Admin_Email_Approval_Needed_With_Reason_Default": "O usuário [nome] ([email]) foi registrado.
Razão: [razão]
Verifique \"Administração ->Usuários\" para ativá-lo ou excluí-lo.
",
"Accounts_AllowAnonymousRead": "Permitir Leitura Anônima",
- "Accounts_AllowAnonymousWrite": "Permitir Leitura Anônima",
+ "Accounts_AllowAnonymousWrite": "Permitir Escrita Anônima",
"Accounts_AllowDeleteOwnAccount": "Permitir que Usuários Apaguem A Própria Conta",
"Accounts_AllowedDomainsList": "Lista de Domínios Permitidos",
"Accounts_AllowedDomainsList_Description": "Lista de domínios permitidos, separados por vírgula",
@@ -343,6 +343,7 @@
"Apply_and_refresh_all_clients": "Aplicar e atualizar todos os clientes",
"Apps": "Apps",
"Apps_Engine_Version": "Versão da Apps Engine",
+ "Apps_Framework_Development_Mode": "Habilitar modo de desenvolvimento",
"Apps_Framework_enabled": "Ativar o App Framework",
"Apps_Settings": "Configurações da aplicação",
"Apps_WhatIsIt": "Apps: o que são eles?",
@@ -358,7 +359,7 @@
"Are_you_sure": "Você tem certeza?",
"Are_you_sure_you_want_to_delete_your_account": "Tem certeza de que deseja excluir a sua conta?",
"Are_you_sure_you_want_to_disable_Facebook_integration": "Tem certeza de que deseja desabilitar a integração do Facebook?",
- "Assets": "Assetes",
+ "Assets": "Recursos",
"assign-admin-role": "Atribuir papel de administrador",
"assign-admin-role_description": "Permissão para atribuir a função de administrador a outros usuários",
"Assign_admin": "Atribuindo administrador",
@@ -491,7 +492,7 @@
"CAS_version_Description": "Use apenas uma versão suportada do CAS suportada pelo seu serviço CAS SSO.",
"Categories": "Categorias",
"CDN_PREFIX": "Prefixo CDN",
- "CDN_PREFIX_ALL": "Use o prefixo CDN para todos os activos",
+ "CDN_PREFIX_ALL": "Use o prefixo da CDN para todos os recursos",
"CDN_JSCSS_PREFIX": "Prefixo CDN para JS / CSS",
"Certificates_and_Keys": "Certificados e Chaves",
"Change_Room_Type": "Mudando o Tipo de Sala",
@@ -596,11 +597,11 @@
"Closed_by_visitor": "Encerrado pelo visitante",
"Closing_chat": "Encerrando chat",
"Cloud": "Nuvem",
+ "Cloud_workspace_connected_plus_account": "Seu workspace está agora conectado ao Rocket.Chat Cloud e uma conta está associada.",
"Cloud_connect": "Rocket.Chat Cloud Connect",
+ "Cloud_workspace_connected_without_account": "Seu workspace está agora conectado ao Rocket.Chat Cloud. Se desejar, você pode fazer o login no Rocket.Chat Cloud e associar seu workspace à sua conta do Cloud.",
"Cloud_what_is_it": "O que é isso?",
"Cloud_what_is_it_description": "O Rocket.Chat Cloud Connect permite que você conecte seu workspace Rocket.Chat auto-hospedado à nossa nuvem. Fazer isso permite que você gerencie suas licenças, Faturamento e Suporte no Rocket.Chat Cloud.",
- "Cloud_workspace_connected_plus_account": "Seu workspace está agora conectado ao Rocket.Chat Cloud e uma conta está associada.",
- "Cloud_workspace_connected_without_account": "Seu workspace está agora conectado ao Rocket.Chat Cloud. Se desejar, você pode fazer o login no Rocket.Chat Cloud e associar seu workspace à sua conta do Cloud.",
"Cloud_login_to_cloud": "Login do o Rocket.Chat Cloud",
"Cloud_address_to_send_registration_to": "Endereço para enviar seu e-mail de registro na Cloud.",
"Cloud_update_email": "Atualizar email",
@@ -643,7 +644,7 @@
"conversation_with_s": "a conversa com %s",
"Convert_Ascii_Emojis": "Converter ASCII para Emoji",
"Copied": "Copiado",
- "Copy": "Cópia",
+ "Copy": "Copiar",
"Copy_to_clipboard": "Copiar para área de transferência",
"COPY_TO_CLIPBOARD": "COPIAR PARA ÁREA DE TRANSFERÊNCIA",
"could-not-access-webdav": "Não foi possível acessar o WebDAV",
@@ -1883,8 +1884,8 @@
"Make_Admin": "Tornar Administrador",
"Make_sure_you_have_a_copy_of_your_codes": "Certifique-se de ter uma cópia dos seus códigos: __codes__ Se você perder o acesso ao seu aplicativo de autenticação, você pode usar um desses códigos para fazer login.",
"manage-apps": "Gerenciar aplicativos",
- "manage-assets": "Gerenciar ativos",
- "manage-assets_description": "Permissão para gerenciar os ativos do servidor",
+ "manage-assets": "Gerenciar Recursos",
+ "manage-assets_description": "Permissão para gerenciar os recursos do servidor",
"manage-emoji": "Gerenciar Emoji",
"manage-emoji_description": "Permissão para gerenciar o servidor emojis",
"manage-integrations": "Gerenciar Integrações",
@@ -1899,7 +1900,7 @@
"Manage_the_App": "Gerencie a aplicação",
"Manager_added": "Gerente adicionado",
"Manager_removed": "Gerente removido",
- "Managing_assets": "Gerenciando assets",
+ "Managing_assets": "Gerenciando recursos",
"Managing_integrations": "Gerenciando integrações",
"Manufacturing": "Fabricação",
"MapView_Enabled": "Ativar Mapview",
@@ -2094,6 +2095,7 @@
"No_starred_messages": "Não há mensagens favoritas",
"No_such_command": "Nenhum comando desse tipo: `/ __command__`",
"No_discussions_yet": "Ainda sem discussões",
+ "No_Threads": "Nenhuma thread encontrada",
"No_user_with_username_%s_was_found": "Nenhum usuário com nome de usuário \"%s\" foi encontrado!",
"Nobody_available": "Ninguém disponível",
"Node_version": "Versão do Node",
@@ -2202,7 +2204,7 @@
"Payload": "Payload",
"Peer_Password": "Senha do par",
"People": "Pessoas",
- "Permalink": "Link-permanente",
+ "Permalink": "Link permanente",
"Permissions": "Permissões",
"Personal_Access_Tokens": "Tokens de acesso pessoal",
"Phone_number": "Telefone",
@@ -2354,7 +2356,7 @@
"Register_Server_Standalone_Service_Providers": "Crie contas com provedores de serviços",
"Register_Server_Standalone_Update_Settings": "Atualize as configurações pré-configuradas",
"Register_Server_Standalone_Own_Certificates": "Recompile os aplicativos móveis com seus próprios certificados",
- "Registration": "inscrição",
+ "Registration": "Registro",
"Registration_Succeeded": "Registrado com Sucesso",
"Registration_via_Admin": "Registro via Admin",
"Regular_Expressions": "Expressões regulares",
@@ -2841,7 +2843,9 @@
"Topic": "Tópico",
"Total": "Total",
"Total_conversations": "Total de conversas",
+ "Total_Discussions": "Total de discussões",
"Total_messages": "Quantidade de Mensagens",
+ "Total_Threads": "Total de tópicos",
"Total_visitors": "Total de visitantes",
"Tourism": "Turismo",
"Transcript_Enabled": "Pergunte ao visitante se eles gostariam de uma transcrição após o bate-papo fechado",
@@ -3169,4 +3173,4 @@
"Your_question": "A sua pergunta",
"Your_server_link": "O link do seu servidor",
"Your_workspace_is_ready": "O seu espaço de trabalho está pronto a usar 🎉"
-}
+}
\ No newline at end of file
diff --git a/packages/rocketchat-i18n/i18n/pt.i18n.json b/packages/rocketchat-i18n/i18n/pt.i18n.json
index ecede69410b8..836f9e548cdc 100644
--- a/packages/rocketchat-i18n/i18n/pt.i18n.json
+++ b/packages/rocketchat-i18n/i18n/pt.i18n.json
@@ -596,11 +596,11 @@
"Closed_by_visitor": "Encerrado pelo convidado",
"Closing_chat": "A encerrar chat",
"Cloud": "Nuvem",
+ "Cloud_workspace_connected_plus_account": "O seu espaço de trabalho está agora conectado ao Rocket.Chat Cloud e uma conta está associada.",
"Cloud_connect": "Rocket.Chat Cloud Connect",
+ "Cloud_workspace_connected_without_account": "O seu espaço de trabalho está agora conectado ao Rocket.Chat Cloud. ",
"Cloud_what_is_it": "O que é isto?",
"Cloud_what_is_it_description": "O Rocket.Chat Cloud Connect permite que se conecte ao seu espaço de trabalho Rocket.Chat auto-hospedado na nossa nuvem. Fazer isto permite que gerencie as suas licenças, facturação e Suporte no Rocket.Chat Cloud.",
- "Cloud_workspace_connected_plus_account": "O seu espaço de trabalho está agora conectado ao Rocket.Chat Cloud e uma conta está associada.",
- "Cloud_workspace_connected_without_account": "O seu espaço de trabalho está agora conectado ao Rocket.Chat Cloud. ",
"Cloud_login_to_cloud": "Login do o Rocket.Chat Cloud",
"Cloud_address_to_send_registration_to": "O endereço do seu e-mail para envio do registo na Cloud.",
"Cloud_update_email": "Actualizar email",
diff --git a/packages/rocketchat-i18n/i18n/ru.i18n.json b/packages/rocketchat-i18n/i18n/ru.i18n.json
index d955bcccd22a..c9b2dd96c597 100644
--- a/packages/rocketchat-i18n/i18n/ru.i18n.json
+++ b/packages/rocketchat-i18n/i18n/ru.i18n.json
@@ -583,11 +583,11 @@
"Closed_by_visitor": "Закрыто посетителем",
"Closing_chat": "Закрыть чат",
"Cloud": "Облако",
+ "Cloud_workspace_connected_plus_account": "Ваше рабочее пространство теперь подключено к облаку Rocket.Chat, и учетная запись связана.",
"Cloud_connect": "Rocket.Chat Cloud Connect",
+ "Cloud_workspace_connected_without_account": "Ваше рабочее пространство теперь подключено к облаку Rocket.Chat. При желании вы можете войти в Rocket.Chat Cloud и связать свое рабочее пространство с учетной записью Cloud.",
"Cloud_what_is_it": "Что это?",
"Cloud_what_is_it_description": "Rocket.Chat Cloud Connect позволяет вам подключить свое собственное рабочее пространство Rocket.Chat к нашему облаку. Это позволяет вам управлять своими лицензиями, выставлением счетов и поддержкой в Rocket.Chat Cloud.",
- "Cloud_workspace_connected_plus_account": "Ваше рабочее пространство теперь подключено к облаку Rocket.Chat, и учетная запись связана.",
- "Cloud_workspace_connected_without_account": "Ваше рабочее пространство теперь подключено к облаку Rocket.Chat. При желании вы можете войти в Rocket.Chat Cloud и связать свое рабочее пространство с учетной записью Cloud.",
"Cloud_login_to_cloud": "Войти в Rocket.Chat Cloud",
"Cloud_address_to_send_registration_to": "Адрес для отправки электронного письма с регистрацией в облаке.",
"Cloud_update_email": "Обновить электронную почту",
@@ -2245,6 +2245,7 @@
"Public_Channel": "Открытый канал",
"Public_Community": "Открытое сообщество",
"Public_Relations": "Связи с общественностью",
+ "Purchased": "Покупка",
"Push": "Push уведомления",
"Push_Setting_Requires_Restart_Alert": "После изменения этого параметра потребуется перезапустить Rocket.Chat.",
"Push_apn_cert": "APN сертификат",
@@ -2263,6 +2264,8 @@
"Push_show_message": "Показать сообщение в уведомлении",
"Push_show_username_room": "Показывать канал/группу/имя пользователя в уведомлении",
"Push_test_push": "Тест",
+ "Purchase_for_free": "Покупка БЕСПЛАТНО",
+ "Purchase_for_price": "Покупка за $%s",
"Query": "Запрос",
"Query_description": "Дополнительные условия для определения того, каким пользователям отправлять электронную почту. Отписавшиеся пользователи, автоматически удаляются из запроса. Должен быть валидным JSON. Например: \"{\"createdAt\":{\"$gt\":{\"$date\": \"2015-01-01T00:00:00.000Z\"}}}\"",
"Queue": "Очередь",
@@ -2377,6 +2380,7 @@
"RetentionPolicyRoom_OverrideGlobal": "Отменить глобальную политику хранения",
"RetentionPolicyRoom_ReadTheDocs": "Внимание! Изменение этих настроек без особой осторожноти может уничтожить всю историю сообщений. Прежде чем включать эту функцию, пожалуйста, прочтите документацию здесь .",
"Retry_Count": "Число повторных попыток",
+ "Return_to_home": "Вернуться домой",
"Robot_Instructions_File_Content": "Содержание файла Robots.txt",
"Rocket_Chat_Alert": "Rocket.Chat Alert",
"Role": "Роль",
@@ -2454,6 +2458,7 @@
"Screen_Share": "Демонстрация экрана",
"Script_Enabled": "Использовать скрипт",
"Search": "Поиск",
+ "Search_Apps": "Поиск приложений",
"Search_by_file_name": "Поиск по имени файла",
"Search_by_username": "Поиск по логину",
"Search_Channels": "Поиск каналов",
@@ -2743,6 +2748,7 @@
"This_month": "Этот месяц",
"This_room_has_been_archived_by__username_": "Комната была архивирована __username__",
"This_room_has_been_unarchived_by__username_": "__username__ вернул этот чат из архива",
+ "thread": "Обсуждение",
"Threads": "Потоки",
"This_week": "На этой неделе",
"Thursday": "Четверг",
@@ -2756,6 +2762,7 @@
"Today": "Сегодня",
"To_additional_emails": "Дополнительные email адресаты",
"To_install_RocketChat_Livechat_in_your_website_copy_paste_this_code_above_the_last_body_tag_on_your_site": "Для того, чтобы установить Rocket.Chat Livechat на вашем сайте, скопируйте и вставьте этот код выше последнего </body> тега на вашем сайте.",
+ "To_install_the_new_version_of_RocketChat_Livechat_in_your_website_copy_paste_this_code_above_the_last_body_tag_on_your_site": "Чтобы установить новую версию Rocket.Chat Livechat на свой веб-сайт, скопируйте & вставьте этот код над последним тегом < / body > на своем сайте.",
"to_see_more_details_on_how_to_integrate": "чтобы увидеть более подробную информацию о том, как интегрировать.",
"To_users": "Пользователям",
"Toggle_original_translated": "Переключить оригинал/перевод",
diff --git a/packages/rocketchat-i18n/i18n/tr.i18n.json b/packages/rocketchat-i18n/i18n/tr.i18n.json
index 58dcc48b8a16..cc8211f4c687 100644
--- a/packages/rocketchat-i18n/i18n/tr.i18n.json
+++ b/packages/rocketchat-i18n/i18n/tr.i18n.json
@@ -2,7 +2,7 @@
"403": "Yasak",
"500": "İç Sunucu Hatası",
"#channel": "#kanal",
- "0_Errors_Only": "0 - Sadece Hatalar",
+ "0_Errors_Only": "0 - Yalnızca Hatalar",
"12_Hour": "12 Saat",
"1_Errors_and_Information": "1 - Hatalar ve Bilgi",
"24_Hour": "24 saat",
@@ -17,16 +17,16 @@
"Accept_with_no_online_agents": "Çevrimiçi 'Firma Temsilcisi' Olmadan da Kabul Et",
"access-mailer": "Posta Göndericisine Erişim",
"access-mailer_description": "Kullanıcılara toplu e-posta gönderme izni",
- "access-permissions": "İzinler Ekranına Giriş",
+ "access-permissions": "İzinler Ekranına Erişim",
"access-permissions_description": "Belirli roller için izinleri değiştir",
"Access_not_authorized": "Yetkisiz erişim",
- "Access_Token_URL": "Erişim anahtarı URL",
+ "Access_Token_URL": "Erişim Belirteci URL'si",
"Accessing_permissions": "Erişim izinleri",
"Account_SID": "Hesap SID",
"Accounts": "Hesaplar",
- "Accounts_Admin_Email_Approval_Needed_Default": "[name] ([email]) kullanıcısı kaydedildi.
Lütfen etkinleştirmek veya silmek için \"Yönetim ->Kullanıcılar\" ı kontrol edin.
",
- "Accounts_Admin_Email_Approval_Needed_Subject_Default": "Yeni bir kullanıcı kayıtlı ve onay gerektiriyor",
- "Accounts_Admin_Email_Approval_Needed_With_Reason_Default": "[name] ([email]) kullanıcısı kaydedildi.
Sebep: [reason]
Lütfen etkinleştirmek veya silmek için \"Yönetim ->Kullanıcılar\" ı kontrol edin.
",
+ "Accounts_Admin_Email_Approval_Needed_Default": "[name] ([email]) kullanıcısı kaydoldu.
Lütfen etkinleştirmek veya silmek için \"Yönetim -> Kullanıcılar\"ı kontrol edin.
",
+ "Accounts_Admin_Email_Approval_Needed_Subject_Default": "Yeni bir kullanıcı kaydoldu ve onay bekliyor",
+ "Accounts_Admin_Email_Approval_Needed_With_Reason_Default": "[name] ([email]) kullanıcısı kaydoldu.
Sebep: [reason]
Lütfen etkinleştirmek veya silmek için \"Yönetim -> Kullanıcılar\"ı kontrol edin.
",
"Accounts_AllowAnonymousRead": "Anonim okumaya izin ver",
"Accounts_AllowAnonymousWrite": "Anonim yazmaya izin ver",
"Accounts_AllowDeleteOwnAccount": "Kullanıcıların kendi hesaplarını silmelerine izin ver",
@@ -40,10 +40,10 @@
"Accounts_AllowUserProfileChange": "Kullanıcının Profilini Değiştirmesine izin ver",
"Accounts_AvatarBlockUnauthenticatedAccess": "Avatarlara Kimliği Doğrulanmamış Erişimi Engelle",
"Accounts_AvatarCacheTime": "Avatar önbellek zamanı",
- "Accounts_AvatarCacheTime_description": "Http protokolünün avatar görüntülerini önbelleğe alması için söylendiği saniye sayısı.",
- "Accounts_AvatarResize": "Profil Resimlerini yeniden boyutlandır",
- "Accounts_AvatarSize": "Profil Resmi Boyutu",
- "Accounts_AvatarExternalProviderUrl": "Dışarıdan Avatar Sağlayıcı URL'i",
+ "Accounts_AvatarCacheTime_description": "HTTP protokolünün avatar görsellerini önbelleğe kaydetmesini söyleyeceği saniye",
+ "Accounts_AvatarResize": "Avatarlar Yeniden Boyutlandırılsın",
+ "Accounts_AvatarSize": "Avatar Boyutu",
+ "Accounts_AvatarExternalProviderUrl": "Avatar için Dış Sağlayıcı URL'si",
"Accounts_AvatarExternalProviderUrl_Description": "Örnek: `https://acme.com/api/v1/{username}`",
"Accounts_BlockedDomainsList": "Engellenen Alanlar Listesi",
"Accounts_BlockedDomainsList_Description": "Engellenen alanların virgülle ayrılmış listesi",
@@ -52,7 +52,7 @@
"Accounts_CustomFields_Description": "Geçerli bir JSON olmalı; anahtar sözcükler, alan ayarları sözlüğünü içeren alan adlarıdır. Örnek:{\n \"role\": {\n \"type\": \"select\",\n \"defaultValue\": \"student\",\n \"options\": [\"teacher\", \"student\"],\n \"required\": true,\n \"modifyRecordField\": {\n \"array\": true,\n \"field\": \"roles\"\n }\n },\n \"twitter\": {\n \"type\": \"text\",\n \"required\": true,\n \"minLength\": 2,\n \"maxLength\": 10\n }\n}
",
"Accounts_CustomFieldsToShowInUserInfo": "Kullanıcı Bilgilerini Göstermek için Özel alanlar",
"Accounts_Default_User_Preferences": "Varsayılan Kullanıcı Tercihleri",
- "Accounts_Default_User_Preferences_audioNotifications": "Sesli Mesaj için Varsayılan Uyarı",
+ "Accounts_Default_User_Preferences_audioNotifications": "Sesli İleti için Varsayılan Uyarı",
"Accounts_Default_User_Preferences_desktopNotifications": "Masaüstü Bildirimleri Varsayılan Uyarı",
"Accounts_Default_User_Preferences_mobileNotifications": "Mobil Bildirim Varsayılan Uyarısı",
"Accounts_Default_User_Preferences_not_available": "Kullanıcı Tercihleri'ni henüz kullanıcı tarafından ayarlanmadığı için almadı.",
@@ -65,7 +65,7 @@
"Accounts_Email_Deactivated": "[name]Hesabınız devre dışı bırakıldı.
",
"Accounts_Enrollment_Email_Default": "[Site_Name] sitesine hoşgeldiniz![Site_URL] 'a gidin ve şu an mevcut en iyi açık kaynak sohbet çözümünü deneyin!
",
"Accounts_Email_Deactivated_Subject": "Hesap devre dışı",
- "Accounts_EmailVerification": "E-Posta Doğrulama",
+ "Accounts_EmailVerification": "E-posta doğrulaması etkin",
"Accounts_EmailVerification_Description": "Bu özelliği kullanmak için doğru SMTP ayarlarına sahip olduğunuza emin olun",
"Accounts_Enrollment_Email_Subject_Default": "[Site_Name] sitesine hoşgeldiniz!",
"Accounts_Enrollment_Email": "Kayıt E-Postası",
@@ -76,7 +76,7 @@
"Accounts_iframe_enabled": "Etkin",
"Accounts_iframe_url": "iframe URL",
"Accounts_LoginExpiration": "Oturum Sonlanma Süresi (Gün)",
- "Accounts_ManuallyApproveNewUsers": "Yeni kullanıcıları elle onayla",
+ "Accounts_ManuallyApproveNewUsers": "Yeni kullanıcılar elle onaylansın",
"Accounts_OAuth_Custom_Authorize_Path": "Yol Yetkilendirme",
"Accounts_OAuth_Custom_Button_Color": "Tuş Rengi",
"Accounts_OAuth_Custom_Button_Label_Color": "Tuş Metin Rengi",
@@ -164,34 +164,34 @@
"Accounts_Password_Policy_MaxLength_Description": "Parolaların bu miktarda karakterden daha fazla olmamasını sağlar. Devre dışı bırakmak için `-1` kullanın.",
"Accounts_Password_Policy_MinLength": "Minimum uzunluk",
"Accounts_Password_Policy_MinLength_Description": "Şifrelerin en azından bu miktarda karakter içermesi gerekir. Devre dışı bırakmak için `-1` kullanın.",
- "Accounts_PasswordReset": "Parola Sıfırlama",
- "Accounts_Registration_AuthenticationServices_Default_Roles": "Doğrulama Servisi için Varsayılan İşlem",
- "Accounts_Registration_AuthenticationServices_Default_Roles_Description": "Kimlik doğrulama servislerini kullanarak kayıt olan kullanıcılara verilecek varsayılan roller (virgülle ayrılmış olmalıdır)",
- "Accounts_Registration_AuthenticationServices_Enabled": "Kimlik Doğrulama Hizmetleri ile Kayıt Olma",
+ "Accounts_PasswordReset": "Parola sıfırlama etkin",
+ "Accounts_Registration_AuthenticationServices_Default_Roles": "Kimlik Doğrulama Hizmetleri için varsayılan roller",
+ "Accounts_Registration_AuthenticationServices_Default_Roles_Description": "Kimlik doğrulama hizmetlerini kullanarak kayıt olan kullanıcılara verilecek varsayılan roller (virgülle ayrılmış olmalıdır)",
+ "Accounts_Registration_AuthenticationServices_Enabled": "Kimlik Doğrulama Hizmetleri ile kayıt etkin",
"Accounts_RegistrationForm": "Kayıt formu",
"Accounts_RegistrationForm_Disabled": "Devre Dışı",
"Accounts_RegistrationForm_LinkReplacementText": "Kayıt Formu Bağlantı Değişim Metni",
"Accounts_RegistrationForm_Public": "Genel",
"Accounts_RegistrationForm_Secret_URL": "Gizli URL",
- "Accounts_RegistrationForm_SecretURL": "Kayıt Formu Gizli URL",
+ "Accounts_RegistrationForm_SecretURL": "Kayıt Formu için Gizli URL",
"Accounts_RegistrationForm_SecretURL_Description": "Kayıt URL'sine eklenmesi için rastgele bir harf dizisi sağlamalısınız. Örnek: https://open.rocket.chat/register/[secret_hash]",
- "Accounts_RequireNameForSignUp": "Üye Olmak İçin Ad İste",
- "Accounts_RequirePasswordConfirmation": "Şifre Doğrulaması Gerektir",
+ "Accounts_RequireNameForSignUp": "Kaydolmak için ad gerekli olsun",
+ "Accounts_RequirePasswordConfirmation": "Şifre doğrulaması gerekli olsun",
"Accounts_SearchFields": "Arama sırasında kullanılacak alanlar",
- "Accounts_Send_Email_When_Activating": "Kullanıcı etkinleştirildiğinde kullanıcıya e-posta gönder",
- "Accounts_Send_Email_When_Deactivating": "Kullanıcı devre dışı bırakıldığında kullanıcıya e-posta gönder",
+ "Accounts_Send_Email_When_Activating": "Kullanıcı etkinleştirildiğinde kullanıcıya e-posta gönderilsin",
+ "Accounts_Send_Email_When_Deactivating": "Kullanıcı devre dışı bırakıldığında kullanıcıya e-posta gönderilsin",
"Accounts_Directory_DefaultView": "Varsayılan Dizin Listeleme",
"Accounts_SetDefaultAvatar": "Varsayılanı Avatar Seç",
"Accounts_SetDefaultAvatar_Description": "Varsayılan avatarın OAuth Hesabı'na veya Gravatar'a dayalı olarak belirlenmeye çalışılması",
"Accounts_ShowFormLogin": "Form-tabanlı Girişi Göster",
- "Accounts_TwoFactorAuthentication_Enabled": "İki Faktörlü Kimlik Doğrulamayı Etkinleştir",
+ "Accounts_TwoFactorAuthentication_Enabled": "İki Aşamalı Kimlik Doğrulamayı Etkinleştir",
"Accounts_TwoFactorAuthentication_MaxDelta": "Maksimum delta",
"Accounts_UserAddedEmail_Default": "[Site_Name] sitesine hoşgeldiniz![Site_URL] 'a gidin ve şu an mevcut en iyi açık kaynak sohbet çözümünü deneyin!
E-mail [email] ve Şifre: [password] 'nizi kullanarak giriş yapabilirsiniz. İlk girişinizden sonra bunları değiştirmeniz gerekebilir.",
"Accounts_TwoFactorAuthentication_MaxDelta_Description": "Maksimum Delta, herhangi bir zamanda kaç jetonun geçerli olduğunu belirler. Jetonlar her 30 saniyede bir üretilir ve (30 * Maksimum Delta) saniye için geçerlidir. Örnek: Maksimum Delta değeri 10'a ayarlandığında, her simge zaman damgasından 300 saniye önce veya sonra kullanılabilir. Bu, istemcinin saati sunucuyla düzgün bir şekilde senkronize edilmediğinde kullanışlıdır.",
"Accounts_UseDefaultBlockedDomainsList": "Standart Engelli Domain Listesini Kullan",
"Accounts_UseDNSDomainCheck": "DNS Alan Adı Kontrolü'nü Kullan",
"Accounts_UserAddedEmailSubject_Default": "[Site_Name] adlı siteye eklendiniz",
- "Accounts_UserAddedEmail_Description": "
Kullanıcının tam adı, ad veya soyadı için sırasıyla [name], [fname], [lname] kullanabilirsiniz. Kullanıcının e-posta'sı için [email]. Kullanıcının parolası için [password]. Site adı ve Site URL'si için de [Site_Name] ve [Site_URL] kullanabilirsiniz. ",
+ "Accounts_UserAddedEmail_Description": " Kullanıcının tam adı, ad veya soyadı içrma Temsilcisi' Olmadan da Kabul Etin sırasıyla [name], [fname], [lname] kullanabilirsiniz. Kullanıcının e-posta'sı için [email]. Kullanıcının parolası için [password]. Site adı ve Site URL'si için de [Site_Name] ve [Site_URL] kullanabilirsiniz. ",
"Activate": "Etkinleştir",
"Activity": "Etkinlik",
"Add": "Ekle",
@@ -240,9 +240,9 @@
"All_added_tokens_will_be_required_by_the_user": "Eklenen tüm işaretçiler kullanıcı tarafından gerekli olacaktır",
"All_channels": "Tüm kanallar",
"All_logs": "Tüm Kayıtlar",
- "All_messages": "Tüm mesajlar",
+ "All_messages": "Tüm iletiler",
"All_users": "Tüm kullanıcılar",
- "All_users_in_the_channel_can_write_new_messages": "Kanaldaki tüm kullanıcılar yeni mesaj yazabilir.",
+ "All_users_in_the_channel_can_write_new_messages": "Kanaldaki tüm kullanıcılar yeni ileti yazabilir",
"Allow_collect_and_store_HTTP_header_informations": "HTTP header bilgilerini toplamaya ve depolamaya izin ver",
"Allow_collect_and_store_HTTP_header_informations_description": "Bu ayar, Canlı Sohbet'in IP adresi, Kullanıcı-Temsilci vs. gibi HTTP header verisinden toplanan bilgileri depolamaya izin verip vermeyeceğini belirler.",
"Allow_Invalid_SelfSigned_Certs": "Kendinden İmzalı Sertifikalara İzin Ver",
@@ -255,16 +255,16 @@
"Analytics": "Mantıksal Analiz",
"Analytics_features_enabled": "Özellikler Etkin",
"Analytics_features_messages_Description": "Bir kullanıcının iletilerde yaptığı eylemlerle bağlantılı özel olayları izler.",
- "Analytics_features_rooms_Description": "Bir kanal veya grup (silme, ayrılma, oluşturma) üzerindeki eylemlerle bağlantılı özel olayları izler.",
- "Analytics_features_users_Description": "Kullanıcılar (Parola sıfırlama süreleri, profil resmi değiştirme, vb) ile ilgili eylemlerle bağlantılı özel olayları izler.",
+ "Analytics_features_rooms_Description": "Kanal veya gruplar üzerindeki eylemlerle (oluşturma, ayrılma, silme gibi) ilgili özel etkinlikleri izler.",
+ "Analytics_features_users_Description": "Kullanıcılara ilişkin eylemlerle (şifre sıfırlama süreleri, profil resmi değiştirme vb.) ilgili özel olayları izler.",
"Analytics_Google": "Google Analytics",
"Analytics_Google_id": "İzleme Kimliği",
"and": "ve",
- "And_more": "Ve __length__ kadar daha fazla",
+ "And_more": "Ve __length__ daha",
"Animals_and_Nature": "Hayvanlar ve Doğa",
"Announcement": "Duyuru",
"API": "API",
- "API_Add_Personal_Access_Token": "Yeni Kişisel Erişim Simgesi ekle",
+ "API_Add_Personal_Access_Token": "Yeni Kişisel Erişim Belirteci ekle",
"API_Allow_Infinite_Count": "Herşeye Ulaşmaya İzin Ver",
"API_Allow_Infinite_Count_Description": "REST API çağrılarının her şeyi tek bir çağrıya geri döndürmesine izin verilmesi gerekir mi?",
"API_Analytics": "Mantıksal Analiz",
@@ -284,20 +284,20 @@
"API_EmbedSafePorts": "Güvenli Portlar",
"API_EmbedSafePorts_Description": "Önizleme için izin verilen portların virgülle ayrılmış listesi.",
"API_Enable_CORS": "CORS'u etkinleştir",
- "API_Enable_Direct_Message_History_EndPoint": "Doğrudan Mesaj Geçmişi Son Nokta'nı Etkinleştir",
+ "API_Enable_Direct_Message_History_EndPoint": "Doğrudan İleti Geçmişi Noktası Etkin",
"API_Enable_Direct_Message_History_EndPoint_Description": "Bu, `/ api / v1 / im.history.others` aygıtının, diğer kullanıcıların gönderdikleri ve doğrudan arayan kişinin parçası olmadığı doğrudan iletileri görüntülemesini sağlar.",
"API_Enable_Rate_Limiter_Dev": "Geliştirmede Oran Sınırlayıcıyı etkinleştir",
- "API_Enable_Personal_Access_Tokens": "Kişisel Erişim belirteçlerini REST API'sine etkinleştir",
+ "API_Enable_Personal_Access_Tokens": "REST API'ye Kişisel Erişim belirteçlerini etkinleştir",
"API_Enable_Personal_Access_Tokens_Description": "REST API'sı ile kullanmak için kişisel erişim belirteçlerini etkinleştirin",
"API_Enable_Shields": "Kalkanları Etkinleştir",
"API_Enable_Shields_Description": "`/ Api / v1 / shield.svg` alanındaki kalkanları etkinleştir",
"API_GitHub_Enterprise_URL": "Sunucu URL'si",
"API_GitHub_Enterprise_URL_Description": "Örnek: http://domain.com (sondaki eğik çizgiyi dahil etmeyin)",
"API_Gitlab_URL": "GitLab URL",
- "API_Personal_Access_Token_Name": "Kişisel Erişim Belirteçi Adı",
+ "API_Personal_Access_Token_Name": "Kişisel Erişim Belirteci Adı",
"API_Personal_Access_Tokens_To_REST_API": "REST API'sine kişisel erişim belirteçleri",
"API_Personal_Access_Tokens_Remove_Modal": "Bu kişisel erişim kodunu kaldırmak istediğinizden emin misiniz?",
- "API_Personal_Access_Token_Generated": "Kişisel Erişim belirteçi başarıyla oluşturuldu",
+ "API_Personal_Access_Token_Generated": "Kişisel Erişim Belirteci başarıyla oluşturuldu",
"API_Personal_Access_Token_Generated_Text_Token_s_UserId_s": "Lütfen daha sonra görüntüleyemeyeceğiniz için belirtecinizi dikkatli bir şekilde kaydedin. Simgesi: __token__ Kullanıcı kimliği: __userId__ ",
"API_Personal_Access_Tokens_Regenerate_Modal": "Belirtecinizi kaybettiyseniz veya unuttuysanız yeniden oluşturabilirsiniz, ancak bu belirteci kullanan tüm uygulamaların güncellenmesi gerektiğini unutmayın.",
"API_Personal_Access_Tokens_Regenerate_It": "Belirteçi yeniden oluştur",
@@ -337,6 +337,8 @@
"Apply_and_refresh_all_clients": "Uygulayın ve tüm istemcilerin yenilemek",
"Apps": "Uygulamalar",
"Apps_Engine_Version": "Uygulamalar Motoru Sürümü",
+ "Apps_Framework_Development_Mode": "Geliştirme modunu etkinleştir",
+ "Apps_Framework_Development_Mode_Description": "Geliştirme modu, Rocket.Chat'in Marketplace'te olmayan uygulamaların kurulmasına izin verir.",
"Apps_Framework_enabled": "Uygulama Çerçevesini Etkinleştirin",
"Apps_Settings": "Uygulamanın Ayarları",
"Apps_WhatIsIt": "Uygulamalar: Ne?",
@@ -363,10 +365,10 @@
"Attachment_File_Uploaded": "Dosya Yüklendi",
"Attribute_handling": "Özellik işleme",
"Audio": "Ses",
- "Audio_message": "Sesli Mesaj",
+ "Audio_message": "Sesli ileti",
"Audio_Notification_Value_Description": "Herhangi bir özel ses olabilir veya varsayılan sesler olabilir: bip, chelle, ding, damla, highbell, mevsim",
- "Audio_Notifications_Default_Alert": "Sesli Mesaj için Varsayılan Uyarı",
- "Audio_Notifications_Value": "Varsayılan mesaj bildirim sesi",
+ "Audio_Notifications_Default_Alert": "Sesli Bildirimler için Varsayılan Uyarı",
+ "Audio_Notifications_Value": "Varsayılan İleti Bildirim Sesi",
"Auth_Token": "Kimlik Doğrulama Jetonu",
"Authentication": "Doğrulama",
"Author": "Yazar",
@@ -375,7 +377,7 @@
"Authorize": "Yetki vermek",
"auto-translate": "Otomatik Çeviri",
"auto-translate_description": "Otomatik tercüme aracı kullanım izni",
- "Auto_Load_Images": "Fotoğrafları Otomatik Yükle",
+ "Auto_Load_Images": "Görseller Otomatik Olarak Yüklensin",
"Auto_Translate": "Otomatik Çeviri",
"AutoLinker": "Otomatik Bağlantı",
"AutoLinker_Email": "AutoLinker E-posta",
@@ -421,7 +423,7 @@
"Backup_codes": "Yedek kodlar",
"ban-user": "Kullanıcıyı Engelle",
"ban-user_description": "Kanaldan bir kullanıcıyı engelleme izni",
- "Beta_feature_Depends_on_Video_Conference_to_be_enabled": "Beta özellik. Görüntülü konferans görüşmesinin aktif olmasını gerektirir.",
+ "Beta_feature_Depends_on_Video_Conference_to_be_enabled": "Beta özellik. Görüntülü Görüşme görüşmesinin aktif olmasını gerektirir.",
"Best_first_response_time": "En iyi ilk yanıt süresi",
"Block_User": "Kullanıcıyı Engelle",
"Blockchain": "Blockchain",
@@ -433,7 +435,7 @@
"bot_request": "Bot İsteği",
"BotHelpers_userFields": "Kullanıcı Alanları",
"BotHelpers_userFields_Description": "Botlar tarafından erişilebilir, kullanıcı alanlarını içeren bir CSV dosyası.",
- "Bots": "botlar",
+ "Bots": "Botlar",
"Branch": "Branş",
"Broadcast_channel": "Yayın Kanalı",
"Broadcast_channel_Description": "Sadece yetkili kullanıcılar yeni mesajlar yazabilir, ancak diğer kullanıcılar cevaplayabilir.",
@@ -443,12 +445,13 @@
"Broadcasting_client_secret": "Yayın İstemci Anahtarı",
"Broadcasting_enabled": "Yayın Etkin",
"Broadcasting_media_server_url": "Yayın Ortamı Sunucu URL'i",
+ "Browse_Files": "Dosyalara Göz At",
"Bugsnag_api_key": "Bugsnag API Anahtarı",
"Build_Environment": "Çevre Kurmak",
- "bulk-create-c": "Toplu Kanal Oluşturma",
- "bulk-create-c_description": "Toplu olarak topluluk oluşturma izni",
- "bulk-register-user": "Toplu Kanal Oluşturma",
- "bulk-register-user_description": "Toplu olarak topluluk oluşturma izni",
+ "bulk-create-c": "Toplu Kanal Oluştur",
+ "bulk-create-c_description": "Toplu olarak kanal oluşturma izni",
+ "bulk-register-user": "Toplu Kullanıcı Oluştur",
+ "bulk-register-user_description": "Toplu olarak kullanıcı oluşturma izni",
"Busiest_day": "En Meşgul Olunan Gün",
"Busiest_time": "En Meşgul Olunan Saat",
"busy": "meşgul",
@@ -497,9 +500,9 @@
"Channel_already_Unarchived": "adı `#%s` ile Kanal arşivlenmemiş durumda zaten",
"Channel_Archived": "adı `#%s` ile Kanal başarıyla arşivlenen olmuştur",
"Channel_created": "Kanal `#%s` oluşturuldu.",
- "Channel_doesnt_exist": "`#%s` isminde bir kanal bulunmamaktadır.",
+ "Channel_doesnt_exist": "`#%s` adında bir kanal bulunmamaktadır.",
"Channel_name": "Kanal Adı",
- "Channel_Name_Placeholder": "Lütfen kanal ismini giriniz...",
+ "Channel_Name_Placeholder": "Lütfen kanal adını girin...",
"Channel_to_listen_on": "Dinlenecek kanal",
"Channel_Unarchived": "adı `#%s` ile Kanal başarıyla Arşivlenmedi olmuştur",
"Channels": "Kanallar",
@@ -507,6 +510,7 @@
"Channels_list": "Kanallar",
"Chat_button": "sohbet düğmesi",
"Chat_closed": "sohbet kapalı",
+ "Chat_closed_by_agent": "Görüşme temsilci tarafından sonlandırıldı",
"Chat_closed_successfully": "Sohbet başarıyla kapatıldı",
"Chat_Now": "Şimdi konuş",
"Chat_window": "sohbet penceresi",
@@ -535,13 +539,13 @@
"Chatpal_Get_more_information_about_chatpal_on_our_website": "Chatpal hakkında daha fazla bilgi edinmek için http://chatpal.io !",
"Chatpal_go_to_message": "Atlama",
"Chatpal_go_to_room": "Atlama",
- "Chatpal_go_to_user": "Doğrudan mesaj gönder",
+ "Chatpal_go_to_user": "Doğrudan ileti gönder",
"Chatpal_HTTP_Headers": "Http Başlıkları",
"Chatpal_HTTP_Headers_Description": "HTTP Üstbilgileri listesi, her satırda bir başlık. Biçim: isim: değer",
"Chatpal_Main_Language": "Ana dil",
"Chatpal_Main_Language_Description": "Konuşmalarda en çok kullanılan dil",
- "Chatpal_Messages": "Mesajlar",
- "Chatpal_Messages_Only": "Mesajlar",
+ "Chatpal_Messages": "İletiler",
+ "Chatpal_Messages_Only": "İletiler",
"Chatpal_More": "Daha",
"Chatpal_No_Results": "Hiç sonuç yok",
"Chatpal_no_search_results": "Sonuç yok",
@@ -561,39 +565,43 @@
"Chatpal_Window_Size": "Dizin Penceresi Boyutu",
"Chatpal_Window_Size_Description": "Dizin pencerelerinin saat cinsinden boyutu (önyüklemede)",
"Choose_a_room": "Bir oda seç",
- "Choose_messages": "mesajları seçin",
+ "Choose_messages": "İletileri seç",
"Choose_the_alias_that_will_appear_before_the_username_in_messages": "Mesajlarda kullanıcı adı önce görünecektir takma seçin.",
"Choose_the_username_that_this_integration_will_post_as": "Bu bütünleşme olarak yayınlayacağız adını seçin.",
"clean-channel-history": "Kanal Geçmişini Temizle",
- "clean-channel-history_description": "Geçmişin kanallardan silinmesine izin",
+ "clean-channel-history_description": "Kanallardan geçmişin silinmesi izni",
"Clean_Usernames": "Kullanıcı adlarını temizle",
"clear": "Temizle",
- "Clear_all_unreads_question": "Tüm okunmayanlar temizlensin mi?",
- "clear_cache_now": "Önbelleği şimdi temizle",
- "clear_history": "Geçmişi temizle",
- "Click_here": "Buraya Tıkla",
+ "Clear_all_unreads_question": "Tüm okunmamışlar temizlensin mi?",
+ "clear_cache_now": "Önbelleği Şimdi Temizle",
+ "clear_history": "Geçmişi Temizle",
+ "Click_here": "Buraya tıkla",
"Click_here_for_more_info": "Daha fazla bilgi için buraya tıklayınız",
"Click_here_to_enter_your_encryption_password": "Şifreleme parolanızı girmek için buraya tıklayın",
"Click_here_to_view_and_copy_your_password": "Şifrenizi görüntülemek ve kopyalamak için buraya tıklayın",
- "Click_the_messages_you_would_like_to_send_by_email": "E-posta ile göndermek istediğiniz mesajları tıklayın.",
- "Click_to_join": "Katılmak için tıklayınız!",
- "Client_ID": "Müşteri Kimliği",
- "Client_Secret": "Müşteri Sırrı",
- "Clients_will_refresh_in_a_few_seconds": "Müşteriler birkaç saniye içinde yenilenir",
+ "Click_the_messages_you_would_like_to_send_by_email": "E-posta ile göndermek istediğiniz iletilere tıklayın.",
+ "Click_to_join": "Görüşmeye Katıl!",
+ "Client_ID": "İstemci Kimliği",
+ "Client_Secret": "İstemci Gizli Kodu",
+ "Clients_will_refresh_in_a_few_seconds": "İstemciler birkaç saniye içinde yenilenecek",
"close": "kapat",
"Close": "Kapat",
- "close-livechat-room": "Close Livechat Oda",
+ "close-livechat-room": "Livechat Odasını Kapat",
"close-livechat-room_description": "Mevcut LiveChat kanalını kapatma izni",
- "close-others-livechat-room": "Close Livechat Oda",
+ "close-others-livechat-room": "Livechat Odasını Kapat",
"close-others-livechat-room_description": "Diğer LiveChat kanallarını kapatma izni",
"Closed": "Kapalı",
- "Closed_by_visitor": "Ziyaretçi tarafından kapatıldı",
- "Closing_chat": "kapanış sohbet",
+ "Closed_by_visitor": "Ziyaretçi tarafından sonlandırıldı",
+ "Closing_chat": "Görüşme sonlandırılıyor",
"Cloud": "Bulut",
- "Cloud_connect": "Rocket.Chat Cloud Bağlantısı",
- "Cloud_what_is_it": "Bu nedir?",
"Cloud_workspace_connected_plus_account": "Çalışma alanınız artık Rocket.Chat Cloud ile bağlı ve bir hesap ilişkili.",
+ "Cloud_connect": "Rocket.Chat Cloud Bağlantısı",
"Cloud_workspace_connected_without_account": "Çalışma alanınız artık Rocket.Chat Cloud ile bağlı. İsterseniz, Rocket.Chat Cloud'da oturum açabilir ve Cloud hesabınızla çalışma alanınızı ilişkilendirebilirsiniz.",
+ "Cloud_what_is_it": "Bu nedir?",
+ "Cloud_what_is_it_description": "Rocket.Chat Cloud Connect, kendi barındırdığınız Rocket.Chat Workspace'inizi Cloud'umuza bağlamanızı sağlar. Bunu yapmak, Rocket.Chat Cloud'daki lisanslarınızı, Fatura ve Desteği yönetmenizi etkinleştirir.",
+ "Cloud_workspace_connected": "Çalışma alanınız Rocket.Chat Cloud ile başarıyla bağlandı. Hesap bilgilerini yönetmek için buluta erişebilirsiniz",
+ "Cloud_workspace_support": "Bir bulut hizmetinde sorun yaşarsanız, lütfen önce eşitlemeyi deneyin. Sorun devam ederse, lütfen Cloud Console üzerinde bir destek talebi oluşturun.",
+ "Cloud_workspace_disconnect": "Artık bulut hizmetlerinden yararlanmak istemiyorsanız, çalışma alanınızın Rocket.Chat Cloud ile bağlantısını kesebilirsiniz.",
"Cloud_login_to_cloud": "Rocket.Chat Cloud'da oturum aç",
"Cloud_address_to_send_registration_to": "Bulut'a kaydınız için e-postanızın gönderileceği adres",
"Cloud_update_email": "E-Postayı Güncelle",
@@ -604,7 +612,7 @@
"Cloud_error_in_authenticating": "Doğrulama sırasında hata alındı",
"Cloud_error_code": "Kod:",
"Collaborative": "Ortak",
- "Collapse_Embedded_Media_By_Default": "Varsayılan olarak gömülü medya kapa",
+ "Collapse_Embedded_Media_By_Default": "Gömülü Ortam Varsayılan Olarak Daraltılsın",
"color": "Renk",
"Color": "Renk",
"Colors": "Renkler",
@@ -628,15 +636,15 @@
"Content": "İçerik",
"Continue": "Devam etmek",
"Continuous_sound_notifications_for_new_livechat_room": "Yeni canlı oda için sürekli sesli bildirimler",
- "Conversation": "Direkt Mesaj",
+ "Conversation": "Konuşma",
"Conversations": "Konuşmalar",
"Conversation_closed": "Konuşma kapalı: __comment__.",
- "Conversation_finished_message": "Konuşma Son Mesaj",
+ "Conversation_finished_message": "Konuşma Bitirme İletisi",
"Conversations_per_day": "Günlük Konuşmalar",
"conversation_with_s": "%s ile görüşme",
"Convert_Ascii_Emojis": "ASCII kodu emojiye dönüştür",
- "Copied": "kopyalanan",
- "Copy": "kopya",
+ "Copied": "Kopyalandı",
+ "Copy": "Kopyala",
"Copy_to_clipboard": "Panoya kopyala",
"COPY_TO_CLIPBOARD": "PANOYA KOPYALA",
"could-not-access-webdav": "WebDAV'a erişilemiyor",
@@ -885,14 +893,14 @@
"Cozy": "Rahat",
"Create": "Oluştur",
"create-c": "Herkese Açık Kanallar Oluştur",
- "create-c_description": "Herkese açık kanalları oluşturma yetkisi",
- "create-d": "Doğrudan Mesajlar Oluştur",
+ "create-c_description": "Herkese açık kanal oluşturma izni",
+ "create-d": "Doğrudan İletiler Oluştur",
"create-d_description": "Doğrudan ileti başlatma izni",
- "create-p": "Özel Kanallar Yarat",
- "create-p_description": "Özel kanallar oluşturma izni",
- "create-personal-access-tokens": "Kişisel Erişim Belirteçleri yaratın",
- "create-user": "Kullanıcı oluşturma",
- "create-user_description": "Kullanıcı oluşturma yetkisi",
+ "create-p": "Özel Kanallar Oluştur",
+ "create-p_description": "Özel kanal oluşturma izni",
+ "create-personal-access-tokens": "Kişisel Erişim Belirteçleri Oluştur",
+ "create-user": "Kullanıcı Oluştur",
+ "create-user_description": "Kullanıcı oluşturma izni",
"Create_A_New_Channel": "Yeni Kanal Oluştur",
"Create_new": "Yeni oluştur",
"Create_unique_rules_for_this_channel": "Bu kanal için benzersiz kurallar oluşturun",
@@ -969,15 +977,15 @@
"Delete": "Sil",
"delete-c": "Herkese Açık Kanalları Sil",
"delete-c_description": "Herkese açık kanalları silmek için izin",
- "delete-d": "Doğrudan Mesajları Sil",
- "delete-d_description": "Doğrudan mesajları silmek için izin",
+ "delete-d": "Doğrudan İletileri Sil",
+ "delete-d_description": "Doğrudan iletileri silme izni",
"delete-message": "İletiyi Sil",
"delete-message_description": "Odadaki bir iletiyi silme izni",
"delete-p": "Özel Kanalları Sil",
"delete-p_description": "Özel kanalları silmek için izin",
"delete-user": "Kullanıcıyı sil",
"delete-user_description": "Kullanıcıları silmek için izin",
- "Delete_message": "mesajı sil",
+ "Delete_message": "İletiyi sil",
"Delete_my_account": "Hesabımı sil",
"Delete_Room_Warning": "Bir odayı silmek, oda içinde gönderilen tüm mesajları silecektir. Bu işlem geri alınamaz.",
"Delete_User_Warning": "Bu kullanıcıyı silerseniz tüm mesajları da beraberinde silinecektir! Bu işlemi geri alamazsınız.",
@@ -1023,23 +1031,27 @@
"Directory": "Dizin",
"Disable_Facebook_integration": "Facebook entegrasyonunu devre dışı bırak",
"Disable_Notifications": "Bildirimleri kapat",
- "Disable_two-factor_authentication": "İki faktörlü kimlik doğrulamayı devre dışı bırak",
+ "Disable_two-factor_authentication": "İki aşamalı kimlik doğrulamayı devre dışı bırak",
"Disabled": "engelli",
"Disallow_reacting": "Tepki Vermeye İzin Verme",
"Disallow_reacting_Description": "İzin verilmeyen izinler",
+ "Disconnect": "Bağlantıyı kes",
"Display_offline_form": "Ekran çevrimdışı formu",
"Display_unread_counter": "Okunmamış ileti sayısını görüntüle",
"Displays_action_text": "Görüntüler aksiyon metni",
"Discussion_name": "Tartışma adı",
+ "Discussions": "Tartışmalar",
"Discussion_start": "Tartışma başlat",
"Discussion_target_channel_description": "Sormak istediğinizle ilgili bir kanal seçin",
"Discussion_target_channel_prefix": "Burada bir tartışma oluşturuyorsunuz",
"Discussion_target_channel": "Ana kanal veya grup",
+ "discussion-created": "__message__",
+ "Discussion_description": "Neler olup bittiğiyle ilgili genel bakışa yardımcı olun! Tartışma oluşturmanız sonucunda, seçtiğiniz kanaldan yeni bir alt kanalı oluşturulur ve birbirine bağlanır.",
"Discussion_first_message_title": "İletiniz",
"Discussion_title": "Yeni tartışma oluştur",
"Dont_ask_me_again": "Bana bir daha sorma!",
- "Dont_ask_me_again_list": "Bana tekrar sorma listesi",
- "Do_not_display_unread_counter": "Bu kanalın sayaçlarını gösterme",
+ "Dont_ask_me_again_list": "Bana bir daha sorma listesi",
+ "Do_not_display_unread_counter": "Bu kanalın sayacını görüntüleme",
"Do_you_want_to_accept": "Kabul etmek ister misin?",
"Do_you_want_to_change_to_s_question": "%s değiştirmek istiyor musunuz?",
"Document_Domain": "Belge Alanı",
@@ -1063,12 +1075,13 @@
"Duration": "Süre",
"E2E Encryption": "Uçtan Uca Şifreleme",
"E2E_Enabled": "Uçtan Uca Şifreleme Etkin",
+ "E2E_Enable_description": "Şifreli gruplar oluşturma ve grupları ile doğrudan iletileri şifrelenmiş hale getirme seçeneği etkin",
"E2E_Encryption_Password_Explanation": "Artık şifrelenmiş özel gruplar ve doğrudan iletiler oluşturabilirsiniz. Mevcut özel grupları veya doğrudan iletileri de şifrelenmiş hale getirebilirsiniz. Uçtan uca şifreleme ile, iletilerinizi şifreleme/şifresini çözme anahtarı sunucuda saklanmayacaktır. Bu nedenle şifrenizi güvenli bir yerde saklamanız gerekmektedir. Uçtan uca şifrelemeyi üzerinde kullanmak istediğiniz diğer aygıtta girmeniz gerekir.",
"E2E_password_reveal_text": "Artık şifrelenmiş özel gruplar ve doğrudan iletiler oluşturabilirsiniz. Mevcut özel gruplar ve doğrudan iletileri de şifrelenmiş hale getirebilirsiniz. Uçtan uca şifreleme ile, iletilerinizi şifreleme/şifresini çözme anahtarı sunucuda saklanmayacaktır. Bu nedenle şifrenizi güvenli bir yerde saklamanız gerekmektedir. Uçtan uca şifrelemeyi üzerinde kullanmak istediğiniz diğer aygıtta girmeniz gerekir.Daha fazlasını buradan öğrenin! Şifreniz: %s Bu otomatik oluşturulan şifredir; istediğiniz zaman mevcut şifrenizle oturum açtığınız bir tarayıcıdan şifreleme anahtarınızı yenisi ile değiştirebilirsiniz. Bu şifre, siz saklayıncaya ve bu iletiyi reddedinceye kadar, yalnızca bu tarayıcıda saklanacaktır.",
- "E2E_password_request_text": "Şifrelenmiş özel gruplarınıze ve doğrudan iletilerinize erişmek için, şifreleme parolanızını girin. Anahtar, sunucuda saklanmadığı için kullandığnız her istemcide iletilerinizi şifreleme/şifre çözme için bu parolayı girmeniz gerekmektedir.",
+ "E2E_password_request_text": "Şifrelenmiş özel gruplarınıza ve doğrudan iletilerinize erişmek için, şifreleme parolanızını girin. Anahtar, sunucuda saklanmadığı için kullandığnız her istemcide iletilerinizi şifreleme/şifre çözme için bu parolayı girmeniz gerekmektedir.",
"Edit": "Düzenle",
- "edit-message": "Mesajı Düzenle",
- "edit-message_description": "Bir odada bulunan mesajı düzenleme yetkisi",
+ "edit-message": "İletiyi Düzenle",
+ "edit-message_description": "Odalardaki iletileri düzenleme izni",
"edit-other-user-active-status": "Diğer Kullanıcı Etkin Durumunu Düzenle",
"edit-other-user-active-status_description": "Diğer hesapları etkinleştirme veya devre dışı bırakma izni",
"edit-other-user-info": "Diğer Kullanıcı Bilgilerini Düzenle",
@@ -1097,11 +1110,11 @@
"Email_Footer_Description": "Aşağıdaki yer tutucuları kullanabilirsiniz: [Site_Name] ve [Site_URL] sırasıyla Uygulama Adı ve URL. ",
"Email_from": "itibaren",
"Email_Header_Description": "Aşağıdaki yer tutucuları kullanabilirsiniz: [Site_Name] ve [Site_URL] sırasıyla Uygulama Adı ve URL. ",
- "Email_Notification_Mode": "Çevrimdışı E-posta Bildirimleri",
- "Email_Notification_Mode_All": "Her Mansiyon / DM",
+ "Email_Notification_Mode": "Çevrimdışı E-posta Bildirimlerinin Geleceği Durumlar",
+ "Email_Notification_Mode_All": "Her Bahsetme veya Doğrudan İleti",
"Email_Notification_Mode_Disabled": "engelli",
"Email_or_username": "E-posta ya da kullanıcı adı",
- "Email_Placeholder": "Lütfen e-mail adresinizi giriniz...",
+ "Email_Placeholder": "Lütfen e-posta adresinizi girin...",
"Email_Placeholder_any": "Lütfen e-posta adreslerini girin...",
"Email_subject": "Konu",
"email_style_label": "E-Posta Tarzı",
@@ -1111,26 +1124,26 @@
"EmojiCustomFilesystem": "Özel Emoji Dosya Sistemi",
"Empty_title": "Boş başlık",
"Enable": "etkinleştirme",
- "Enable_Auto_Away": "Otomatik Uzaklığı Etkinleştir",
+ "Enable_Auto_Away": "Otomatik Uzakta Etkin",
"Enable_Desktop_Notifications": "Masaüstü Bildirimlerini etkinleştir",
"Enable_Svg_Favicon": "SVG favicon'u etkinleştir",
- "Enable_two-factor_authentication": "İki faktörlü kimlik doğrulamayı etkinleştir",
+ "Enable_two-factor_authentication": "İki aşamalı kimlik doğrulamayı etkinleştir",
"Enabled": "Etkin",
"Encrypted": "Şifreli",
"Encrypted_channel_Description": "Uçtan uca şifrelenmiş kanal. Arama şifrelenmiş kanallarda çalışmayacak ve bildirimlerde ileti içeriği gözükmeyecektir.",
- "Encrypted_message": "Şifreli ileti",
+ "Encrypted_message": "Şifrelenmiş ileti",
"Encrypted_setting_changed_successfully": "Şifrelenmiş ayar başarılı bir şekilde değiştirildi",
"EncryptionKey_Change_Disabled": "Şifreleme anahtarınız için bir parola ayarlayamazsınız, çünkü özel anahtarınız bu istemcide mevcut değil. Yeni bir parola ayarlamak için mevcut parolanızı kullanarak özel anahtarınızı yüklemeniz veya anahtarın halihazırda yüklü olduğu bir istemciyi kullanmanız gerekir.",
"Encryption_key_saved_successfully": "Şifreleme anahtarınız başarılı bir şekilde kaydedildi.",
- "End_OTR": "OTR'yi sonlandır",
+ "End_OTR": "Kayıt Dışını Sonlandır",
"Enter_a_name": "Ad girin",
- "Enter_a_regex": "Bir regex girin",
- "Enter_a_room_name": "Bir oda ismi girin",
- "Enter_a_username": "Bir kullanıcı adı girin",
+ "Enter_a_regex": "Regex girin",
+ "Enter_a_room_name": "Oda adı girin",
+ "Enter_a_username": "Kullanıcı adı girin",
"Enter_Alternative": "Alternatif mod (Enter + Ctrl / Alt / Üst Karakter / CMD ile gönder)",
"Enter_authentication_code": "Kimlik doğrulama kodu girin",
- "Enter_Behaviour": "Anahtar davranışını girin",
- "Enter_Behaviour_Description": "Bu, giriş tuşu bir mesaj gönderirse veya bir satır sonu yaparsa değişir",
+ "Enter_Behaviour": "Enter tuşu Davranışı",
+ "Enter_Behaviour_Description": "Bu, Enter tuşunun işlevinin ileti göndermek mi yoksa alt satıra geçmek mi olacağını ayarlar.",
"Enter_E2E_password_to_decode_your_key": "Anahtarınızı çözmek için Uçtan Uca şifrenizi girin",
"Enter_name_here": "Burada adı girin",
"Enter_Normal": "Normal mod (Enter ile gönder)",
@@ -1143,7 +1156,7 @@
"error-application-not-found": "Uygulama bulunamadı",
"error-archived-duplicate-name": "Adı '__room_name__' ile arşivlenmiş bir kanal var",
"error-avatar-invalid-url": "Geçersiz avatar URL: __url__",
- "error-avatar-url-handling": "Hata __username__ için bir URL (__url__) den avatar ayarını tutarken",
+ "error-avatar-url-handling": "__username__ için URL (__url__) üzerinden avatar ayarlama sırasında hata oluştu",
"error-cant-invite-for-direct-room": "Doğrudan odalara kullanıcı davet edemez",
"error-channels-setdefault-is-same": "Kanal varsayılan ayarı, değiştirileceği ayarın aynısıdır.",
"error-channels-setdefault-missing-default-param": "BodyParam 'varsayılan' gerekli",
@@ -1152,7 +1165,7 @@
"error-could-not-change-username": "adını değiştirmek olamazdı",
"error-delete-protected-role": "Korunan rol silinemiyor",
"error-department-not-found": "Bölümü bulunamadı",
- "error-direct-message-file-upload-not-allowed": "Doğrudan mesajlarda dosya paylaşımına izin verilmiyor",
+ "error-direct-message-file-upload-not-allowed": "Doğrudan iletilerde dosya paylaşımına izin verilmiyor",
"error-duplicate-channel-name": "'__channel_name__' isminde bir kanal var",
"error-edit-permissions-not-allowed": "Düzenleme izinlerine izin verilmiyor",
"error-email-domain-blacklisted": "E-posta alanı kara listede",
@@ -1202,9 +1215,9 @@
"error-invalid-webhook-response": "webhook URL 200 dışında bir statü ile cevap verdi",
"error-message-deleting-blocked": "İleti silme engellendi",
"error-message-editing-blocked": "İleti düzenleme engellendi",
- "error-message-size-exceeded": "İleti boyutu Message_MaxAllowedSize aşıyor",
+ "error-message-size-exceeded": "İleti boyutu en fazla Message_MaxAllowedSize olmalı",
"error-missing-unsubscribe-link": "[unsubscribe] bağlantısını sağlamalısınız.",
- "error-no-tokens-for-this-user": "Bu kullanıcı için belirteçleri vardır",
+ "error-no-tokens-for-this-user": "Bu kullanıcı için belirteç bulunmuyor",
"error-not-allowed": "İzin verilmedi",
"error-not-authorized": "Yetkili değil",
"error-password-policy-not-met": "Şifre sunucunun politikasını karşılamıyor",
@@ -1215,14 +1228,14 @@
"error-password-policy-not-met-oneSpecial": "Şifre, sunucunun en az bir özel karakter politikasını karşılamıyor",
"error-password-policy-not-met-oneUppercase": "Şifre, sunucunun en az bir büyük harfli politikasını karşılamıyor",
"error-password-policy-not-met-repeatingCharacters": "Parola, sunucunun yasaklı yinelenen karakterler politikasını karşılamıyor (birbirinin yanında çok fazla aynı karakter var)",
- "error-push-disabled": "Itme devre dışı",
+ "error-push-disabled": "Anlık bildirim devre dışı bırakıldı",
"error-remove-last-owner": "Bu son sahibidir. Bu kaldırmadan önce yeni bir sahibi ayarlayın.",
"error-role-in-use": "kullanımda olduğundan rol silinemiyor",
"error-role-name-required": "Rol adı gerekli",
"error-room-is-not-closed": "Oda kapalı değil",
"error-the-field-is-required": "Alan __field__ gereklidir.",
"error-this-is-not-a-livechat-room": "Bu bir Livechat odası değil",
- "error-personal-access-tokens-are-current-disabled": "Kişisel Erişim belirteçleri şu anda devre dışı",
+ "error-personal-access-tokens-are-current-disabled": "Kişisel Erişim Belirteçleri şu anda devre dışı",
"error-token-already-exists": "Bu ada sahip bir belirteç zaten mevcut",
"error-token-does-not-exists": "Token mevcut değil",
"error-too-many-requests": "Hata, çok fazla istek. Lütfen yavaşla. Yine denemeden önce __seconds__ saniye beklemeniz gerekir.",
@@ -1259,9 +1272,10 @@
"except_pinned": "(tutturulmuş olanlar hariç)",
"Execute_Synchronization_Now": "Şimdi senkronizasyonu çalıştır",
"Exit_Full_Screen": "Tam Ekrandan Çık",
- "Export_My_Data": "Verilerimi Dışarı Aktar",
+ "Export_My_Data": "Verilerimi Dışa Aktar",
"expression": "İfade",
"Extended": "Genişletilmiş",
+ "External_Domains": "Dış Alan Adları",
"External_Queue_Service_URL": "Harici Kuyruk Hizmeti URL'si",
"External_Service": "Dış Hizmet",
"Facebook_Page": "Facebook Sayfası",
@@ -1272,6 +1286,7 @@
"Feature_Depends_on_Livechat_Visitor_navigation_as_a_message_to_be_enabled": "Bu özellik, \"Ziyaretçi Gezinme Geçmişini İleti Olarak Gönder\" seçeneğinin etkinleştirilmesine bağlıdır.",
"Features_Enabled": "Özellikler Etkin",
"FEDERATION_Discovery_Method": "Keşif Yöntemi",
+ "FEDERATION_Discovery_Method_Description": "DNS kayıtlarınızda hub'ı veya bir SRV'yi ve bir TXT girişini kullanabilirsiniz.",
"FEDERATION_Domain": "Alan Adı",
"FEDERATION_Domain_Description": "Bu sunucunun bağlanması gereken alan adını ekleyin - örneğin: @rocket.chat",
"FEDERATION_Domain_Alert": "Bu özelliği etkinleştirdikten sonra bunu değiştirmeyin, henüz alan adı değişikliklerini gerçekleştiremiyoruz.",
@@ -1287,7 +1302,7 @@
"File_exceeds_allowed_size_of_bytes": "Dosya __size__ bayt izin boyutunu aşıyor",
"File_name_Placeholder": "Dosyaları ara...",
"File_removed_by_automatic_prune": "Dosya otomatik kuru erik tarafından kaldırıldı",
- "File_not_allowed_direct_messages": "Doğrudan mesajlarda dosya paylaşımına izin verilmiyor.",
+ "File_not_allowed_direct_messages": "Doğrudan iletilerde dosya paylaşımına izin verilmiyor.",
"File_removed_by_prune": "Dosya kuru erik tarafından kaldırıldı",
"File_type_is_not_accepted": "Dosya türü kabul edilmiyor.",
"File_uploaded": "Dosya yüklendi",
@@ -1295,7 +1310,7 @@
"FileUpload_Disabled": "Dosya yüklemeleri devre dışı.",
"FileUpload_Enabled": "Dosya Yükleme Etkin",
"FileUpload_Error": "Dosya Yükleme Hatası",
- "FileUpload_Enabled_Direct": "Doğru Mesajlar'da Etkinleştirilen Dosya Yükleme Sayısı",
+ "FileUpload_Enabled_Direct": "Doğrudan İletilerde Dosya Yükleme Etkin",
"FileUpload_File_Empty": "boş Dosya",
"FileUpload_FileSystemPath": "sistem Yolu",
"FileUpload_GoogleStorage_AccessId": "Google Depolama Erişimi Kimliği",
@@ -1373,6 +1388,7 @@
"Forward_chat": "Sohbeti İlet",
"Forward_to_department": "Bölüme İlet",
"Forward_to_user": "Kullanıcıya İlet",
+ "Free": "Ücretsiz",
"Frequently_Used": "Sıklıkla kullanılan",
"Friday": "Cuma",
"From": "itibaren",
@@ -1384,10 +1400,10 @@
"github_no_public_email": "Sen GitHub hesabınızda kamu e-posta olarak herhangi bir e-posta yok",
"Give_a_unique_name_for_the_custom_oauth": "Özel OAuth için benzersiz bir ad verin",
"Give_the_application_a_name_This_will_be_seen_by_your_users": "Uygulamayı bir ad verin. Bu kullanıcılar tarafından görülecektir.",
- "Global": "global",
- "Global Policy": "Global Politika",
+ "Global": "Genel",
+ "Global Policy": "Genel Politika",
"Global_purge_override_warning": "Küresel bir saklama politikası var. \"Küresel saklama politikasını geçersiz kıl\" ı devre dışı bırakırsanız, yalnızca küresel politikadan daha sıkı bir politika uygulayabilirsiniz.",
- "Global_Search": "Küresel arama",
+ "Global_Search": "Genel arama",
"Go_to_your_workspace": "Çalışma alanınıza git",
"Google_Vision_usage_limit_exceeded": "Google Vizyon kullanım sınırı aşıldı",
"GoogleCloudStorage": "Google Cloud Storage",
@@ -1400,7 +1416,7 @@
"GoogleVision_Max_Monthly_Calls": "Maks Aylık Aramalar",
"GoogleVision_Max_Monthly_Calls_Description": "Sınırsız için 0 kullanın.",
"GoogleVision_ServiceAccount": "Google Vizyon Hizmeti Hesabı",
- "GoogleVision_ServiceAccount_Description": "Bir sunucu anahtarı (JSON biçimi) oluşturun ve JSON içeriğini buraya yapıştırın",
+ "GoogleVision_ServiceAccount_Description": "Bir sunucu anahtarı (JSON biçiminde) oluşturun ve JSON içeriğini buraya yapıştırın",
"GoogleVision_Type_Document": "Belge Metin Algılama",
"GoogleVision_Type_Faces": "Yüz Tanıma",
"GoogleVision_Type_Labels": "Etiketler Tespiti",
@@ -1414,7 +1430,7 @@
"Graphql_CORS": "GraphQL CORS",
"Graphql_Subscription_Port": "GraphQL Abone Portu",
"Group_by_Type": "Türe göre grupla",
- "Group_discussions": "Tartışmaları grupla",
+ "Group_discussions": "Tartışmalar gruplansın",
"Group_favorites": "Favorileri grupla",
"Group_mentions_disabled_x_members": "Grup, '@' ve '@' kelimelerinden daha fazla __total__ üyesi olan odalar için devre dışı bırakıldı.",
"Group_mentions_only": "Grup yalnızca bahsediyor",
@@ -1429,20 +1445,20 @@
"Hi_username": "Merhaba __name__",
"Hidden": "Gizli",
"Hide": "Gizle",
- "Hide_Avatars": "Profil Resimlerini Gizle",
+ "Hide_Avatars": "Avatarlar Gizlensin",
"Hide_counter": "Sayacı gizle",
- "Hide_flextab": "Tıklayarak Sağ Taraftaki Barı Gizle",
- "Hide_Group_Warning": "Eğer grup \"%s\" gizlemek istediğinizden emin misiniz?",
+ "Hide_flextab": "Sağ Kenar Çubuğu Tıklanarak Gizlensin",
+ "Hide_Group_Warning": "\"%s\" grubunu gizlemek istediğinize emin misiniz?",
"Hide_Livechat_Warning": "\"%s\" ile canlı çekimi gizlemek istediğinize emin misiniz?",
"Hide_Private_Warning": "\"%s\" ile tartışmayı gizlemek istediğinize emin misiniz?",
- "Hide_roles": "Rolleri Gizle",
+ "Hide_roles": "Roller Gizlensin",
"Hide_room": "Odayı gizle",
"Hide_Room_Warning": "Eğer oda \"%s\" gizlemek istediğinizden emin misiniz?",
"Hide_Unread_Room_Status": "Okunmamış Oda Durumunu Gizle",
- "Hide_usernames": "adlarını gizlemek",
- "Highlights": "Özeti",
- "Highlights_How_To": "Birisi buradan ekleyebilirsiniz, bir kelime veya kelime öbeği bahseder haberdar edilecektir. Sen virgül ile kelimeleri veya cümleleri ayırabilirsiniz. Vurgu Kelimeler küçük harfe duyarlı değildir.",
- "Highlights_List": "Vurgu sözler",
+ "Hide_usernames": "Kullanıcı Adları Gizlensin",
+ "Highlights": "Vurgular",
+ "Highlights_How_To": "Birisi bu sözcük veya ifadelerden bahsettiğinde bildirim gelmesi için, buraya ekleyin. Sözcük veya ifadeleri virgüller ile ayırabilirsiniz. Vurgulanacak Sözcükler büyük küçük harf duyarlı değildir.",
+ "Highlights_List": "Vurgulanacak sözcükler",
"History": "Geçmiş",
"Host": "evsahibi",
"hours": "saat",
@@ -1491,12 +1507,12 @@
"Importer_import_cancelled": "İthalat iptal edildi.",
"Importer_import_failed": "ithalat çalışırken bir hata oluştu.",
"Importer_importing_channels": "kanalları İthalat.",
- "Importer_importing_messages": "mesajlar içe.",
+ "Importer_importing_messages": "İletiler içe aktarılıyor.",
"Importer_importing_started": "ithalat başlatılıyor.",
"Importer_importing_users": "kullanıcıları içe.",
"Importer_not_in_progress": "ithalatçı şu anda çalışmıyor.",
"Importer_not_setup": "Herhangi bir veri döndürmediği için ithalatçı düzgün kurulmamıştır.",
- "Importer_Prepare_Restart_Import": "Yeniden İthalat",
+ "Importer_Prepare_Restart_Import": "İçe Aktarmayı Yeniden Başlat",
"Importer_Prepare_Start_Import": "İçe Aktarmayı Başlat",
"Importer_Prepare_Uncheck_Archived_Channels": "Işaretini kaldırın Arşivlenen Kanallar",
"Importer_Prepare_Uncheck_Deleted_Users": "Işaretini kaldırın Silinmiş Kullanıcıları",
@@ -1525,11 +1541,12 @@
"Install_FxOs_follow_instructions": "Lütfen cihazınızdaki uygulama yüklemeyi onayın (karşınıza çıktığında \"Install\" tuşuna basın).",
"Install_package": "Kurulum paketi",
"Installation": "montaj",
+ "Installed": "Yüklü",
"Installed_at": "kurulu",
"Invitation_HTML": "Davet HTML",
"Instance_Record": "Örnek Kayıt",
"Instructions": "Yönergeler",
- "Instructions_to_your_visitor_fill_the_form_to_send_a_message": "senin ziyaretçiye talimatlar bir mesaj göndermek için formu doldurunuz",
+ "Instructions_to_your_visitor_fill_the_form_to_send_a_message": "Ziyaretçinizin ileti göndermek için dolduracağı form yönergeleri",
"Invitation_HTML_Default": "Sen davet edildi [Site_Name] [Site_URL] gidin ve bugün mevcut en iyi açık kaynak sohbet çözümü deneyin!
",
"Insurance": "Sigorta",
"Integration_added": "Entegrasyon eklendi",
@@ -1551,7 +1568,7 @@
"Integration_Outgoing_WebHook_History_Time_Triggered": "Zaman Entegrasyonu Tetiklendi",
"Integration_Outgoing_WebHook_History_Trigger_Step": "Son Tetikleyici Adım",
"Integration_Outgoing_WebHook_No_History": "Bu giden web kancası entegrasyonunda kaydedilen herhangi bir geçmişi olmamıştır.",
- "Integration_Retry_Count": "Yeniden Dene Sayımı",
+ "Integration_Retry_Count": "Sayımı Yeniden Dene",
"Integration_Retry_Count_Description": "URL çağrısı başarısız olursa kaç kez entegrasyon denenmelidir?",
"Integration_Retry_Delay": "Yeniden Dene Gecikmesini Yeniden Dene",
"Integration_Retry_Delay_Description": "Tekrar deneme hangi gecikme algoritmasını kullanmalıdır? 10^x
veya 2^x
veya x*2
",
@@ -1573,7 +1590,7 @@
"Integrations_Outgoing_Type_UserCreated": "Kullanıcı Oluşturuldu",
"InternalHubot": "İç Hubot",
"InternalHubot_EnableForChannels": "Herkese Açık Kanallar için Etkinleştir",
- "InternalHubot_EnableForDirectMessages": "Doğru Mesajları Etkinleştir",
+ "InternalHubot_EnableForDirectMessages": "Doğrudan İletiler için Etkin",
"InternalHubot_EnableForPrivateGroups": "Özel Kanallar için Etkinleştir",
"InternalHubot_PathToLoadCustomScripts": "Komut Dosyalarını Yüklemek İçin Klasör",
"InternalHubot_reload": "Komut dosyalarını yeniden yükle",
@@ -1647,8 +1664,8 @@
"Joined": "Katılım",
"Jump": "Atlama",
"Jump_to_first_unread": "Okunmamış ilk iletiye git",
- "Jump_to_message": "mesajı atlamak",
- "Jump_to_recent_messages": "son mesajlara atla",
+ "Jump_to_message": "İletiye git",
+ "Jump_to_recent_messages": "Son iletilere git",
"Just_invited_people_can_access_this_channel": "Davet edilenler bu kanala erişebilir.",
"Katex_Dollar_Syntax": "İzin Dolar sözdizimi",
"Katex_Dollar_Syntax_Description": "$$ Katex blok $$ $ ve inline Katex $ sözdizimleri kullanımına izin",
@@ -1657,18 +1674,18 @@
"Katex_Parenthesis_Syntax": "İzin Parantez Sözdizimi",
"Katex_Parenthesis_Syntax_Description": "\\ [Katex blok \\] ve \\ (inline Katex \\) sözdizimi kullanımına izin",
"Keep_default_user_settings": "Varsayılan ayarları koru",
- "Keyboard_Shortcuts_Edit_Previous_Message": "Önceki mesajı düzenle",
+ "Keyboard_Shortcuts_Edit_Previous_Message": "Önceki iletiyi düzenle",
"Keyboard_Shortcuts_Keys_1": "Komut (veya Ctrl ) + p YA DA Komut (veya Ctrl ) + k ",
"Keyboard_Shortcuts_Keys_2": "Yukarı Ok ",
"Keyboard_Shortcuts_Keys_3": "Komutan (veya Alt ) + Sol Ok ",
"Keyboard_Shortcuts_Keys_4": "Komut (veya Alt ) + Yukarı Ok ",
"Keyboard_Shortcuts_Keys_5": "Komut (veya Alt ) + Sağ Ok ",
- "Keyboard_Shortcuts_Keys_6": "Komutan (veya Alt ) + Aşağı Ok ",
- "Keyboard_Shortcuts_Keys_7": "Kaydır + Enter ",
+ "Keyboard_Shortcuts_Keys_6": "Komut (veya Alt ) + Aşağı Ok ",
+ "Keyboard_Shortcuts_Keys_7": "ÜstKrktr + Enter ",
"Keyboard_Shortcuts_Keys_8": "ÜstKrkt (veya Ctrl) + ESC ",
"Keyboard_Shortcuts_Mark_all_as_read": "Tüm iletileri (tüm kanallardaki) okundu olarak işaretle",
- "Keyboard_Shortcuts_Move_To_Beginning_Of_Message": "Mesajın başına git",
- "Keyboard_Shortcuts_Move_To_End_Of_Message": "Mesajın sonuna git",
+ "Keyboard_Shortcuts_Move_To_Beginning_Of_Message": "İletinin başına git",
+ "Keyboard_Shortcuts_Move_To_End_Of_Message": "İletinin sonuna git",
"Keyboard_Shortcuts_New_Line_In_Message": "İleti girdisinde yeni satır",
"Keyboard_Shortcuts_Open_Channel_Slash_User_Search": "Açık Kanal / Kullanıcı araması",
"Keyboard_Shortcuts_Title": "Klavye Kısayolları",
@@ -1678,7 +1695,7 @@
"Language_Not_set": "Özel değil",
"Language_Version": "İngilizce Versiyon",
"Last_login": "Son giriş",
- "Last_Message_At": "Son İleti",
+ "Last_Message_At": "Son İleti Saati",
"Last_seen": "Son görülme",
"Last_token_part": "Son belirteç parçası",
"Last_Message": "Son İleti",
@@ -1770,12 +1787,12 @@
"LDAP_Username_Field": "Adı Alan",
"LDAP_Username_Field_Description": "Hangi alan yeni kullanıcılar için * kullanıcı adı * olarak kullanılacaktır. giriş sayfasında haberdar adını kullanmak için boş bırakın. Sen # {givenName} `gibi, çok şablon etiketlerini kullanabilirsiniz. # {Sn}`. Varsayılan değer `sAMAccountName` olduğunu.",
"Lead_capture_email_regex": "Kurşun yakalama e-posta regex'i",
- "Lead_capture_phone_regex": "Kurşun yakalama telefonu regex",
+ "Lead_capture_phone_regex": "Telefon yakalama regex'ini yönet",
"Least_Amount": "En az miktar",
"leave-c": "Kanallardan Çık",
"leave-p": "Özel Grupları Bırak",
"Leave": "Ayrıl",
- "Leave_Group_Warning": "Eğer grup \"%s\" ayrılmak istediğinize emin misiniz?",
+ "Leave_Group_Warning": "\"%s\" grubundan ayrılmak istediğinize emin misiniz?",
"Leave_Livechat_Warning": "Livechat'ı \"%s\" ile terk etmek istediğinize emin misiniz?",
"Leave_Private_Warning": "\"%s\" ile tartışmadan ayrılmak istediğinize emin misiniz?",
"Leave_room": "Odadan ayrıl",
@@ -1784,7 +1801,7 @@
"Lets_get_you_new_one": "Size yeni bir tane verelim!",
"line": "satır",
"List_of_Channels": "Kanal Listesi",
- "List_of_Direct_Messages": "Doğrudan Mesajlar listesi",
+ "List_of_Direct_Messages": "Doğrudan İletiler Listesi",
"Livechat": "Livechat",
"Livechat_agents": "Canlı ajanlar",
"Livechat_AllowedDomainsList": "Canlı Canlı İzin Alanları",
@@ -1799,9 +1816,9 @@
"Livechat_Inquiry_Already_Taken": "Canlı skor zaten alındı",
"Livechat_managers": "Canlı yöneticileri",
"Livechat_offline": "Canlı çevrimdışı",
- "Livechat_online": "Canlı çevrimiçi",
+ "Livechat_online": "Livechat çevrimiçi",
"Livechat_offline_message_sent": "Livechat çevrimdışı iletisi gönderildi",
- "Livechat_open_inquiery_show_connecting": "Misafir Acenteye Henüz Bağlanmadığında Giriş yerine Mesaj Bağlamayı Göster",
+ "Livechat_open_inquiery_show_connecting": "Ziyaretçinin Henüz Bir Temsilciye Bağlanmadığı Sırada Girdi Yerine Bağlanıyor İletisi Gösterilsin",
"Livechat_Queue": "Livechat Kuyruğu",
"Livechat_registration_form": "Kayıt formu",
"Livechat_registration_form_message": "Kayıt Formu İletisi",
@@ -1826,6 +1843,7 @@
"Loading...": "Yükleniyor...",
"Loading_more_from_history": "Geçmişten daha fazlası yükleniyor",
"Loading_suggestion": "Öneriler yükleniyor...",
+ "Local_Domains": "Yerel Alan Adları",
"Local_Password": "Yerel Şifre",
"Localization": "yerelleştirme",
"Log_Exceptions_to_Channel_Description": "Yakalan tüm istisnaları alacak olan kanal. İstisnaları yok saymak için boş bırakın.",
@@ -1845,17 +1863,17 @@
"Login_with": "%s ile giriş yap",
"Logistics": "Lojistik",
"Logout": "Çıkış Yap",
- "Logout_Others": "Yerler Diğer giriş itibaren logout",
+ "Logout_Others": "Oturum Açılan Diğer Yerlerde Oturumu Kapat",
"Longest_chat_duration": "En Uzun Sohbet Süresi",
"Longest_reaction_time": "En Uzun Tepki Süresi",
"Longest_response_time": "En Uzun Yanıt Süresi",
"Logs": "Günlükler",
- "mail-messages": "Posta mesajları",
+ "mail-messages": "Posta İletileri",
"mail-messages_description": "Posta iletilerini kullanma izni",
"Mail_Message_Invalid_emails": "Bir veya daha fazla geçersiz e-posta sağladı: %s",
"Mail_Message_Missing_to": "Bir veya daha fazla kullanıcı seçmeniz ya da virgülle ayrılmış olarak bir veya daha fazla e-posta adresi girmeniz gerekiyor.",
"Mail_Message_No_messages_selected_select_all": "Herhangi bir ileti seçmediniz",
- "Mail_Messages": "posta iletileri",
+ "Mail_Messages": "Posta İletileri",
"Mail_Messages_Instructions": "mesajlar tıklayarak e-posta yoluyla göndermek istediğiniz iletileri seçin",
"Mail_Messages_Subject": "İşte %s mesajların seçilen bir kısım var",
"Mailer": "posta gemisi",
@@ -1890,7 +1908,7 @@
"Mark_all_as_read": "Tüm iletileri (tüm kanallardaki) okundu olarak işaretle",
"Mark_as_read": "Okundu olarak işaretle",
"Mark_as_unread": "Okunmamış olarak işaretle",
- "Markdown_Headers": "Markdown Başlıkları",
+ "Markdown_Headers": "İletilerde Markdown başlıklarına izin verilsin",
"Markdown_Marked_Breaks": "İşaretli Kesmeleri Etkinleştir",
"Markdown_Marked_GFM": "İşaretli GFM'yi Etkinleştir",
"Markdown_Marked_Pedantic": "İşaretli Pedantic'i etkinleştir",
@@ -1904,24 +1922,25 @@
"Media": "medya",
"Medium": "Orta",
"Members_List": "Üyeler Listesi",
- "mention-all": "Herkes Mansiyon",
+ "mention-all": "Tümünden Bahset",
"mention-all_description": "@all sözünü kullanma izni",
- "mention-here": "Buradan Mansiyon",
+ "mention-here": "Burada Bahset",
"mention-here_description": "@here sözünü kullanmak için izin",
"Mentions": "Bahsetmeler",
"Mentions_default": "Bahsetmeler (varsayılan)",
"Mentions_only": "Sadece mansiyonlar",
"Merge_Channels": "Kanalları Birleştirme",
"Message_AllowBadWordsFilter": "İleti kötü sözcük filtrelemesine izin ver",
+ "Message_AllowConvertLongMessagesToAttachment": "Uzun iletilerin eke dönüştürülmesine izin verilsin",
"Message_AllowDeleting": "İleti Silmeye İzin Verilsin",
"Message_AllowDeleting_BlockDeleteInMinutes": "(n) Dakika Sonra İleti Silme Engellensin",
"Message_AllowDeleting_BlockDeleteInMinutes_Description": "engelleme devre dışı bırakmak için 0 girin.",
- "Message_AllowDirectMessagesToYourself": "Kullanıcıya doğrudan mesajlar verin kendinize",
+ "Message_AllowDirectMessagesToYourself": "Kullanıcının kendisine doğrudan ileti göndermesine izin verilsin",
"Message_AllowEditing": "İleti Düzenlemeye İzin Verilsin",
- "Message_AllowEditing_BlockEditInMinutes": "(n) dakika sonra iletiyi düzenlemeyi engelle",
+ "Message_AllowEditing_BlockEditInMinutes": "İleti Düzenlemenin Sona Ereceği Süre",
"Message_AllowEditing_BlockEditInMinutesDescription": "Engellemeyi devre dışı bırakmak için 0 girin.",
"Message_AllowPinning": "İleti Sabitlemeye İzin Verilsin",
- "Message_AllowPinning_Description": "İzin mesajları kanallardan herhangi tutturulmuş olması.",
+ "Message_AllowPinning_Description": "İletilerin kanallara sabitlenmesine izin verilsin",
"Message_AllowSnippeting": "İleti Parçalamaya İzin Ver",
"Message_AllowStarring": "İletilere yıldız verilmesine izin ver",
"Message_AllowUnrecognizedSlashCommand": "Tanınmayan Kesme Komutlarına İzin Ver",
@@ -1941,18 +1960,18 @@
"Message_deleting_blocked": "Bu ileti artık silinemez",
"Message_editing": "İleti düzenleme",
"Message_ErasureType": "İleti Silme Türü",
- "Message_ErasureType_Delete": "Tüm Mesajları Sil",
+ "Message_ErasureType_Delete": "Tüm İletileri Sil",
"Message_ErasureType_Description": "Hesaplarını kaldıran kullanıcıların mesajlarıyla ne yapacağınızı belirleyin.",
"Message_ErasureType_Keep": "Mesajları ve Kullanıcı Adlarını Tut",
- "Message_ErasureType_Unlink": "Kullanıcı ve Mesajlar Arasındaki Bağlantıyı Kaldır",
- "Message_GlobalSearch": "Global Arama",
+ "Message_ErasureType_Unlink": "Kullanıcı ve İletiler Arasındaki Bağlantıyı Kaldır",
+ "Message_GlobalSearch": "Genel Arama",
"Message_GroupingPeriod": "(Saniye) 'de periyodun gruplandırma",
"Message_GroupingPeriodDescription": "her ikisi de aynı kullanıcıya ait olduğunu ve geçen zaman saniye cinsinden bilinçli süreden daha az olsaydı iletiler önceki mesajla gruplandırılmış olacaktır.",
"Message_HideType_au": "\"Kullanıcı Eklendi\" iletilerini gizle",
- "Message_HideType_mute_unmute": "\"Kullanıcı Susturuldu / Sesi Açılmadı\" iletilerini gizle",
+ "Message_HideType_mute_unmute": "\"Kullanıcı Sessize Alındı / Sesi Açıldı\" iletilerini gizle",
"Message_HideType_ru": "\"Kullanıcı Kaldırıldı\" iletilerini gizle",
- "Message_HideType_uj": "\"Kullanıcı Katıl\" iletilerini gizle",
- "Message_HideType_ul": "\"Kullanıcı İzin\" mesajları gizle",
+ "Message_HideType_uj": "\"Kullanıcı Katıldı\" iletilerini gizle",
+ "Message_HideType_ul": "\"Kullanıcı Ayrıldı\" iletilerini gizle",
"Message_Id": "İleti ID'si",
"Message_Ignored": "Bu ileti yok sayıldı",
"Message_info": "Ileti bilgisi",
@@ -1977,14 +1996,16 @@
"Message_TimeAndDateFormat_Description": "Ayrıca bkz .: Moment.js ",
"Message_TimeFormat": "Zaman formatı",
"Message_TimeFormat_Description": "Ayrıca bkz: Moment.js ",
- "Message_too_long": "çok uzun İleti",
+ "Message_too_long": "İleti fazla uzun",
+ "Message_too_long_as_an_attachment_question": "İleti fazla uzun. Bunun yerine bir ek olarak gönderilsin mi?",
"Message_UserId": "Kullanıcı ID'si",
"Message_VideoRecorderEnabled": "Video Kaydedici Etkin",
"Message_VideoRecorderEnabledDescription": "'Dosya Yükleme' ayarları içinde kabul edilen bir medya türü olması için 'video / webm' dosyalarının olması gerekir.",
- "Message_view_mode_info": "Bu alan mesajlarının miktarı ekranda sürebilir değiştirir.",
- "Message": "İleti",
- "messages": "Mesajlar",
- "Messages": "Mesajlar",
+ "Message_view_mode_info": "Bu, iletilerin ekranda kaplayacağı alanın boyutunu ayarlar.",
+ "message": "ileti",
+ "Message": "__message__",
+ "messages": "iletiler",
+ "Messages": "İletiler",
"Messages_that_are_sent_to_the_Incoming_WebHook_will_be_posted_here": "Gelen WebHook gönderilen mesajları burada ilan edilecektir.",
"Meta": "Meta",
"Meta_custom": "Özel Meta Etiketleri",
@@ -2000,29 +2021,29 @@
"Mobile_Notifications_Default_Alert": "Mobil Bildirim Varsayılan Uyarısı",
"Monday": "Pazartesi",
"Monitor_history_for_changes_on": "Değişiklikler için Tarihi Geçmişi açık",
- "More": "Daha",
- "More_channels": "Daha fazla",
+ "More": "Daha fazla",
+ "More_channels": "Daha fazla kanal",
"More_direct_messages": "Daha fazla doğrudan ileti",
"More_groups": "Daha fazla özel grup",
- "More_unreads": "Daha fazla okunmayan",
- "Move_beginning_message": "`%s '- Mesajın başına git",
- "Move_end_message": "`%s '- Mesajın sonuna git",
+ "More_unreads": "Daha fazla okunmamış",
+ "Move_beginning_message": "`%s '- İletinin başına git",
+ "Move_end_message": "`%s '- İletinin sonuna git",
"Msgs": "Msj",
"multi": "çoklu",
"multi_line": "çok hatlı",
"mute-user": "Kullanıcıyı Sessize Al",
"mute-user_description": "Aynı kanaldaki diğer kullanıcıların sesini kapatma izni",
"Mute_all_notifications": "Tüm bildirimleri kapat",
- "Mute_Focused_Conversations": "Odaklanmış Konuşmaları Sustur",
- "Mute_Group_Mentions": "@all ve @here bahseder",
+ "Mute_Focused_Conversations": "Odaklanmış Konuşmalar Sessize Alınsın",
+ "Mute_Group_Mentions": "@all ve @here bahsetmeleri sessize al",
"Mute_someone_in_room": "Odada Sessiz birisi",
"Mute_user": "Sessiz kullanıcı",
"Muted": "sessiz",
"My_Account": "Hesabım",
- "My Data": "Verim",
+ "My Data": "Verilerim",
"My_location": "Benim konumum",
"n_messages": "%s ileti",
- "N_new_messages": "%s yeni mesajlar",
+ "N_new_messages": "%s yeni ileti",
"Name": "Ad",
"Name_cant_be_empty": "Ad boş olamaz",
"Name_of_agent": "maddenin adı",
@@ -2039,7 +2060,7 @@
"New_line_message_compose_input": "`%s` - İleti girdisine yeni satır",
"New_logs": "yeni günlükleri",
"New_Message_Notification": "Yeni İleti Bildirimi",
- "New_messages": "Yeni mesajlar",
+ "New_messages": "Yeni iletiler",
"New_password": "Yeni şifre",
"New_Password_Placeholder": "Lütfen yeni şifre girin ...",
"Confirm_new_password": "Yeni şifreyi onayla",
@@ -2052,7 +2073,7 @@
"New_videocall_request": "Yeni Görüntülü Arama İsteği",
"New_visitor_navigation": "Yeni Gezinme: __history__",
"Newer_than": "Daha yeni",
- "Newer_than_may_not_exceed_Older_than": "\"Daha yeni\", \"Eskiden\" geçemez.",
+ "Newer_than_may_not_exceed_Older_than": "\"Daha yeni\", \"Daha eski\"den yeni olamaz",
"No_available_agents_to_transfer": "Aktarılacak ajanlar yok",
"No_channel_with_name_%s_was_found": "Adı \"%s\" olan hiçbir kanal bulunamadı!",
"No_channels_yet": "Henüz hiç bir kanala bağlı değilsiniz.",
@@ -2071,6 +2092,7 @@
"No_snippet_messages": "Snippet yok",
"No_starred_messages": "Favori ileti yok",
"No_such_command": "Böyle bir komut yok: `/__command__`",
+ "No_discussions_yet": "Henüz tartışma yok",
"No_user_with_username_%s_was_found": "\"%s\" adında hiçbir kullanıcı bulunamadı!",
"Nobody_available": "Kimse yok",
"Node_version": "düğüm versiyonu",
@@ -2084,19 +2106,19 @@
"Nothing": "Hiçbir şey",
"Nothing_found": "Bulunamadı",
"Not_Imported_Messages_Title": "Aşağıdaki iletiler başarılı bir şekilde içe aktarılamadı",
- "Notification_Desktop_Default_For": "Için Masaüstü Bildirimlerini Göster",
+ "Notification_Desktop_Default_For": "Masaüstü Bildirimlerinin Gösterileceği Durumlar",
"Notification_Duration": "Bildirim süresi",
- "Notification_Mobile_Default_For": "Için Mobil Bildirimleri Push",
+ "Notification_Mobile_Default_For": "Mobil Anlık Bildirimlerin Gösterileceği Durumlar",
"Notifications": "Bildirimler",
"Notifications_Always_Notify_Mobile": "Mobil cihazlara her zaman haber ver",
"Notifications_Always_Notify_Mobile_Description": "Durumunu ne olursa olsun mobil cihaza her zaman bildirmeyi seçin.",
"Notifications_Duration": "Notifications_Duration",
- "Notifications_Max_Room_Members": "Tüm İleti Bildirimlerini Devre Dışı Bırakmadan Önce Maks. Oda Üyeleri",
+ "Notifications_Max_Room_Members": "Tüm İleti Bildirimlerini Devre Dışı Bırakmadan Önce Maksimum Oda Üyesi",
"Notifications_Max_Room_Members_Description": "Tüm mesajlar için bildirimler devre dışı bırakıldığında odanın azami üye sayısı. Kullanıcılar, tüm bildirimleri tek tek almak üzere oda başına ayarlarını değiştirebilir. (Devre dışı bırakmak için 0)",
- "Notifications_Muted_Description": "Her şeyi sessize almayı seçerseniz, ek açıklamalar dışında yeni mesajlar olduğunda listedeki oda vurgulamasını görmezsiniz. Bildirimleri kapatmak, bildirim ayarlarını geçersiz kılacaktır.",
- "Notifications_Preferences": "Bildirimler Tercihler",
- "Notifications_Sound_Volume": "Bildirimlerin ses düzeyi",
- "Notify_active_in_this_room": "Bu odada aktif kullanıcılara haber ver",
+ "Notifications_Muted_Description": "Her şeyi sessize almayı seçerseniz, bahsetmeler dışında yeni ileti olduğunda listedeki oda vurgulamasını görmezsiniz. Bildirimleri sessize almak, bildirim ayarlarını ezecektir.",
+ "Notifications_Preferences": "Bildirim Tercihleri",
+ "Notifications_Sound_Volume": "Bildirimlerin ses seviyesi",
+ "Notify_active_in_this_room": "Bu odadaki etkin kullanıcıları bilgilendir",
"Notify_all_in_this_room": "Bu odadaki tüm bildirimleri göster",
"Num_Agents": "# Ajanlar",
"Number_of_messages": "İleti sayısı",
@@ -2105,8 +2127,8 @@
"OAuth_Applications": "OAuth Uygulamaları",
"Objects": "nesneler",
"Off": "Kapalı",
- "Off_the_record_conversation": "Off-the-record Konuşma",
- "Off_the_record_conversation_is_not_available_for_your_browser_or_device": "Off-kayıt konuşma tarayıcınız veya aygıt için geçerli değildir.",
+ "Off_the_record_conversation": "Kayıt Dışı Görüşme",
+ "Off_the_record_conversation_is_not_available_for_your_browser_or_device": "Kayıt Dışı görüşme tarayıcınız veya aygıtınız için kullanılabilir değil.",
"Office_Hours": "Çalışma saatleri",
"Office_hours_enabled": "Çalışma Saatleri Etkin",
"Office_hours_updated": "Ofis saatleri güncellendi",
@@ -2114,23 +2136,23 @@
"Offline_DM_Email": "Doğrudan İleti E-Posta Konusu",
"Offline_Email_Subject_Description": "Aşağıdaki yer tutucuları kulanabilirsiniz:Uygulama adı, URL, kullanıcı adı ve oda adı için, sırasıyla [Site_Name], [Site_URL], [User] ve [Room]. ",
"Offline_form": "Çevrimdışı formu",
- "Offline_form_unavailable_message": "Çevrimdışı formu kullanılamıyor mesajı",
- "Offline_Link_Message": "MESAJA GİT",
+ "Offline_form_unavailable_message": "Çevrimdışı Formu Kullanılamıyor İletisi",
+ "Offline_Link_Message": "İLETİYE GİT",
"Offline_Mention_All_Email": "Tüm E-postaları Konuyla İlgili Anlat",
"Offline_Mention_Email": "#__room__ içinde __user__ sizden bahsetti",
- "Offline_message": "Çevrimdışı ileti",
- "Offline_success_message": "Çevrimdışı başarı mesajı",
+ "Offline_message": "Çevrimdışı iletisi",
+ "Offline_success_message": "Çevrimdışı Başarı İletisi",
"Offline_unavailable": "Çevrimdışı kullanılamıyor",
"Old Colors": "Eski Renkler",
"Old Colors (minor)": "Eski Renkler (küçük)",
- "Older_than": "Daha yaşlı",
+ "Older_than": "Daha eski",
"On": "Açık",
"Online": "Çevrimiçi",
"online": "çevrimiçi",
- "Only_authorized_users_can_write_new_messages": "Yalnızca yetkili kullanıcılar yeni mesajlar yazabilir",
+ "Only_authorized_users_can_write_new_messages": "Yalnızca yetkili kullanıcılar yeni ileti yazabilir",
"Only_from_users": "Sadece bu kullanıcılardan içerik aldırın (herkesin içeriğini yarıda bırakmak için boş bırakın)",
"Only_On_Desktop": "Masaüstü modu (yalnızca masaüstünde enter ile gönderir)",
- "Only_you_can_see_this_message": "Sadece siz bu mesajı görebilirsiniz",
+ "Only_you_can_see_this_message": "Yalnızca siz bu iletiyi görebilirsiniz",
"Oops!": "Hata",
"Oops_page_not_found": "Hata! Sayfa bulunamadı",
"Open": "Açık",
@@ -2163,8 +2185,8 @@
"OS_Uptime": "İS Açık Kalma",
"Other": "Diğer",
"others": "Diğerleri",
- "OTR": "OTR",
- "OTR_is_only_available_when_both_users_are_online": "Her iki kullanıcı çevrimiçi olduğunda OTR yalnızca",
+ "OTR": "Kayıt Dışı",
+ "OTR_is_only_available_when_both_users_are_online": "Kayıt Dışı, yalnızca her iki kullanıcı da çevrimiçi ise kullanılabilir.",
"Outgoing_WebHook": "Giden WebHook",
"Outgoing_WebHook_Description": "Gerçek zamanlı olarak Rocket.Chat'ten veri alın.",
"Override_URL_to_which_files_are_uploaded_This_url_also_used_for_downloads_unless_a_CDN_is_given": "dosyaların yüklendiği hangi geçersiz kıl URL. Ayrıca CDN sürece indirme için kullanılan bu url verilir",
@@ -2179,13 +2201,13 @@
"Payload": "Yükünü",
"Peer_Password": "Eş Şifre",
"People": "Kişiler",
- "Permalink": "Kalıcı",
+ "Permalink": "Bağlantıyı kopyala",
"Permissions": "İzinler",
- "Personal_Access_Tokens": "Kişisel Erişim Belirteçi",
- "Phone_number": "Telefon ",
- "pin-message": "Dürbün Mesajı",
+ "Personal_Access_Tokens": "Kişisel Erişim Belirteçleri",
+ "Phone_number": "Telefon numarası",
+ "pin-message": "İletiyi Sabitle",
"pin-message_description": "Kanaldaki bir iletiyi sabitleme izni",
- "Pin_Message": "Mesajı Sabitle",
+ "Pin_Message": "İletiyi Sabitle",
"Pinned_a_message": "Sabitlenen ileti:",
"Pinned_Messages": "Sabitlenmiş İletiler",
"PiwikAdditionalTrackers": "Ek Piwik Siteleri",
@@ -2204,7 +2226,7 @@
"Please_add_a_comment_to_close_the_room": ", Oda kapatmak için bir yorum ekleyin",
"Please_answer_survey": "Bu sohbet hakkında hızlı bir anketi yanıtlamak için bir dakikanızı ayırın",
"please_enter_valid_domain": "Lütfen geçerli bir alan adı girin",
- "Please_enter_value_for_url": "senin avatar url için bir değer girin.",
+ "Please_enter_value_for_url": "Lütfen avatarınızın URL'i için bir değer girin.",
"Please_enter_your_new_password_below": "Aşağıdaki Yeni şifrenizi girin:",
"Please_enter_your_password": "lütfen şifrenizi tekrar girin",
"Please_fill_an_email": "Lütfen bir e-posta girin",
@@ -2219,9 +2241,9 @@
"Please_select_enabled_yes_or_no": "Etkin için bir seçenek seçiniz",
"Please_wait": "Lütfen bekleyin",
"Please_wait_activation": "Lütfen bekleyin, bu biraz zaman alabilir.",
- "Please_wait_while_OTR_is_being_established": "OTR kurulurken lütfen bekleyin",
+ "Please_wait_while_OTR_is_being_established": "Lütfen Kayıt Dışı kurulurken bekleyin",
"Please_wait_while_your_account_is_being_deleted": "Hesabınız silindikten edilirken lütfen bekleyiniz ...",
- "Please_wait_while_your_profile_is_being_saved": "Profil kaydedilirken lütfen bekleyin ...",
+ "Please_wait_while_your_profile_is_being_saved": "Lütfen profiliniz kaydedilirken bekleyin...",
"Pool": "Havuz",
"Port": "Liman",
"post-readonly": "OkunmuşOrtamı gönder",
@@ -2248,27 +2270,28 @@
"Private_Team": "Özel takım",
"Productivity": "Verimlilik",
"Profile": "Profil",
- "Profile_details": "profil detayları",
- "Profile_picture": "Profil fotoğrafı",
+ "Profile_details": "Profil Ayrıntıları",
+ "Profile_picture": "Profil Resmi",
"Profile_saved_successfully": "Profil başarıyla kaydedildi",
"Protocol": "İletişim Kuralları",
"Prune": "Kuru erik",
"Prune_finished": "Prune bitti",
- "Prune_Messages": "Kuru Yazılı Mesajlar",
- "Prune_Modal": "Bu mesajları ertelemek istediğinden emin misin? Budanmış mesajlar kurtarılamaz.",
+ "Prune_Messages": "İletileri Buda",
+ "Prune_Modal": "Bu iletileri budamak istediğinize emin misin? Budanmış mesajlar geri getirlemez.",
"Prune_Warning_all": "Bu %s içindeki tüm %s leri silecek!",
"Prune_Warning_before": "Bu %s içindeki tüm %s leri %s'den önce siler.",
"Prune_Warning_after": "Bu %s içindeki tüm %s leri %s sonra siler.",
"Prune_Warning_between": "Bu, %s içindeki tüm %s leri %s ve %s arasında siler.",
- "Pruning_messages": "Budama mesajları ...",
+ "Pruning_messages": "İletiler budanıyor...",
"Pruning_files": "Budama dosyaları ...",
- "messages_pruned": "mesajlar budanmış",
+ "messages_pruned": "iletiler budandı",
"files_pruned": "dosyalar budanmış",
"Public": "kamu",
"Public_Channel": "Herkese Açık Kanal",
"Public_Community": "Kamu Topluluğu",
"Public_Relations": "Halkla ilişkiler",
- "Push": "it",
+ "Purchased": "Satın alınan",
+ "Push": "Anlık Bildirim",
"Push_Setting_Requires_Restart_Alert": "Bu değerin değiştirilmesi Rocket'in yeniden başlatılmasını gerektirir.",
"Push_apn_cert": "APN Sertifikası",
"Push_apn_dev_cert": "APN Geliştirici Sertifikası",
@@ -2278,14 +2301,16 @@
"Push_apn_passphrase": "APN Parola",
"Push_debug": "Hata ayıklama",
"Push_enable": "Etkin",
- "Push_enable_gateway": "Ağ Geçidi etkinleştirme",
- "Push_gateway": "geçit",
+ "Push_enable_gateway": "Ağ geçidi etkin",
+ "Push_gateway": "Ağ geçidi",
"Push_gcm_api_key": "GCM API Anahtarı",
"Push_gcm_project_number": "GCM Proje Numarası",
"Push_production": "Üretim",
- "Push_show_message": "bildirim mesajı göster",
+ "Push_show_message": "Bildirimde İleti Gösterilsin",
"Push_show_username_room": "Bildirimde göster kanal / grup / kullanıcı adı",
"Push_test_push": "Test",
+ "Purchase_for_free": "ÜCRETSİZ satın al",
+ "Purchase_for_price": "%s$'a satın al",
"Query": "Sorgu",
"Query_description": "e-posta göndermek için hangi kullanıcıların belirlemek için ek şartlar. Abone olmayan kullanıcılar otomatik sorgudan kaldırılır. Bu geçerli bir JSON olmalıdır. Örnek: \"{\" createdAt \": {\" $ gt \": {\" $ tarihi \":\" 2015-01-01T00: 00: 00.000Z \"}}}\"",
"Queue": "Kuyruk",
@@ -2308,7 +2333,7 @@
"Real_Time_Monitoring": "Gerçek Zamanlı İzleme",
"Reason_To_Join": "Katılma Nedeniniz",
"Receive_alerts": "Uyarıları al",
- "Receive_Group_Mentions": "@all almak ve @here bahseder",
+ "Receive_Group_Mentions": "@all ve @here bahsetmelerini al",
"Recent_Import_History": "Son İçe Aktarma Geçmişi",
"Record": "Kayıt",
"Redirect_URI": "URI yönlendir",
@@ -2321,13 +2346,13 @@
"Register_Server": "Kayıt Sunucusu",
"Register_Server_Info": "Rocket.Chat Technologies Corp. tarafından sağlanan önceden yapılandırılmış ağ geçitlerini ve proxy'leri kullanın.",
"Register_Server_Registered": "Erişime kaydol",
- "Register_Server_Registered_Push_Notifications": "Mobil push bildirimleri ağ geçidi",
+ "Register_Server_Registered_Push_Notifications": "Mobil anlık bildirimleri ağ geçidi",
"Register_Server_Registered_Livechat": "Livechat omnichannel proxy",
"Register_Server_Registered_OAuth": "Sosyal ağ için OAuth proxy'si",
"Register_Server_Registered_Marketplace": "Apps Marketplace",
"Register_Server_Opt_In": "Bülten, teklifler ve ürün güncellemeleri",
"Register_Server_Standalone": "Bağımsız ol, ihtiyacınız olacak",
- "Register_Server_Standalone_Service_Providers": "Servis sağlayıcıları ile hesap oluşturma",
+ "Register_Server_Standalone_Service_Providers": "Servis sağlayıcıları ile hesaplar oluştur",
"Register_Server_Standalone_Update_Settings": "Önceden yapılandırılmış ayarları güncelle",
"Register_Server_Standalone_Own_Certificates": "Mobil uygulamaları kendi sertifikalarınızla yeniden derleyin",
"Registration": "Kayıt",
@@ -2358,13 +2383,13 @@
"Report_Abuse": "Uygunsuz",
"Report_exclamation_mark": "Rapor!",
"Report_sent": "rapor gönderildi",
- "Report_this_message_question_mark": "Bu iletiyi bildir?",
+ "Report_this_message_question_mark": "Bu ileti bildirilsin mi?",
"Reporting": "Raporlama",
- "Require_all_tokens": "Tüm belirteçleri zorla",
+ "Require_all_tokens": "Tüm belirteçler gerekli",
"Require_any_token": "Herhangi bir belirteç isteyin",
"Require_password_change": "Şifre değişikliğini zorunlu",
"Resend_verification_email": "Doğrulama e-postasını tekrar gönder",
- "Reset": "Reset",
+ "Reset": "Sıfırla",
"Reset_E2E_Key": "Uçtan Uca Anahtarını Sıfırla",
"reset-other-user-e2e-key": "Diğer Kullanıcı Uçtan Uca Anahtarını Sıfırla",
"Reset_password": "Şifreyi sıfırla",
@@ -2384,24 +2409,26 @@
"RetentionPolicy_Enabled": "Etkin",
"RetentionPolicy_AppliesToChannels": "Kanallara uygulanır",
"RetentionPolicy_AppliesToGroups": "Özel gruplara uygulanır",
- "RetentionPolicy_AppliesToDMs": "Doğrudan mesajlara uygulanır",
+ "RetentionPolicy_AppliesToDMs": "Doğrudan iletilere uygulanır",
"RetentionPolicy_ExcludePinned": "Sabitlenmiş iletileri dışında tut",
"RetentionPolicy_FilesOnly": "Sadece dosyaları sil",
"RetentionPolicy_FilesOnly_Description": "Sadece dosyalar silinecek, mesajların kendisi yerinde kalacaktır.",
"RetentionPolicy_MaxAge": "Maksimum ileti yaşı",
"RetentionPolicy_MaxAge_Channels": "Kanallarda maksimum ileti yaşı",
"RetentionPolicy_MaxAge_Groups": "Özel gruplarda maksimum ileti yaşı",
- "RetentionPolicy_MaxAge_DMs": "Doğrudan mesajlarda maksimum ileti yaşı",
- "RetentionPolicy_MaxAge_Description": "Bu değerden daha eski olan tüm iletileri gün cinsinden belirle",
+ "RetentionPolicy_MaxAge_DMs": "Doğrudan iletilerde maksimum ileti yaşı",
+ "RetentionPolicy_MaxAge_Description": "Bu değerde günden daha eski olan tüm iletileri buda ",
"RetentionPolicy_Precision": "Zamanlayıcı Hassasiyeti",
"RetentionPolicy_Precision_Description": "Kuru erik zamanlayıcı kaç kez çalıştırılmalıdır. Bunu daha hassas bir değere ayarlamak, hızlı saklama zamanlayıcılarına sahip kanalların daha iyi çalışmasını sağlar, ancak büyük topluluklarda ekstra işlem gücüne mal olabilir.",
- "RetentionPolicyRoom_Enabled": "Eski iletileri otomatik olarak belirle",
+ "RetentionPolicyRoom_Enabled": "Eski iletileri otomatik olarak buda",
"RetentionPolicyRoom_ExcludePinned": "Sabitlenmiş iletileri dışında tut",
- "RetentionPolicyRoom_FilesOnly": "Yalnızca kuru çiçekler",
+ "RetentionPolicyRoom_FilesOnly": "Yalnızca dosyaları buda, iletileri koru",
"RetentionPolicyRoom_MaxAge": "Maksimum ileti yaşı gün olarak (varsayılan: __max__)",
- "RetentionPolicyRoom_OverrideGlobal": "Küresel saklama politikasını geçersiz kıl",
+ "RetentionPolicyRoom_OverrideGlobal": "Genel saklama politikasını geçersiz kıl",
"RetentionPolicyRoom_ReadTheDocs": "Dikkat! Azami özen göstermeden bu ayarları yapmak tüm ileti geçmişini yok edebilir. Lütfen buradan özelliği açmadan önce belgelemeyi okuyun.",
"Retry_Count": "Yeniden Dene Sayımı",
+ "Return_to_home": "Anasayfaya dön",
+ "Return_to_previous_page": "Önceki sayfaya dön",
"Robot_Instructions_File_Content": "Robots.txt Dosya İçeriği",
"Rocket_Chat_Alert": "Rocket.Chat Uyarısı",
"Role": "rol",
@@ -2410,11 +2437,11 @@
"Room": "Oda",
"Room_announcement_changed_successfully": "Oda anonsu başarıyla değişti",
"Room_archivation_state": "devlet",
- "Room_archivation_state_false": "Aktif",
+ "Room_archivation_state_false": "Etkin",
"Room_archivation_state_true": "arşivlenen",
"Room_archived": "Oda arşivlenen",
"room_changed_announcement": "Oda duyurusu şu şekilde değişti: __room_announcement__ tarafından: __user_by__ ",
- "room_changed_description": "Oda açıklaması değiştirildi: __room_description__ tarafından: __user_by__ ",
+ "room_changed_description": "__user_by__ oda açıklamasını değiştirdi: __room_description__ ",
"room_changed_privacy": "__user_by__ Tarafından __room_type__: Oda tipi değiştirildi",
"room_changed_topic": "Oda konusu __room_topic__ olarak __user_by__ tarafından değiştirildi",
"Room_default_change_to_private_will_be_default_no_more": "Bu varsayılan bir kanaldır ve özel bir grup olarak değiştirmek, artık varsayılan kanal olmamasına neden olacaktır. Devam etmek istiyor musunuz?",
@@ -2436,7 +2463,7 @@
"Room_type_of_default_rooms_cant_be_changed": "Bu varsayılan oda ve tür değiştirilemez, lütfen yöneticinize danışın.",
"Room_unarchived": "arşivden oda",
"Room_uploaded_file_list": "Dosyalar Listesi",
- "Room_uploaded_file_list_empty": "Hiçbir dosya mevcut değil.",
+ "Room_uploaded_file_list_empty": "Hiç dosya yok.",
"Rooms": "Odalar",
"Routing": "Yönlendirme",
"Run_only_once_for_each_visitor": "Her ziyaretçi için sadece bir kez çalıştır",
@@ -2446,7 +2473,7 @@
"run-migration_description": "Taşıma işlemlerini çalıştırma izni",
"Running_Instances": "Örneklerini Çalıştırma",
"Runtime_Environment": "Çalışma Zamanı Ortamı",
- "S_new_messages_since_s": "%s yeni ileti (%s itibaren)",
+ "S_new_messages_since_s": "%s yeni ileti (%s'den beri)",
"Same_As_Token_Sent_Via": "\"Token Sent Via\" ile aynı",
"Same_Style_For_Mentions": "Bahisler için aynı tarz",
"SAML": "SAML",
@@ -2467,7 +2494,7 @@
"save-others-livechat-room-info": "Diğerlerini Kaydedin Livechat Oda Bilgileri",
"save-others-livechat-room-info_description": "Diğer livechat kanallarından bilgi kaydetme izni",
"Save_changes": "Değişiklikleri kaydet",
- "Save_Mobile_Bandwidth": "Mobil Kotanı Koru",
+ "Save_Mobile_Bandwidth": "Mobil Kota Koruma Etkin",
"Save_to_enable_this_action": "Bu eylemi etkinleştirmek için Kaydet",
"Save_To_Webdav": "WebDAV'a kaydet",
"Save_your_encryption_password": "Şifreleme parolanızı kaydedin",
@@ -2479,6 +2506,7 @@
"Screen_Share": "ekran Paylaşımı",
"Script_Enabled": "Senaryo Etkin",
"Search": "Ara",
+ "Search_Apps": "Uygulamalarda Ara",
"Search_by_file_name": "Dosya adına göre ara",
"Search_by_username": "Kullanıcı adına göre ara",
"Search_Channels": "Kanal Arama",
@@ -2506,8 +2534,8 @@
"send-many-messages": "Çok sayıda ileti gönder",
"Send": "Gönder",
"Send_a_message": "Bir ileti gönder",
- "Send_a_test_mail_to_my_user": "benim kullanıcı için bir sınama posta göndermek",
- "Send_a_test_push_to_my_user": "Benim kullanıcıya bir test itme gönder",
+ "Send_a_test_mail_to_my_user": "Kullanıcıma bir deneme e-postası gönder",
+ "Send_a_test_push_to_my_user": "Kullanıcıma bir anlık bildirim gönder",
"Send_confirmation_email": "Doğrulama e-postası gönder",
"Send_data_into_RocketChat_in_realtime": "gerçek zamanlı Rocket.Chat içine veri gönderme.",
"Send_email": "E-posta gönder",
@@ -2515,11 +2543,11 @@
"Send_invitation_email_error": "Girdiğiniz e-posta adresi geçerli değil.",
"Send_invitation_email_info": "Bir seferde çoklu e-posta davetiyeleri gönderebilirsiniz.",
"Send_invitation_email_success": "Aşağıdaki adreslere başarılı bir şekilde davetiye e-postası gönderdiniz:",
- "Send_request_on_agent_message": "Ajan Mesajlarına Talep Gönder",
+ "Send_request_on_agent_message": "Temsilci İletilerinde İstek Gönder",
"Send_request_on_chat_close": "Sohbet yakın üzerinde isteği gönderin",
"Send_request_on_lead_capture": "Kurşun yakalama talebi gönder",
- "Send_request_on_offline_messages": "çevrimdışı iletilerde isteği gönderin",
- "Send_request_on_visitor_message": "Ziyaretçi Mesajlarına İstek Gönder",
+ "Send_request_on_offline_messages": "Çevrimdışı İletilerde İstek Gönder",
+ "Send_request_on_visitor_message": "Ziyaretçi İletilerinde İstek Gönder",
"Send_Test": "Testi gönder",
"Send_Visitor_navigation_history_as_a_message": "Ziyaretçi Gezinme Geçmişini İleti Olarak Gönder",
"Send_visitor_navigation_history_on_request": "İstek üzerine Ziyaretçi Gezinme Geçmişi Gönder",
@@ -2562,11 +2590,11 @@
"Show_name_field": "Ad alanını göster",
"show_offline_users": "çevrimdışı kullanıcıları göster",
"Show_on_registration_page": "Kayıt sayfasında göster",
- "Show_only_online": "yalnızca çevrimiçi göster",
+ "Show_only_online": "Yalnızca çevrimiçini göster",
"Show_on_offline_page": "Çevrimdışı sayfada göster",
"Show_preregistration_form": "ön kayıt formunu göster",
"Show_queue_list_to_all_agents": "Kuyruk listesini tüm temsilcilere göster",
- "Show_room_counter_on_sidebar": "Oda sayacını kenar çubuğunda gösterin",
+ "Show_room_counter_on_sidebar": "Oda sayısı kenar çubuğunda gösterilsin",
"Show_the_keyboard_shortcut_list": "Klavye kısayol listesini göster",
"Showing_archived_results": " %s arşivlenmiş sonuçlar gösteriliyor
",
"Showing_online_users": "__total__ kullanıcıdan __total_showing__ tanesi gösteriliyor",
@@ -2584,7 +2612,7 @@
"SlackBridge_error": "SlackBridge, iletilerinizi%s:%s içeri aktarırken hata verdi.",
"SlackBridge_finish": "SlackBridge, mesajları%s alanına aktarmayı bitirdi. Tüm mesajları görüntülemek için lütfen tekrar yükleyin.",
"SlackBridge_Out_All": "SlackBridge Out All",
- "SlackBridge_Out_All_Description": "Slack'te bulunan ve bot'un katıldığı tüm kanallardan mesaj gönder",
+ "SlackBridge_Out_All_Description": "Slack üzerinde mevcut tüm kanallardaki iletileri ve botun katıldığını gönder",
"SlackBridge_Out_Channels": "Gevşek Çıkış Kanalları",
"SlackBridge_Out_Channels_Description": "Hangi kanalların Mesajları Slack'e geri göndereceğini seçin",
"SlackBridge_Out_Enabled": "SlackBridge Çıkışı Etkin",
@@ -2592,7 +2620,7 @@
"SlackBridge_start": "@%s, `#%s 'adresinden bir SlackBridge içe aktarma işlemi başlattı. Bitirince size haber vereceğiz.",
"Slash_Gimme_Description": "İletilerinizden önce ༼ つ ◕_◕ ༽つ gösterir",
"Slash_LennyFace_Description": "İletilerinizden sonra ( ͡° ͜ʖ ͡°) gösterir",
- "Slash_Shrug_Description": "Ekranlar ¯ \\ _ (ツ) _ / ¯ senin mesajından sonra",
+ "Slash_Shrug_Description": "İletilerinizden sonra ¯\\_(ツ)_/¯ gösterir",
"Slash_Tableflip_Description": "Ekranlar (╯ ° □ °) ╯( ┻━┻",
"Slash_TableUnflip_Description": "Ekranlar ┬─┬ ノ (゜ - ゜ ノ)",
"Slash_Topic_Description": "Konu ayarla",
@@ -2614,10 +2642,10 @@
"SMTP_Port": "SMTP Bağlantı Noktası(Port)",
"SMTP_Test_Button": "Test SMTP Ayarları",
"SMTP_Username": "SMTP Kullanıcı Adı",
- "snippet-message": "Snippet Mesajı",
- "snippet-message_description": "Snippet iletisi oluşturmak için izin",
+ "snippet-message": "Snippet İletisi",
+ "snippet-message_description": "Snippet iletisi oluşturma izni",
"Snippet_Added": "%s üzerinde oluşturuldu",
- "Snippet_Messages": "Snippet Mesajları",
+ "Snippet_Messages": "Snippet İletileri",
"Snippet_name": "Snippet adı",
"Snippeted_a_message": "Bir snippet oluşturdu __snippetLink__",
"Social_Network": "Sosyal ağ",
@@ -2633,17 +2661,17 @@
"Start_audio_call": "Ses aramayı başlatmak",
"Start_Chat": "Sohbete Başla",
"Start_of_conversation": "Görüşme başlangıcı",
- "Start_OTR": "Başlangıç OTR",
- "Start_video_call": "Video araması başlat",
+ "Start_OTR": "Kayıt Dışını başlat",
+ "Start_video_call": "Görüntülü görüşme başlat",
"Start_video_conference": "Video konferansa başlasın mı?",
"Start_with_s_for_user_or_s_for_channel_Eg_s_or_s": "Ile başlayın %s
kullanıcı veya için %s
kanalı için. Örn: %s
veya %s
",
- "Started_a_video_call": "Video Görüşme Başladı",
+ "Started_a_video_call": "Görüntülü Görüşme başlattı",
"Started": "Başladı",
"Started_At": "At başladı",
"Statistics": "İstatistik",
"Statistics_reporting": "İstatistikleri Rocket.Chat'e gönder",
"Statistics_reporting_Description": "istatistiklerinizi göndererek, Rocket.Chat birçok örnekleri konuşlandırıldığı nasıl bize sistemin nasıl davrandığını iyi yanı sıra, tanımlamaya yardımcı olacak, bu yüzden daha da artırabilir. hiçbir kullanıcı bilgisi gönderilir ve aldığımız tüm bilgiler gizli tutulur olarak, endişe etmeyin.",
- "Stats_Active_Users": "Aktif Kullanıcılar",
+ "Stats_Active_Users": "Etkin Kullanıcılar",
"Stats_Avg_Channel_Users": "Kanal Kullanıcıları Ortalaması",
"Stats_Avg_Private_Group_Users": "Özel Grup Kullanıcıları Ortalaması ",
"Stats_Away_Users": "Uzakta Olan Kullanıcılar",
@@ -2655,9 +2683,9 @@
"Stats_Total_Direct_Messages": "Toplam Doğrudan İleti Odaları",
"Stats_Total_Livechat_Rooms": "Toplam Canlı Chat Odası",
"Stats_Total_Messages": "Toplam İleti Sayısı",
- "Stats_Total_Messages_Channel": "Kanallarda Toplam İleti",
- "Stats_Total_Messages_Direct": "Doğru Mesajlardaki Toplam İleti",
- "Stats_Total_Messages_Livechat": "Canlı Sohbette Toplam İleti",
+ "Stats_Total_Messages_Channel": "Kanallardaki Toplam İleti",
+ "Stats_Total_Messages_Direct": "Doğru İletilerdeki Toplam İleti",
+ "Stats_Total_Messages_Livechat": "Canlı Sohbetteki Toplam İleti",
"Stats_Total_Messages_PrivateGroup": "Özel Gruplardaki Toplam İleti",
"Stats_Total_Private_Groups": "Özel Gruplar Toplamı",
"Stats_Total_Rooms": "Odaların Toplamı",
@@ -2668,7 +2696,7 @@
"Step": "Adım",
"Stop_Recording": "Kaydı Durdur",
"Store_Last_Message": "Son İletiyi Sakla",
- "Store_Last_Message_Sent_per_Room": "Her odaya gönderilen son mesajı sakla.",
+ "Store_Last_Message_Sent_per_Room": "Her bir odaya gönderilen son ileti saklansın.",
"Stream_Cast": "Akarsu Oyuncular",
"Stream_Cast_Address": "Akış Oyuncular Adresi",
"Stream_Cast_Address_Description": "IP veya Rocket.Chat central Stream Cast Oyuncuları. Örneğin. `192.168.1.1: 3000` veya` localhost: 4000`",
@@ -2677,19 +2705,20 @@
"Subject": "Konu",
"Submit": "Gönder",
"Success": "Başarı",
- "Success_message": "Başarı mesajı",
+ "Success_message": "Başarı iletisi",
"Successfully_downloaded_file_from_external_URL_should_start_preparing_soon": "Dış URL'den dosya başarılı bir şekilde indirildi, yakında hazırlanmaya başlamalı",
"Sunday": "Pazar",
"Support": "Yardım",
"Survey": "Anket",
"Survey_instructions": "Tamamen memnun anlam tamamen tatminsiz olan, yani sizi tatmin göre 1 her soruyu oranı ve 5.",
"Symbols": "Semboller",
+ "Sync": "Eşitle",
"Sync / Import": "Eşitle / İçe Aktar",
"Sync_in_progress": "Senkronizasyon devam ediyor",
"Sync_Interval": "Eşitleme sıklığı",
"Sync_success": "Senkronizasyon başarılı",
"Sync_Users": "Kullanıcıları senkronize et",
- "System_messages": "Sistem Mesajları",
+ "System_messages": "Sistem İletileri",
"Tag": "Etiket",
"Take_it": "Bunu al!",
"TargetRoom": "Hedef Oda",
@@ -2714,6 +2743,7 @@
"The_server_will_restart_in_s_seconds": "Sunucu %s saniye içinde yeniden başlayacak",
"The_setting_s_is_configured_to_s_and_you_are_accessing_from_s": "%s ayarı %s şeklinde yapılandırıldı ve %s üzerinden erişiyorsunuz!",
"The_user_will_be_removed_from_s": "%s kullanıcısı silinecektir",
+ "The_user_s_will_be_removed_from_role_s": "%s adlı kullanıcının %s rolü kaldırılacaktır",
"The_user_wont_be_able_to_type_in_s": "%s kullanıcısı yazmaya mümkün olmayacaktır",
"Theme": "Tema",
"theme-color-attention-color": "Dikkat Rengi",
@@ -2777,7 +2807,7 @@
"This_conversation_is_already_closed": "Bu konuşma zaten kapandı.",
"This_email_has_already_been_used_and_has_not_been_verified__Please_change_your_password": "Bu e-posta zaten kullanıldı ve doğrulanmadı. Lütfen şifrenizi değiştirin.",
"This_is_a_desktop_notification": "Bu bir masaüstü bildirimi",
- "This_is_a_push_test_messsage": "Bu itme testi messsage olduğunu",
+ "This_is_a_push_test_messsage": "Bu bir deneme anlık bildirim iletisidir",
"This_message_was_rejected_by__peer__peer": "Bu ileti __peer__ tarafından reddedildi.",
"This_month": "Bu Ay",
"This_room_has_been_archived_by__username_": "Bu oda __username__ tarafından arşivlenmiştir.",
@@ -2820,7 +2850,7 @@
"Total_visitors": "Toplam Ziyaretçi",
"Tourism": "turizm",
"Transcript_Enabled": "Sohbet Kapanışından Sonra Bir Transkript Olsun mı Ziyaretçiye Sorun",
- "Transcript_message": "Transkript Hakkında Sormak İsterseniz Gösterilecek Mesaj",
+ "Transcript_message": "Transkript Hakkında Sormak İsterseniz Gösterilecek İleti",
"Transcript_of_your_livechat_conversation": "Canlı sohbet konuşmasının not belgesi.",
"Translated": "Çeviri",
"Translations": "Çeviriler",
@@ -2833,15 +2863,15 @@
"Turn_OFF": "Kapat",
"Turn_ON": "Aç",
"Two Factor Authentication": "İki Aşamalı Kimlik Doğrulama",
- "Two-factor_authentication": "İki faktörlü kimlik doğrulama",
- "Two-factor_authentication_disabled": "İki faktörlü kimlik doğrulama devre dışı",
- "Two-factor_authentication_enabled": "İki faktörlü kimlik doğrulama etkin",
- "Two-factor_authentication_is_currently_disabled": "İki faktörlü kimlik doğrulama şu anda devre dışı",
+ "Two-factor_authentication": "İki aşamalı kimlik doğrulama",
+ "Two-factor_authentication_disabled": "İki aşamalı kimlik doğrulama devre dışı",
+ "Two-factor_authentication_enabled": "İki aşamalı kimlik doğrulama etkin",
+ "Two-factor_authentication_is_currently_disabled": "İki aşamalı kimlik doğrulama şu anda devre dışı",
"Two-factor_authentication_native_mobile_app_warning": "UYARI: Bunu etkinleştirdiğinizde, yerel mobil uygulamalara (Rocket.Chat +) şifrenizi kullanarak 2FA'yı uygulayana kadar giriş yapamazsınız.",
"Type": "tip",
"Type_your_email": "E-postanızı yazın",
"Type_your_job_title": "İş başlığınızı yazın",
- "Type_your_message": "Mesajınızı yazın",
+ "Type_your_message": "İletinizi yazın",
"Type_your_name": "Adınızı yazın",
"Type_your_new_password": "Yeni şifrenizi yazın",
"Type_your_password": "Şifrenizi giriniz",
@@ -2853,7 +2883,7 @@
"UI_Group_Channels_By_Type": "Kanalları türe göre grupla",
"UI_Merge_Channels_Groups": "kanallı özel gruplar birleştir",
"UI_Unread_Counter_Style": "Okunmamış Karşı Stil",
- "UI_Use_Name_Avatar": "Varsayılan Avatar Oluşturmak İçin Ad Soyadın İlk Harfleri Kullanılsın",
+ "UI_Use_Name_Avatar": "Varsayılan Avatarı Oluşturmak İçin Ad Soyadın İlk Harfleri Kullanılsın",
"UI_Use_Real_Name": "Gerçek Adı Kullan",
"Unarchive": "Arşivden Çıkar",
"unarchive-room": "Unarchive Oda",
@@ -2868,13 +2898,13 @@
"Unpin_Message": "İletiyi Ayır",
"Unread": "Okunmamış",
"Unread_Count": "Okunmamış Sayım",
- "Unread_Count_DM": "Doğru Mesajlar İçin Okunmamış Sayım",
- "Unread_Messages": "Okunmamış Mesajlar",
+ "Unread_Count_DM": "Doğrudan İletiler için Okunmamış Sayısı",
+ "Unread_Messages": "Okunmamış İletiler",
"Unread_on_top": "Okunmamışlar üstte",
"Unread_Rooms": "Okunmamış Odalar",
"Unread_Rooms_Mode": "Okunmamış Odalar Modu",
"Unread_Tray_Icon_Alert": "Okunmamış Kaset Simgesi Uyarısı",
- "Unstar_Message": "Yıldızı kaldır",
+ "Unstar_Message": "Favorilerden çıkar",
"Update": "Güncelle",
"Update_LatestAvailableVersion": "Son Sürüme Güncelle",
"Update_to_version": "Versiyonu güncelle __Version__",
@@ -2890,8 +2920,8 @@
"Uptime": "uptime",
"URL": "URL",
"URL_room_prefix": "URL Oda Prefix",
- "Use_account_preference": "Hesap tercihi kullanın",
- "Use_Emojis": "Emoji Kullanın",
+ "Use_account_preference": "Hesap tercihini kullan",
+ "Use_Emojis": "Emoji Kullanılsın",
"Use_Global_Settings": "Genel Ayarları Kullan",
"Use_initials_avatar": "Kullanıcı adınızın ilk iki harfini kullanın",
"Use_minor_colors": "Küçük renk paletini kullanın (varsayılanlar büyük renkleri taşır)",
@@ -2899,8 +2929,8 @@
"Verification_Email": "Hesabınızı doğrulamak için buraya tıklayın",
"Use_this_username": "Bu kullanıcı adını kullan",
"Use_uploaded_avatar": "Yüklenen avatarı kullanın",
- "Use_url_for_avatar": "avatar için url kullanın",
- "Use_User_Preferences_or_Global_Settings": "Kullanıcı Tercihlerini veya Genel Ayarlar'ı kullanın",
+ "Use_url_for_avatar": "Avatar için URL kullan",
+ "Use_User_Preferences_or_Global_Settings": "Kullanıcı Tercihlerini veya Genel Ayarları Kullan",
"User": "Kullanıcı",
"User Search": "Kullanıcı Arama",
"User Search (Group Validation)": "Kullanıcı Arama (Grup Doğrulama)",
@@ -2919,19 +2949,19 @@
"User_default": "Kullanıcı varsayılanı",
"User_doesnt_exist": "`@%s` isminde bir kullanıcı bulunmamaktadır.",
"User_e2e_key_was_reset": "Kullanıcı Uçtan Uca anahtarı başarılı bir şekilde sıfırlandı.",
- "User_has_been_activated": "Kullanıcı aktif edildi",
+ "User_has_been_activated": "Kullanıcı etkinleştirildi",
"User_has_been_deactivated": "Kullanıcı devre dışı bırakıldı",
"User_has_been_deleted": "Kullanıcı silindi",
- "User_has_been_ignored": "Kullanıcı göz ardı edildi",
+ "User_has_been_ignored": "Kullanıcı yoksayılanlara alındı",
"User_has_been_muted_in_s": "Kullanıcı %s sesi kapatıldı",
"User_has_been_removed_from_s": "Kullanıcı %s kaldırıldı",
- "User_has_been_unignored": "Kullanıcı artık göz ardı edilmiyor",
+ "User_has_been_unignored": "Kullanıcı artık yoksayılanlarda değil",
"User_Info": "Kullanıcı Bilgileri",
"User_Interface": "Kullanıcı Arayüzü",
"User_is_blocked": "Kullanıcı engellendi",
"User_is_no_longer_an_admin": "Kullanıcı artık bir yönetici değil",
"User_is_now_an_admin": "Kullanıcı şimdi bir yönetici",
- "User_is_unblocked": "Kullanıcı engellendi",
+ "User_is_unblocked": "Kullanıcının engeli kaldırıldı",
"User_joined_channel": "Kanala katıldı.",
"User_joined_conversation": "Konuşmaya katıldı",
"User_joined_channel_female": "Kanala katıldı.",
@@ -2950,7 +2980,7 @@
"User_Presence": "Kullanıcı Durumları",
"User_removed": "kullanıcı kaldırıldı",
"User_removed_by": "__user_removed__ __user_by__ tarafından kaldırıldı.",
- "User_sent_a_message_on_channel": "__username__ , __channel__ ile ilgili bir ileti gönderdi:",
+ "User_sent_a_message_on_channel": "__username__ , __channel__ kanalına bir ileti gönderdi:",
"User_sent_a_message_to_you": "__username__ size bir ileti gönderdi:",
"user_sent_an_attachment": "__user__ bir ek gönderdi",
"User_Settings": "Kullanıcı Ayarları",
@@ -2978,16 +3008,16 @@
"Username_and_message_must_not_be_empty": "Kullanıcı adı ve ileti boş olmamalı.",
"Username_cant_be_empty": "Kullanıcı adı boş bırakılamaz",
"Username_Change_Disabled": "Kullanıcı adı değişimini yönetim tarafından devre dışı bırakılmıştır",
- "Username_denied_the_OTR_session": "__username__ adlı OTR oturumu reddedildi",
+ "Username_denied_the_OTR_session": "__username__ Kayıt Dışı oturumu reddetti",
"Username_description": "Kullanıcı adı, diğerlerinin iletilerde sizden bahsetmeleri için kullanılır.",
"Username_doesnt_exist": "`%s` şeklinde bir kullanıcı adı mevcut değil.",
- "Username_ended_the_OTR_session": "__username__ OTR oturumunu sona erdirdi",
+ "Username_ended_the_OTR_session": "__username__ Kayıt Dışı oturumu sonlandırdı",
"Username_invalid": "%s geçerli bir kullanıcı adı değil, Kullanıcı adınızı seçerken yalnızca harfler, sayılar, noktalar, tireler ve alt çizgiler kullanın",
"Username_is_already_in_here": "`@%s` zaten burada.",
"Username_is_not_in_this_room": "`#%s` adlı kullanıcı bu odada değil.",
"Username_Placeholder": "Lütfen kullanıcı adlarını giriniz...",
"Username_title": "Kullanıcı adını kaydet",
- "Username_wants_to_start_otr_Do_you_want_to_accept": "__username__ adlı OTR başlamak istiyor. kabul etmek istiyor musunuz?",
+ "Username_wants_to_start_otr_Do_you_want_to_accept": "__username__ Kayıt Dışı başlatmak istiyor. Kabul etmek istiyor musunuz?",
"Users": "Kullanıcılar",
"Users_added": "Kullanıcılar eklendi",
"Users_in_role": "rolü Kullanıcılar",
@@ -3005,17 +3035,17 @@
"Verify": "Doğrula",
"Verify_your_email": "E-postanızı doğrulayın",
"Version": "versiyon",
- "Video Conference": "Video Konferans",
- "Video_Chat_Window": "Görüntülü Sohbet",
- "Video_Conference": "Video Konferans",
- "Video_message": "Video mesajı",
+ "Video Conference": "Görüntülü Görüşme",
+ "Video_Chat_Window": "Görüntülü Görüşme",
+ "Video_Conference": "Görüntülü Görüşme",
+ "Video_message": "Görüntülü ileti",
"Videocall_declined": "Video Görüşmesi Reddedildi.",
"Videocall_enabled": "Video Çağrısı Etkin",
"view-broadcast-member-list": "Yayın Odasındaki Üye Listesini Görüntüle",
"view-c-room": "Genel Kanalı Görüntüle",
"view-c-room_description": "Herkese açık kanalları görüntüleme izni",
- "view-d-room": "Doğrudan Mesajları Görüntüle",
- "view-d-room_description": "Doğrudan mesajları görüntüleme izni",
+ "view-d-room": "Doğrudan İletileri Görüntüle",
+ "view-d-room_description": "Doğrudan iletileri görüntüleme izni",
"view-full-other-user-info": "Tam Diğer Kullanıcı Bilgisi",
"view-full-other-user-info_description": "Hesap oluşturma tarihi, son giriş, vb. De dahil olmak üzere diğer kullanıcıların tam profilini görüntüleme izni.",
"view-history": "Geçmişi Görüntüle",
@@ -3077,7 +3107,7 @@
"WebRTC_direct_audio_call_from_%s": "%s adlı kullanıcıdan doğrudan sesli arama",
"WebRTC_direct_video_call_from_%s": "%s adlı kullanıcıdan doğrudan görüntülü arama",
"WebRTC_Enable_Channel": "Kanallar için etkinleştir",
- "WebRTC_Enable_Direct": "Doğrudan Mesajlar için etkinleştir",
+ "WebRTC_Enable_Direct": "Doğrudan İletiler için Etkin",
"WebRTC_Enable_Private": "Özel Kanallar için etkinleştir",
"WebRTC_group_audio_call_from_%s": "%s grubundan sesli arama",
"WebRTC_group_video_call_from_%s": "%s grubundan video görüntülü görüşme",
@@ -3109,7 +3139,7 @@
"you_are_in_preview_mode_of": "#__room_name__ kanalının önizleme modundasınız.",
"You_are_logged_in_as": "Oturum açtınız:",
"You_are_not_authorized_to_view_this_page": "Bu sayfayı görüntüleme yetkiniz yok.",
- "You_can_change_a_different_avatar_too": "Bu entegrasyondan göndermek için kullanılan avatar geçersiz kılabilirsiniz.",
+ "You_can_change_a_different_avatar_too": "Bu entegrasyondan göndermek için kullanılan avatarı ezebilirsiniz.",
"You_can_close_this_window_now": "Artık bu pencereyi kapatabilirsiniz.",
"You_can_search_using_RegExp_eg": "RegExp kullanarak arama yapabilirsiniz. Örneğin /^text$/i
",
"You_can_use_an_emoji_as_avatar": "Avatar olarak emoji de kullanabilirsiniz.",
@@ -3126,7 +3156,7 @@
"You_need_to_change_your_password": "Şifrenizi değiştirmeniz gerekiyor",
"You_need_to_type_in_your_password_in_order_to_do_this": "Bunu yapmak için şifrenizi yazmanız gerekiyor!",
"You_need_to_type_in_your_username_in_order_to_do_this": "Bunu yapmak için kullanıcı adınızı yazmanız gerekiyor!",
- "You_need_to_verifiy_your_email_address_to_get_notications": "Sen bildirimleri almak için e-posta adresinizi doğrulanana gerekiyor",
+ "You_need_to_verifiy_your_email_address_to_get_notications": "Bildirim almak için e-posta adresinizin doğrulanmış olması gerekiyor",
"You_need_to_write_something": "Bir şey yazmanız gerekiyor!",
"You_should_inform_one_url_at_least": "En az bir URL tanımlamalısınız.",
"You_should_name_it_to_easily_manage_your_integrations": "Entegrasyonları kolayca yönetmek için ad vermelisiniz.",
@@ -3140,7 +3170,7 @@
"your_message": "iletiniz",
"your_message_optional": "iletiniz (isteğe bağlı)",
"Your_password_is_wrong": "Şifreniz hatalı!",
- "Your_push_was_sent_to_s_devices": "Bildiriminiz %s aygıtlara gönderildi",
+ "Your_push_was_sent_to_s_devices": "Anlık bildiriminiz %s aygıta gönderildi",
"Your_question": "Sorunuz",
"Your_server_link": "Sunucu bağlantınız",
"Your_workspace_is_ready": "Çalışma alanınız kullanılmaya hazır 🎉"
diff --git a/packages/rocketchat-i18n/i18n/zh.i18n.json b/packages/rocketchat-i18n/i18n/zh.i18n.json
index cf64dcb7c9aa..6a950fd99707 100644
--- a/packages/rocketchat-i18n/i18n/zh.i18n.json
+++ b/packages/rocketchat-i18n/i18n/zh.i18n.json
@@ -343,13 +343,15 @@
"Apply_and_refresh_all_clients": "应用并刷新所有客户端",
"Apps": "应用",
"Apps_Engine_Version": "应用程序引擎版本",
+ "Apps_Framework_Development_Mode": "启用开发模式",
+ "Apps_Framework_Development_Mode_Description": "开发模式允许您安装那些不在 Rocket.Chat 市场中的应用。",
"Apps_Framework_enabled": "启用应用框架",
"Apps_Settings": "应用的设置",
"Apps_WhatIsIt": "应用程序:它们是什么?",
"Apps_WhatIsIt_paragraph1": "管理后台的新图标!这是什么意思,什么是应用程序?",
"Apps_WhatIsIt_paragraph2": "首先,在这种情况下的应用程序不涉及移动应用程序。事实上,最好从插件或高级集成角度考虑它们。",
"Apps_WhatIsIt_paragraph3": "其次,它们是动态脚本或软件包,它允许您自定义 Rocket.Chat 实例而不必分叉代码库。但请记住,这是一个新的功能集,因为它可能不是 100% 稳定。此外,我们仍在开发功能集,因此当前并非所有功能都可以在此进行定制。有关开发应用程序的更多信息,请点击此处阅读:",
- "Apps_WhatIsIt_paragraph4": "但是据说,如果您有兴趣启用此功能并尝试使用,请点击此按钮启用 Apps 系统。",
+ "Apps_WhatIsIt_paragraph4": "但话虽如此,如果您有兴趣启用此功能并尝试使用,请点击此按钮启用 Apps 系统。",
"Archive": "归档",
"archive-room": "归档聊天室",
"archive-room_description": "归档频道的权限",
@@ -449,6 +451,7 @@
"Broadcasting_client_secret": "广播客户端 Secret",
"Broadcasting_enabled": "启用广播",
"Broadcasting_media_server_url": "广播媒体服务器 URL",
+ "Browse_Files": "浏览文件",
"Bugsnag_api_key": "Bugsnag API Key",
"Build_Environment": "构建环境",
"bulk-create-c": "批量创建频道",
@@ -513,6 +516,7 @@
"Channels_list": "公共频道列表",
"Chat_button": "聊天按钮",
"Chat_closed": "聊天已关闭",
+ "Chat_closed_by_agent": "客服关闭了聊天",
"Chat_closed_successfully": "聊天已成功关闭",
"Chat_Now": "现在聊天",
"Chat_window": "聊天窗口",
@@ -596,11 +600,14 @@
"Closed_by_visitor": "由访客关闭",
"Closing_chat": "正在关闭聊天",
"Cloud": "Cloud ",
+ "Cloud_workspace_connected_plus_account": "您的工作区现已连接到 Rocket.Chat Cloud 并且帐户已关联。",
"Cloud_connect": "Rocket.Chat Cloud 连接",
+ "Cloud_workspace_connected_without_account": "您的工作区现在已连接到 Rocket.Chat Cloud。如果您愿意,可以登录 Rocket.Chat Cloud 并将您的工作区与您的 Cloud 帐户相关联。",
"Cloud_what_is_it": "这是什么?",
"Cloud_what_is_it_description": "Rocket.Chat Cloud 连接允许您将自托管的 Rocket.Chat 工作区连接到我们的云端。这样,您就可以在 Rocket.Chat Cloud 中管理许可证,计费和支持。",
- "Cloud_workspace_connected_plus_account": "您的工作区现已连接到 Rocket.Chat Cloud 并且帐户已关联。",
- "Cloud_workspace_connected_without_account": "您的工作区现在已连接到 Rocket.Chat Cloud。如果您愿意,可以登录 Rocket.Chat Cloud 并将您的工作区与您的 Cloud 帐户相关联。",
+ "Cloud_workspace_connected": "您的工作区已成功连接到 Rocket.Chat 云。您可以通过云来管理账号信息",
+ "Cloud_workspace_support": "如果您遇到任何云服务的问题, 请先尝试同步。如果问题依旧, 请在云控制台中开启一个支持工单。",
+ "Cloud_workspace_disconnect": "如果您不想继续使用云服务, 您可以断开工作区和 Rocket.Chat 云的连接。",
"Cloud_login_to_cloud": "登录至 Rocket.Chat Cloud",
"Cloud_address_to_send_registration_to": "您用来注册 Cloud 的电子邮件的地址。",
"Cloud_update_email": "更新邮箱",
@@ -1034,6 +1041,7 @@
"Disabled": "已禁用",
"Disallow_reacting": "不允许反应",
"Disallow_reacting_Description": "不允许反应",
+ "Disconnect": "断开连接",
"Display_offline_form": "显示离线表单",
"Display_unread_counter": "显示未读消息的数量",
"Displays_action_text": "显示动作文字",
@@ -1391,6 +1399,7 @@
"Forward_chat": "转发对话",
"Forward_to_department": "转到部门",
"Forward_to_user": "转到用户",
+ "Free": "免费",
"Frequently_Used": "常用",
"Friday": "星期五",
"From": "从",
@@ -1543,6 +1552,7 @@
"Install_FxOs_follow_instructions": "请在设备上确认安装应用(根据提示点击“安装”)",
"Install_package": "安装软件包",
"Installation": "安装",
+ "Installed": "已安装",
"Installed_at": "安装在",
"Invitation_HTML": "邀请邮件 HTML",
"Instance_Record": "实例记录",
@@ -2292,6 +2302,7 @@
"Public_Channel": "公共频道",
"Public_Community": "公共社区",
"Public_Relations": "公共关系",
+ "Purchased": "已购买",
"Push": "推送",
"Push_Setting_Requires_Restart_Alert": "更改此值需要重新启动 Rocket.Chat 。",
"Push_apn_cert": "APN 证书",
@@ -2310,6 +2321,8 @@
"Push_show_message": "在通知中显示消息",
"Push_show_username_room": "在通知中显示频道/群组/用户名",
"Push_test_push": "测试",
+ "Purchase_for_free": "免费购买",
+ "Purchase_for_price": "以 $%s 购买",
"Query": "请求",
"Query_description": "决定向哪些用户发送电子邮件的额外条件。取消订阅的用户会自动从请求中删除。这必须是有效的 JSON 数据,例如:`{\"createdAt\":{\"$gt\":{\"$date\": \"2015-01-01T00:00:00.000Z\"}}}`。",
"Queue": "队列",
@@ -2384,6 +2397,7 @@
"Report_sent": "报告已发送",
"Report_this_message_question_mark": "举报此消息?",
"Reporting": "报告",
+ "Request_comment_when_closing_conversation": "在聊天结束时请求评价",
"Require_all_tokens": "要求所有令牌",
"Require_any_token": "需要任何令牌",
"Require_password_change": "要求修改密码",
@@ -2426,6 +2440,8 @@
"RetentionPolicyRoom_OverrideGlobal": "覆盖全局保留策略",
"RetentionPolicyRoom_ReadTheDocs": "小心!调整这些设置可能会破坏所有消息历史记录。请在此处 启用该功能之前阅读文档。",
"Retry_Count": "重试计数",
+ "Return_to_home": "返回主页",
+ "Return_to_previous_page": "返回前一页",
"Robot_Instructions_File_Content": "Robots.txt 文件内容",
"Rocket_Chat_Alert": "Rocket.Chat 提醒",
"Role": "角色",
@@ -2503,6 +2519,7 @@
"Screen_Share": "屏幕共享",
"Script_Enabled": "脚本已启用",
"Search": "搜索",
+ "Search_Apps": "搜索应用",
"Search_by_file_name": "按文件名搜索",
"Search_by_username": "按用户名搜索",
"Search_Channels": "搜索频道",
@@ -2708,6 +2725,7 @@
"Survey": "调查",
"Survey_instructions": "根据问题填写您的满意度,1 代表完全不满意,5 代表非常满意。",
"Symbols": "符号",
+ "Sync": "同步",
"Sync / Import": "同步 / 导入",
"Sync_in_progress": "同步正在进行中",
"Sync_Interval": "同步间隔",
diff --git a/packages/rocketchat-livechat/.app/client/lib/chatMessages.js b/packages/rocketchat-livechat/.app/client/lib/chatMessages.js
index c4edf1ae596b..95880c92e25a 100644
--- a/packages/rocketchat-livechat/.app/client/lib/chatMessages.js
+++ b/packages/rocketchat-livechat/.app/client/lib/chatMessages.js
@@ -142,7 +142,7 @@ this.ChatMessages = class ChatMessages {
});
};
- if (!visitor.getId()) {
+ if (!visitor.getId() || visitor.getDepartment() !== Livechat.department) {
const guest = {
token: visitor.getToken(),
};
diff --git a/packages/rocketchat-livechat/.app/client/views/messages.js b/packages/rocketchat-livechat/.app/client/views/messages.js
index 593bbeb595a7..5bc18ec8f535 100644
--- a/packages/rocketchat-livechat/.app/client/views/messages.js
+++ b/packages/rocketchat-livechat/.app/client/views/messages.js
@@ -134,6 +134,7 @@ Template.messages.events({
}
visitor.setId(result.userId);
+ visitor.setData(result.visitor);
LivechatVideoCall.request();
});
} else {
diff --git a/packages/rocketchat-livechat/.app/client/views/register.js b/packages/rocketchat-livechat/.app/client/views/register.js
index 6252821b150f..ee7306c5a501 100644
--- a/packages/rocketchat-livechat/.app/client/views/register.js
+++ b/packages/rocketchat-livechat/.app/client/views/register.js
@@ -77,9 +77,7 @@ Template.register.events({
}
}
- if (departmentId) {
- Livechat.department = departmentId;
- }
+ Livechat.department = departmentId;
const guest = {
token: visitor.getToken(),
diff --git a/packages/rocketchat-livechat/.app/i18n/tr.i18n.json b/packages/rocketchat-livechat/.app/i18n/tr.i18n.json
index 0f58585056a7..4bb7146cbd98 100644
--- a/packages/rocketchat-livechat/.app/i18n/tr.i18n.json
+++ b/packages/rocketchat-livechat/.app/i18n/tr.i18n.json
@@ -17,15 +17,15 @@
"How_responsive_was_the_chat_agent": "Müşteri temsilcisi sorularınıza ne kadar cevap verdi?",
"How_satisfied_were_you_with_this_chat": "Bu sohbetten ne kadar memnun kaldınız?",
"Installation": "Kurulum",
- "New_messages": "Yeni Mesajlar",
+ "New_messages": "Yeni iletiler",
"New_livechat_in_queue": "Sıradaki yeni sohbet",
"No": "Hayır",
"Options": "Seçenekler",
"Please_answer_survey": "Lütfen bu sohbet hakkında hızlı bir anket yapmak için bir dakikanızı ayırın",
"Please_choose_a_department": "Lütfen bir bölüm seçin",
- "Please_fill_name_and_email": "Lütfen isim ve e-posta alanlarını doldurunuz",
+ "Please_fill_name_and_email": "Lütfen ad ve e-posta giriniz",
"Powered_by": "Tarafından Desteklenmektedir",
- "Request_video_chat": "Görüntülü sohbet isteği gönder",
+ "Request_video_chat": "Görüntülü görüşme isteği gönder",
"Select_a_department": "Departman seç",
"Switch_department": "Bölüm değiştir",
"Department_switched": "Bölüm değiştirildi",
@@ -38,12 +38,12 @@
"Thanks_We_ll_get_back_to_you_soon": "Teşekkürler! Size yakın zamanda döneceğiz.",
"transcript_sent": "Tanskript Gönderildi",
"Type_your_email": "E-postanızı yazın",
- "Type_your_message": "Mesajınızı yazın",
+ "Type_your_message": "İletinizi yazın",
"Type_your_name": "Adınızı yazın",
"Upload_file_question": "Dosya yükle",
"User_joined": "Kullanıcı katıldı",
"User_left": "Kullanıcı ayrıldı",
- "We_are_not_online_right_now_please_leave_a_message": "Şu anda çevrimiçi değiliz. Lütfen mesaj bırakın.",
+ "We_are_not_online_right_now_please_leave_a_message": "Şu anda çevrimiçi değiliz. Lütfen bir ileti bırakın.",
"We_are_offline_Sorry_for_the_inconvenience": "Çevrimdışıyız. Uygunsuzluktan dolayı özür dileriz.",
"Yes": "Evet",
"You": "Siz",
diff --git a/packages/rocketchat-livechat/.app/imports/client/visitor.js b/packages/rocketchat-livechat/.app/imports/client/visitor.js
index 545036f45546..c1aaf04ef70f 100644
--- a/packages/rocketchat-livechat/.app/imports/client/visitor.js
+++ b/packages/rocketchat-livechat/.app/imports/client/visitor.js
@@ -60,6 +60,11 @@ export default {
return this.token.get();
},
+ getDepartment() {
+ const data = this.getData();
+ return data && data.department;
+ },
+
setToken(token) {
if (!token || token === this.token.get()) {
return;
diff --git a/private/client/imports/general/variables.css b/private/client/imports/general/variables.css
index b39e0ed7c83f..0c7ad35acd3a 100644
--- a/private/client/imports/general/variables.css
+++ b/private/client/imports/general/variables.css
@@ -253,10 +253,24 @@
* Badge
*/
--badge-text-color: var(--color-white);
- --badge-radius: var(--border-radius);
+ --badge-radius: 12px;
--badge-text-size: 0.75rem;
--badge-background: var(--rc-color-primary-dark);
- --badge-unread-background: var(--rc-color-button-primary);
+ --badge-unread-background: var(--rc-color-primary-dark);
+ --badge-dm-background: var(--rc-color-primary-dark);
+ --badge-user-mentions-background: var(--rc-color-button-primary);
+ --badge-group-mentions-background: var(--rc-color-primary-dark);
+
+ /*
+ * Mention link
+ */
+ --mention-link-radius: 10px;
+ --mention-link-background: rgba(29, 116, 245, 0.2);
+ --mention-link-text-color: var(--rc-color-button-primary);
+ --mention-link-me-background: var(--rc-color-button-primary);
+ --mention-link-me-text-color: var(--color-white);
+ --mention-link-group-background: var(--rc-color-primary-dark);
+ --mention-link-group-text-color: var(--color-white);
/*
* Message box
@@ -282,6 +296,8 @@
--header-padding: 16px;
--header-toggle-favorite-color: var(--color-gray-medium);
--header-toggle-favorite-star-color: var(--rc-color-alert-light);
+ --header-toggle-encryption-off-color: var(--color-gray-medium);
+ --header-toggle-encryption-on-color: var(--rc-color-alert-message-secondary);
--header-title-username-color-darker: var(--color-dark);
--header-title-font-size: var(--text-default-size);
--header-title-font-size--subtitle: var(--text-small-size);
@@ -330,4 +346,11 @@
--alerts-color: var(--color-white);
--alerts-font-size: var(--text-default-size);
--content-page-padding: 2.5rem;
+
+ /*
+ * badge
+ */
+ --badge-size: 14px;
+ --badge-font-size: 0.625rem;
+
}
diff --git a/private/server/colors.less b/private/server/colors.less
index bfa7f6d1192e..269cac743c77 100755
--- a/private/server/colors.less
+++ b/private/server/colors.less
@@ -22,11 +22,34 @@
@transparent-darkest: rgba(17, 12, 12, 0.5);
@transparent-darker: rgba(0, 0, 0, 0.15);
-@transparent-dark: rgba(0, 0, 0, 0.05);
+@transparent-dark: rgba(15, 34, 0, 0.05);
@transparent-light: rgba(255, 255, 255, 0.1);
@transparent-lighter: rgba(255, 255, 255, 0.3);
@transparent-lightest: rgba(255, 255, 255, 0.6);
+:root {
+ --legacy-default-action-color: @default-action-color;
+ --legacy-default-action-contrast: @default-action-contrast;
+ --legacy-primary-background-contrast: @primary-background-contrast;
+ --legacy-primary-action-contrast: @primary-action-contrast;
+ --legacy-secondary-background-contrast: @secondary-background-contrast;
+ --legacy-secondary-action-contrast: @secondary-action-contrast;
+ --legacy-selection-background: @selection-background;
+ --legacy-success-background: @success-background;
+ --legacy-success-border: @success-border;
+ --legacy-error-background: @error-background;
+ --legacy-error-border: @error-border;
+ --legacy-error-contrast: @error-contrast;
+ --legacy-pending-background: @pending-background;
+ --legacy-pending-border: @pending-border;
+ --legacy-transparent-darkest: @transparent-darkest;
+ --legacy-transparent-darker: @transparent-darker;
+ --legacy-transparent-dark: @transparent-dark;
+ --legacy-transparent-light: @transparent-light;
+ --legacy-transparent-lighter: @transparent-lighter;
+ --legacy-transparent-lightest: @transparent-lightest;
+}
+
/** ----------------------------------------------------------------------------
* Mixins
*/
@@ -503,14 +526,6 @@ input:-webkit-autofill {
}
.messages-container {
- .edit-room-title {
- color: @secondary-font-color;
-
- &:hover {
- color: @primary-font-color;
- }
- }
-
.footer {
background: @content-background-color;
}
@@ -549,11 +564,6 @@ input:-webkit-autofill {
&.selectable .selected {
background-color: @selection-background;
}
-
- // .editing .body,
- // textarea.editing {
- // background-color: lighten(@pending-color, 40%);
- // }
}
/** ----------------------------------------------------------------------------
@@ -586,20 +596,6 @@ input:-webkit-autofill {
}
}
- .mention-link {
- background-color: fade(@rc-color-button-primary, 20%);
-
- &.mention-link-me {
- background: @primary-action-color;
- color: @primary-action-contrast;
- }
-
- &.mention-link-all {
- background: @attention-color;
- color: @primary-action-contrast;
- }
- }
-
.highlight-text {
background-color: @selection-background;
}
@@ -630,12 +626,6 @@ input:-webkit-autofill {
* Flex tabs / admin fly-out panels
*/
.flex-tab {
- .message {
- &.new-day::before {
- background-color: @secondary-background-color;
- }
- }
-
.channel-settings {
.buttons {
.button {
diff --git a/server/importPackages.js b/server/importPackages.js
index c95d56077667..40daed01b3de 100644
--- a/server/importPackages.js
+++ b/server/importPackages.js
@@ -86,6 +86,7 @@ import '../app/slashcommands-unarchiveroom/server';
import '../app/smarsh-connector';
import '../app/spotify/server';
import '../app/theme/server';
+import '../app/threads/server';
import '../app/tokenpass/server';
import '../app/ui-admin/server';
import '../app/ui-master/server';
diff --git a/server/methods/canAccessRoom.js b/server/methods/canAccessRoom.js
index 928e7c7ac280..ee270af0fa60 100644
--- a/server/methods/canAccessRoom.js
+++ b/server/methods/canAccessRoom.js
@@ -32,25 +32,27 @@ Meteor.methods({
}
const room = Rooms.findOneById(rid);
- if (room) {
- if (canAccessRoom.call(this, room, user, extraData)) {
- if (user) {
- room.username = user.username;
- }
- return room;
- }
- if (!userId && settings.get('Accounts_AllowAnonymousRead') === false) {
- throw new Meteor.Error('error-invalid-user', 'Invalid user', {
- method: 'canAccessRoom',
- });
+ if (!room) {
+ throw new Meteor.Error('error-invalid-room', 'Invalid room', {
+ method: 'canAccessRoom',
+ });
+
+ }
+
+ if (canAccessRoom.call(this, room, user, extraData)) {
+ if (user) {
+ room.username = user.username;
}
+ return room;
+ }
- return false;
- } else {
- throw new Meteor.Error('error-invalid-room', 'Invalid room', {
+ if (!userId && settings.get('Accounts_AllowAnonymousRead') === false) {
+ throw new Meteor.Error('error-invalid-user', 'Invalid user', {
method: 'canAccessRoom',
});
}
+
+ return false;
},
});
diff --git a/server/methods/getUsersOfRoom.js b/server/methods/getUsersOfRoom.js
index e999182c455b..02d4524f3e7c 100644
--- a/server/methods/getUsersOfRoom.js
+++ b/server/methods/getUsersOfRoom.js
@@ -1,9 +1,48 @@
import { Meteor } from 'meteor/meteor';
import { Subscriptions } from '../../app/models';
import { hasPermission } from '../../app/authorization';
+import { settings } from '../../app/settings';
+
+function findUsers({ rid, status, skip, limit }) {
+ return Subscriptions.model.rawCollection().aggregate([
+ { $match: { rid } },
+ {
+ $lookup:
+ {
+ from: 'users',
+ localField: 'u._id',
+ foreignField: '_id',
+ as: 'u',
+ },
+ },
+ {
+ $project: {
+ 'u._id': 1,
+ 'u.name': 1,
+ 'u.username': 1,
+ 'u.status': 1,
+ },
+ },
+ ...(status ? [{ $match: { 'u.status': status } }] : []),
+ {
+ $sort: {
+ [settings.get('UI_Use_Real_Name') ? 'u.name' : 'u.username']: 1,
+ },
+ },
+ ...(skip > 0 ? [{ $skip: skip }] : []),
+ ...(limit > 0 ? [{ $limit: limit }] : []),
+ {
+ $project: {
+ _id: { $arrayElemAt: ['$u._id', 0] },
+ name: { $arrayElemAt: ['$u.name', 0] },
+ username: { $arrayElemAt: ['$u.username', 0] },
+ },
+ },
+ ]).toArray();
+}
Meteor.methods({
- async getUsersOfRoom(rid, showAll) {
+ async getUsersOfRoom(rid, showAll, { limit, skip } = {}) {
const userId = Meteor.userId();
if (!userId) {
throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'getUsersOfRoom' });
@@ -18,38 +57,26 @@ Meteor.methods({
throw new Meteor.Error('error-not-allowed', 'Not allowed', { method: 'getUsersOfRoom' });
}
- const subscriptions = Subscriptions.findByRoomIdWhenUsernameExists(rid);
+ const total = Subscriptions.findByRoomIdWhenUsernameExists(rid).count();
+ const users = await findUsers({ rid, status: { $ne: 'offline' }, limit, skip });
+
+ if (showAll && users.length < limit) {
+ const offlineUsers = await findUsers({
+ rid,
+ status: { $eq: 'offline' },
+ limit: limit - users.length,
+ skip,
+ });
+
+ return {
+ total,
+ records: users.concat(offlineUsers),
+ };
+ }
return {
- total: subscriptions.count(),
- records: await Subscriptions.model.rawCollection().aggregate([
- { $match: { rid } },
- {
- $lookup:
- {
- from: 'users',
- localField: 'u._id',
- foreignField: '_id',
- as: 'u',
- },
- },
- {
- $project: {
- 'u._id': 1,
- 'u.name': 1,
- 'u.username': 1,
- 'u.status': 1,
- },
- },
- ...(showAll ? [] : [{ $match: { 'u.status': { $in: ['online', 'away', 'busy'] } } }]),
- {
- $project: {
- _id: { $arrayElemAt: ['$u._id', 0] },
- name: { $arrayElemAt: ['$u.name', 0] },
- username: { $arrayElemAt: ['$u.username', 0] },
- },
- },
- ]).toArray(),
+ total,
+ records: users,
};
},
});
diff --git a/server/methods/resetAvatar.js b/server/methods/resetAvatar.js
index 06dc8160c442..77bea775507a 100644
--- a/server/methods/resetAvatar.js
+++ b/server/methods/resetAvatar.js
@@ -1,12 +1,13 @@
import { Meteor } from 'meteor/meteor';
import { DDPRateLimiter } from 'meteor/ddp-rate-limiter';
import { FileUpload } from '../../app/file-upload';
-import { Users } from '../../app/models';
+import { Users } from '../../app/models/server';
import { settings } from '../../app/settings';
import { Notifications } from '../../app/notifications';
+import { hasPermission } from '../../app/authorization/server';
Meteor.methods({
- resetAvatar() {
+ resetAvatar(userId) {
if (!Meteor.userId()) {
throw new Meteor.Error('error-invalid-user', 'Invalid user', {
method: 'resetAvatar',
@@ -19,7 +20,26 @@ Meteor.methods({
});
}
- const user = Meteor.user();
+ let user;
+
+ if (userId && userId !== Meteor.userId()) {
+ if (!hasPermission(Meteor.userId(), 'edit-other-user-avatar')) {
+ throw new Meteor.Error('error-unauthorized', 'Unauthorized', {
+ method: 'resetAvatar',
+ });
+ }
+
+ user = Users.findOneById(userId, { fields: { _id: 1, username: 1 } });
+ } else {
+ user = Meteor.user();
+ }
+
+ if (user == null) {
+ throw new Meteor.Error('error-invalid-desired-user', 'Invalid desired user', {
+ method: 'resetAvatar',
+ });
+ }
+
FileUpload.getStore('Avatars').deleteByName(user.username);
Users.unsetAvatarOrigin(user._id);
Notifications.notifyLogged('updateAvatar', {
diff --git a/server/methods/setAvatarFromService.js b/server/methods/setAvatarFromService.js
index dc8ce21cd727..c93cd2c2d4a5 100644
--- a/server/methods/setAvatarFromService.js
+++ b/server/methods/setAvatarFromService.js
@@ -3,12 +3,15 @@ import { Match, check } from 'meteor/check';
import { DDPRateLimiter } from 'meteor/ddp-rate-limiter';
import { settings } from '../../app/settings';
import { setUserAvatar } from '../../app/lib';
+import { Users } from '../../app/models/server';
+import { hasPermission } from '../../app/authorization/server';
Meteor.methods({
- setAvatarFromService(dataURI, contentType, service) {
+ setAvatarFromService(dataURI, contentType, service, userId) {
check(dataURI, String);
check(contentType, Match.Optional(String));
check(service, Match.Optional(String));
+ check(userId, Match.Optional(String));
if (!Meteor.userId()) {
throw new Meteor.Error('error-invalid-user', 'Invalid user', {
@@ -22,7 +25,25 @@ Meteor.methods({
});
}
- const user = Meteor.user();
+ let user;
+
+ if (userId && userId !== Meteor.userId()) {
+ if (!hasPermission(Meteor.userId(), 'edit-other-user-avatar')) {
+ throw new Meteor.Error('error-unauthorized', 'Unauthorized', {
+ method: 'setAvatarFromService',
+ });
+ }
+
+ user = Users.findOneById(userId, { fields: { _id: 1, username: 1 } });
+ } else {
+ user = Meteor.user();
+ }
+
+ if (user == null) {
+ throw new Meteor.Error('error-invalid-desired-user', 'Invalid desired user', {
+ method: 'setAvatarFromService',
+ });
+ }
return setUserAvatar(user, dataURI, contentType, service);
},
diff --git a/server/publications/subscription.js b/server/publications/subscription.js
index 16324181370f..15b1bafce932 100644
--- a/server/publications/subscription.js
+++ b/server/publications/subscription.js
@@ -37,6 +37,7 @@ const fields = {
muteGroupMentions: 1,
ignored: 1,
E2EKey: 1,
+ tunread: 1,
};
Meteor.methods({
diff --git a/server/startup/migrations/index.js b/server/startup/migrations/index.js
index fac1f2fd867f..7465d88879ae 100644
--- a/server/startup/migrations/index.js
+++ b/server/startup/migrations/index.js
@@ -137,4 +137,5 @@ import './v136';
import './v137';
import './v138';
import './v139';
+import './v140';
import './xrun';
diff --git a/server/startup/migrations/v140.js b/server/startup/migrations/v140.js
new file mode 100644
index 000000000000..efa35de80778
--- /dev/null
+++ b/server/startup/migrations/v140.js
@@ -0,0 +1,17 @@
+import { Migrations } from '../../../app/migrations/server';
+
+import { Messages, Rooms } from '../../../app/models/server';
+
+Migrations.add({
+ version: 140,
+ up() {
+ Messages.find({ drid: { $exists: 1 } }, { fields: { drid: 1 } }).forEach(({ _id, drid }) => Rooms.findOne({ _id: drid }) || Messages.update({ _id }, {
+ $unset: {
+ drid: 1,
+ dcount: 1,
+ dlm: 1,
+ t: 1,
+ },
+ }));
+ },
+});
diff --git a/server/startup/serverRunning.js b/server/startup/serverRunning.js
index 040d7fa057e1..a7859d005a28 100644
--- a/server/startup/serverRunning.js
+++ b/server/startup/serverRunning.js
@@ -9,13 +9,27 @@ import semver from 'semver';
Meteor.startup(function() {
let oplogState = 'Disabled';
- if (MongoInternals.defaultRemoteCollectionDriver().mongo._oplogHandle && MongoInternals.defaultRemoteCollectionDriver().mongo._oplogHandle.onOplogEntry) {
+
+ const { mongo } = MongoInternals.defaultRemoteCollectionDriver();
+
+ if (mongo._oplogHandle && mongo._oplogHandle.onOplogEntry) {
oplogState = 'Enabled';
if (settings.get('Force_Disable_OpLog_For_Cache') === true) {
oplogState += ' (Disabled for Cache Sync)';
}
}
+ let mongoDbVersion;
+ let mongoDbEngine;
+ try {
+ const { version, storageEngine } = Promise.await(mongo.db.command({ serverStatus: 1 }));
+ mongoDbVersion = version;
+ mongoDbEngine = storageEngine.name;
+ } catch (e) {
+ mongoDbVersion = 'Error getting version';
+ console.error('Error getting MongoDB version');
+ }
+
const desiredNodeVersion = semver.clean(fs.readFileSync(path.join(process.cwd(), '../../.node_version.txt')).toString());
const desiredNodeVersionMajor = String(semver.parse(desiredNodeVersion).major);
@@ -23,6 +37,8 @@ Meteor.startup(function() {
let msg = [
`Rocket.Chat Version: ${ Info.version }`,
` NodeJS Version: ${ process.versions.node } - ${ process.arch }`,
+ ` MongoDB Version: ${ mongoDbVersion }`,
+ ` MongoDB Engine: ${ mongoDbEngine }`,
` Platform: ${ process.platform }`,
` Process Port: ${ process.env.PORT }`,
` Site URL: ${ settings.get('Site_Url') }`,
@@ -39,14 +55,20 @@ Meteor.startup(function() {
msg = msg.join('\n');
- if (semver.satisfies(process.versions.node, desiredNodeVersionMajor)) {
- return SystemLogger.startup_box(msg, 'SERVER RUNNING');
+ if (!semver.satisfies(process.versions.node, desiredNodeVersionMajor)) {
+ msg += ['', '', 'YOUR CURRENT NODEJS VERSION IS NOT SUPPORTED,', `PLEASE UPGRADE / DOWNGRADE TO VERSION ${ desiredNodeVersionMajor }.X.X`].join('\n');
+ SystemLogger.error_box(msg, 'SERVER ERROR');
+
+ return process.exit();
}
- msg += ['', '', 'YOUR CURRENT NODEJS VERSION IS NOT SUPPORTED,', `PLEASE UPGRADE / DOWNGRADE TO VERSION ${ desiredNodeVersionMajor }.X.X`].join('\n');
+ if (!semver.satisfies(mongoDbVersion, '>=3.2.0')) {
+ msg += ['', '', 'YOUR CURRENT MONGODB VERSION IS NOT SUPPORTED,', 'PLEASE UPGRADE TO VERSION 3.2 OR LATER'].join('\n');
+ SystemLogger.error_box(msg, 'SERVER ERROR');
- SystemLogger.error_box(msg, 'SERVER ERROR');
+ return process.exit();
+ }
- return process.exit();
+ return SystemLogger.startup_box(msg, 'SERVER RUNNING');
}, 100);
});
diff --git a/tests/end-to-end/api/09-rooms.js b/tests/end-to-end/api/09-rooms.js
index 3f2d2ab4bce5..f9c31ac030cd 100644
--- a/tests/end-to-end/api/09-rooms.js
+++ b/tests/end-to-end/api/09-rooms.js
@@ -1,7 +1,8 @@
import { getCredentials, api, request, credentials } from '../../data/api-data.js';
import { password } from '../../data/user';
import { closeRoom, createRoom } from '../../data/rooms.helper';
-import { updatePermission } from '../../data/permissions.helper';
+import { updatePermission, updateSetting } from '../../data/permissions.helper';
+import { sendSimpleMessage } from '../../data/chat.helper';
describe('[Rooms]', function() {
this.retries(0);
@@ -549,4 +550,251 @@ describe('[Rooms]', function() {
});
});
});
+
+ describe('/rooms.createDiscussion', () => {
+ let testChannel;
+ const testChannelName = `channel.test.${ Date.now() }`;
+ let messageSent;
+ before((done) => {
+ createRoom({ type: 'c', name: testChannelName })
+ .end((err, res) => {
+ testChannel = res.body.channel;
+ sendSimpleMessage({
+ roomId: testChannel._id,
+ }).end((err, res) => {
+ messageSent = res.body.message;
+ done();
+ });
+ });
+ });
+ it('should throw an error when the user tries to create a discussion and the feature is disabled', (done) => {
+ updateSetting('Discussion_enabled', false).then(() => {
+ request.post(api('rooms.createDiscussion'))
+ .set(credentials)
+ .send({
+ prid: testChannel._id,
+ t_name: 'valid name',
+ })
+ .expect(400)
+ .expect((res) => {
+ expect(res.body).to.have.property('success', false);
+ expect(res.body).to.have.property('errorType', 'error-action-not-allowed');
+ })
+ .end(() => updateSetting('Discussion_enabled', true).then(done));
+ });
+ });
+ it('should throw an error when the user tries to create a discussion and does not have at least one of the required permissions', (done) => {
+ updatePermission('start-discussion', []).then(() => {
+ updatePermission('start-discussion-other-user', []).then(() => {
+ request.post(api('rooms.createDiscussion'))
+ .set(credentials)
+ .send({
+ prid: testChannel._id,
+ t_name: 'valid name',
+ })
+ .expect(400)
+ .expect((res) => {
+ expect(res.body).to.have.property('success', false);
+ expect(res.body).to.have.property('errorType', 'error-action-not-allowed');
+ })
+ .end(() => {
+ updatePermission('start-discussion', ['admin', 'user', 'guest'])
+ .then(() => updatePermission('start-discussion-other-user', ['admin', 'user', 'guest']))
+ .then(done);
+ });
+ });
+ });
+ });
+ it('should throw an error when the user tries to create a discussion without the required parameter "prid"', (done) => {
+ request.post(api('rooms.createDiscussion'))
+ .set(credentials)
+ .send({})
+ .expect(400)
+ .expect((res) => {
+ expect(res.body).to.have.property('success', false);
+ expect(res.body).to.have.property('error', 'Body parameter \"prid\" is required.');
+ })
+ .end(done);
+ });
+ it('should throw an error when the user tries to create a discussion without the required parameter "t_name"', (done) => {
+ request.post(api('rooms.createDiscussion'))
+ .set(credentials)
+ .send({
+ prid: testChannel._id,
+ })
+ .expect(400)
+ .expect((res) => {
+ expect(res.body).to.have.property('success', false);
+ expect(res.body).to.have.property('error', 'Body parameter \"t_name\" is required.');
+ })
+ .end(done);
+ });
+ it('should throw an error when the user tries to create a discussion with the required parameter invalid "users"(different from an array)', (done) => {
+ request.post(api('rooms.createDiscussion'))
+ .set(credentials)
+ .send({
+ prid: testChannel._id,
+ t_name: 'valid name',
+ users: 'invalid-type-of-users',
+ })
+ .expect(400)
+ .expect((res) => {
+ expect(res.body).to.have.property('success', false);
+ expect(res.body).to.have.property('error', 'Body parameter \"users\" must be an array.');
+ })
+ .end(done);
+ });
+ it('should throw an error when the user tries to create a discussion with the channel\'s id invalid', (done) => {
+ request.post(api('rooms.createDiscussion'))
+ .set(credentials)
+ .send({
+ prid: 'invalid-id',
+ t_name: 'valid name',
+ })
+ .expect(400)
+ .expect((res) => {
+ expect(res.body).to.have.property('success', false);
+ expect(res.body).to.have.property('errorType', 'error-invalid-room');
+ })
+ .end(done);
+ });
+ it('should throw an error when the user tries to create a discussion with the message\'s id invalid', (done) => {
+ request.post(api('rooms.createDiscussion'))
+ .set(credentials)
+ .send({
+ prid: testChannel._id,
+ t_name: 'valid name',
+ pmid: 'invalid-message',
+ })
+ .expect(400)
+ .expect((res) => {
+ expect(res.body).to.have.property('success', false);
+ expect(res.body).to.have.property('errorType', 'error-invalid-message');
+ })
+ .end(done);
+ });
+ it('should create a discussion successfully when send only the required parameters', (done) => {
+ request.post(api('rooms.createDiscussion'))
+ .set(credentials)
+ .send({
+ prid: testChannel._id,
+ t_name: `discussion-create-from-tests-${ testChannel.name }`,
+ })
+ .expect(200)
+ .expect((res) => {
+ expect(res.body).to.have.property('success', true);
+ expect(res.body).to.have.property('discussion').and.to.be.an('object');
+ })
+ .end(done);
+ });
+ it('should create a discussion successfully when send the required parameters plus the optional parameter "reply"', (done) => {
+ request.post(api('rooms.createDiscussion'))
+ .set(credentials)
+ .send({
+ prid: testChannel._id,
+ t_name: `discussion-create-from-tests-${ testChannel.name }`,
+ reply: 'reply from discussion tests',
+ })
+ .expect(200)
+ .expect((res) => {
+ expect(res.body).to.have.property('success', true);
+ expect(res.body).to.have.property('discussion').and.to.be.an('object');
+ })
+ .end(done);
+ });
+ it('should create a discussion successfully when send the required parameters plus the optional parameter "users"', (done) => {
+ request.post(api('rooms.createDiscussion'))
+ .set(credentials)
+ .send({
+ prid: testChannel._id,
+ t_name: `discussion-create-from-tests-${ testChannel.name }`,
+ reply: 'reply from discussion tests',
+ users: ['rocket.cat'],
+ })
+ .expect(200)
+ .expect((res) => {
+ expect(res.body).to.have.property('success', true);
+ expect(res.body).to.have.property('discussion').and.to.be.an('object');
+ })
+ .end(done);
+ });
+ it('should create a discussion successfully when send the required parameters plus the optional parameter "pmid"', (done) => {
+ request.post(api('rooms.createDiscussion'))
+ .set(credentials)
+ .send({
+ prid: testChannel._id,
+ t_name: `discussion-create-from-tests-${ testChannel.name }`,
+ reply: 'reply from discussion tests',
+ users: ['rocket.cat'],
+ pmid: messageSent._id,
+ })
+ .expect(200)
+ .expect((res) => {
+ expect(res.body).to.have.property('success', true);
+ expect(res.body).to.have.property('discussion').and.to.be.an('object');
+ })
+ .end(done);
+ });
+ });
+
+ describe('/rooms.getDiscussions', () => {
+ let testChannel;
+ const testChannelName = `channel.test.getDiscussions${ Date.now() }`;
+ let discussion;
+ before((done) => {
+ createRoom({ type: 'c', name: testChannelName })
+ .end((err, res) => {
+ testChannel = res.body.channel;
+ request.post(api('rooms.createDiscussion'))
+ .set(credentials)
+ .send({
+ prid: testChannel._id,
+ t_name: `discussion-create-from-tests-${ testChannel.name }`,
+ })
+ .end((err, res) => {
+ discussion = res.body.discussion;
+ done();
+ });
+ });
+ });
+ after((done) => closeRoom({ type: 'p', roomId: discussion._id }).then(done));
+ it('should throw an error when the user tries to gets a list of discussion without a required parameter "roomId"', (done) => {
+ request.get(api('rooms.getDiscussions'))
+ .set(credentials)
+ .query({})
+ .expect(400)
+ .expect((res) => {
+ expect(res.body).to.have.property('success', false);
+ expect(res.body).to.have.property('error', 'The parameter \"roomId\" or \"roomName\" is required [error-roomid-param-not-provided]');
+ })
+ .end(done);
+ });
+ it('should throw an error when the user tries to gets a list of discussion and he cannot access the room', (done) => {
+ updatePermission('view-c-room', []).then(() => {
+ request.get(api('rooms.getDiscussions'))
+ .set(credentials)
+ .query({})
+ .expect(400)
+ .expect((res) => {
+ expect(res.body).to.have.property('success', false);
+ expect(res.body).to.have.property('error', 'Not Allowed');
+ })
+ .end(() => updatePermission('view-c-room', ['admin', 'user', 'bot', 'anonymous']).then(done));
+ });
+ });
+ it('should return a list of discussions with ONE discussion', (done) => {
+ request.get(api('rooms.getDiscussions'))
+ .set(credentials)
+ .query({
+ roomId: testChannel._id,
+ })
+ .expect(200)
+ .expect((res) => {
+ expect(res.body).to.have.property('success', true);
+ expect(res.body).to.have.property('discussions').and.to.be.an('array');
+ expect(res.body.discussions).to.have.lengthOf(1);
+ })
+ .end(done);
+ });
+ });
});
diff --git a/tests/end-to-end/api/12-emoji-custom.js b/tests/end-to-end/api/12-emoji-custom.js
index 5cdab3b809c3..16ff9c7e38e2 100644
--- a/tests/end-to-end/api/12-emoji-custom.js
+++ b/tests/end-to-end/api/12-emoji-custom.js
@@ -7,7 +7,8 @@ describe('[EmojiCustom]', function() {
this.retries(0);
before((done) => getCredentials(done));
-
+ // DEPRECATED
+ // Will be removed after v1.12.0
describe('[/emoji-custom]', () => {
it('should return emojis', (done) => {
request.get(api('emoji-custom'))
@@ -185,6 +186,66 @@ describe('[EmojiCustom]', function() {
});
});
+ describe('[/emoji-custom.list]', () => {
+ it('should return emojis', (done) => {
+ request.get(api('emoji-custom.list'))
+ .set(credentials)
+ .expect(200)
+ .expect((res) => {
+ expect(res.body).to.have.property('emojis').and.to.be.a('object');
+ expect(res.body.emojis).to.have.property('update').and.to.be.a('array').and.to.not.have.lengthOf(0);
+ expect(res.body.emojis).to.have.property('remove').and.to.be.a('array').and.to.have.lengthOf(0);
+ })
+ .end(done);
+ });
+ it('should return emojis when use "query" query parameter', (done) => {
+ request.get(api(`emoji-custom.list?query={"_updatedAt": {"$gt": { "$date": "${ new Date().toISOString() }" } } }`))
+ .set(credentials)
+ .expect(200)
+ .expect((res) => {
+ expect(res.body).to.have.property('success', true);
+ expect(res.body).to.have.property('emojis').and.to.be.a('object');
+ expect(res.body.emojis).to.have.property('update').and.to.be.a('array').and.to.have.lengthOf(0);
+ expect(res.body.emojis).to.have.property('remove').and.to.be.a('array').and.to.have.lengthOf(0);
+ })
+ .end(done);
+ });
+ it('should return emojis when use "updateSince" query parameter', (done) => {
+ request.get(api(`emoji-custom.list?updatedSince=${ new Date().toISOString() }`))
+ .set(credentials)
+ .expect(200)
+ .expect((res) => {
+ expect(res.body).to.have.property('success', true);
+ expect(res.body).to.have.property('emojis').and.to.be.a('object');
+ expect(res.body.emojis).to.have.property('update').and.to.be.a('array').and.to.have.lengthOf(0);
+ expect(res.body.emojis).to.have.property('remove').and.to.be.a('array').and.to.have.lengthOf(0);
+ })
+ .end(done);
+ });
+ it('should return emojis when use both, "updateSince" and "query" query parameter', (done) => {
+ request.get(api(`emoji-custom.list?query={"_updatedAt": {"$gt": { "$date": "${ new Date().toISOString() }" } }}&updatedSince=${ new Date().toISOString() }`))
+ .set(credentials)
+ .expect(200)
+ .expect((res) => {
+ expect(res.body).to.have.property('success', true);
+ expect(res.body).to.have.property('emojis').and.to.be.a('object');
+ expect(res.body.emojis).to.have.property('update').and.to.be.a('array').and.to.have.lengthOf(0);
+ expect(res.body.emojis).to.have.property('remove').and.to.be.a('array').and.to.have.lengthOf(0);
+ })
+ .end(done);
+ });
+ it('should return an error when the "updateSince" query parameter is a invalid date', (done) => {
+ request.get(api('emoji-custom.list?updatedSince=invalid-date'))
+ .set(credentials)
+ .expect(400)
+ .expect((res) => {
+ expect(res.body).to.have.property('success', false);
+ expect(res.body.errorType).to.be.equal('error-roomId-param-invalid');
+ })
+ .end(done);
+ });
+ });
+
describe('[/emoji-custom.delete]', () => {
it('should throw an error when trying delete custom emoji without the required param "emojid"', (done) => {
request.post(api('emoji-custom.delete'))
diff --git a/tests/end-to-end/ui/06-messaging.js b/tests/end-to-end/ui/06-messaging.js
index 941755bccd8a..786c7d195920 100644
--- a/tests/end-to-end/ui/06-messaging.js
+++ b/tests/end-to-end/ui/06-messaging.js
@@ -167,8 +167,8 @@ function messageActionsTest() {
});
it('it should check if the message was replied', () => {
- mainContent.lastMessageTextAttachment.waitForVisible(5000);
- mainContent.lastMessageTextAttachment.getText().should.equal(mainContent.beforeLastMessage.getText());
+ mainContent.lastMessageQuote.waitForVisible(5000);
+ mainContent.lastMessageQuote.getText().should.equal(mainContent.beforeLastMessage.getText());
});
});
diff --git a/tests/end-to-end/ui/08-resolutions.js b/tests/end-to-end/ui/08-resolutions.js
index 41ecd5d73878..5ed288e74660 100644
--- a/tests/end-to-end/ui/08-resolutions.js
+++ b/tests/end-to-end/ui/08-resolutions.js
@@ -35,6 +35,15 @@ describe('[Resolution]', () => {
mainContent.mainContent.getLocation().should.not.deep.equal({ x:0 });
});
+ it('it should not close sidebar on pressing the sidebar item menu', () => {
+ sideNav.firstSidebarItem.moveToObject();
+ sideNav.firstSidebarItemMenu.waitForVisible(10000);
+ sideNav.firstSidebarItemMenu.click();
+ browser.pause(100);
+ mainContent.mainContent.getLocation().should.not.deep.equal({ x:0 });
+ sideNav.popoverOverlay.click();
+ });
+
it('it should open general channel', () => {
sideNav.openChannel('general');
});
diff --git a/tests/pageobjects/main-content.page.js b/tests/pageobjects/main-content.page.js
index 1b0d189bbd0c..16090b63235a 100644
--- a/tests/pageobjects/main-content.page.js
+++ b/tests/pageobjects/main-content.page.js
@@ -32,6 +32,7 @@ class MainContent extends Page {
get lastMessageUserTag() { return browser.element('.message:last-child .role-tag'); }
get lastMessageImg() { return browser.element('.message:last-child .attachment-image img'); }
get lastMessageTextAttachment() { return browser.element('.message:last-child .attachment-text'); }
+ get lastMessageQuote() { return browser.element('.message:last-child .thread-quote'); }
get messageOptionsBtn() { return browser.element('.message:last-child .message-actions__menu'); }
get messageActionMenu() { return browser.element('.rc-popover .rc-popover__content'); }
get messageReply() { return browser.element('[data-id="reply-message"][data-type="message-action"]'); }
diff --git a/tests/pageobjects/side-nav.page.js b/tests/pageobjects/side-nav.page.js
index 315175924b46..baed6e3b07a5 100644
--- a/tests/pageobjects/side-nav.page.js
+++ b/tests/pageobjects/side-nav.page.js
@@ -52,6 +52,10 @@ class SideNav extends Page {
get sidebarWrap() { return browser.element('.sidebar-wrap'); }
+ get firstSidebarItem() { return browser.element('.sidebar-item'); }
+ get firstSidebarItemMenu() { return browser.element('.sidebar-item__menu'); }
+ get popoverOverlay() { return browser.element('.rc-popover.rc-popover--sidebar-item'); }
+
// Opens a channel via rooms list
openChannel(channelName) {
browser.waitForVisible(`.sidebar-item__ellipsis=${ channelName }`, 10000);