diff --git a/app/api/server/lib/emoji-custom.js b/app/api/server/lib/emoji-custom.js new file mode 100644 index 00000000000..1d7dde27066 --- /dev/null +++ b/app/api/server/lib/emoji-custom.js @@ -0,0 +1,20 @@ +import { EmojiCustom } from '../../../models/server/raw'; + +export async function findEmojisCustom({ query = {}, pagination: { offset, count, sort } }) { + const cursor = EmojiCustom.find(query, { + sort: sort || { name: 1 }, + skip: offset, + limit: count, + }); + + const total = await cursor.count(); + + const emojis = await cursor.toArray(); + + return { + emojis, + count: emojis.length, + offset, + total, + }; +} diff --git a/app/api/server/v1/emoji-custom.js b/app/api/server/v1/emoji-custom.js index 15b4035dd2b..249331a3e83 100644 --- a/app/api/server/v1/emoji-custom.js +++ b/app/api/server/v1/emoji-custom.js @@ -3,6 +3,7 @@ import Busboy from 'busboy'; import { EmojiCustom } from '../../../models'; import { API } from '../api'; +import { findEmojisCustom } from '../lib/emoji-custom'; // DEPRECATED // Will be removed after v3.0.0 @@ -51,6 +52,22 @@ API.v1.addRoute('emoji-custom.list', { authRequired: true }, { }, }); +API.v1.addRoute('emoji-custom.all', { authRequired: true }, { + get() { + const { offset, count } = this.getPaginationItems(); + const { sort, query } = this.parseJsonQuery(); + + return API.v1.success(Promise.await(findEmojisCustom({ + query, + pagination: { + offset, + count, + sort, + }, + }))); + }, +}); + API.v1.addRoute('emoji-custom.create', { authRequired: true }, { post() { Meteor.runAsUser(this.userId, () => { diff --git a/app/emoji-custom/client/admin/adminEmoji.html b/app/emoji-custom/client/admin/adminEmoji.html index b85042ea49d..c2e59b84b84 100644 --- a/app/emoji-custom/client/admin/adminEmoji.html +++ b/app/emoji-custom/client/admin/adminEmoji.html @@ -9,10 +9,10 @@
- {{#if isReady}} - {{> icon block="rc-input__icon-svg" icon="magnifier" }} - {{else}} + {{#if isLoading}} {{> loading }} + {{else}} + {{> icon block="rc-input__icon-svg" icon="magnifier" }} {{/if}}
{{_ "No_results_found_for"}} {{.}} - {{/with}} {{/each}} {{#unless isReady}} + {{/with}} {{/each}} {{#if isLoading}} {{> loading}} - {{/unless}} + {{/if}} {{/table}} {{/unless}} diff --git a/app/emoji-custom/client/admin/adminEmoji.js b/app/emoji-custom/client/admin/adminEmoji.js index 1e6e04e053e..6bbf479fbe2 100644 --- a/app/emoji-custom/client/admin/adminEmoji.js +++ b/app/emoji-custom/client/admin/adminEmoji.js @@ -2,39 +2,24 @@ import { ReactiveVar } from 'meteor/reactive-var'; import { Tracker } from 'meteor/tracker'; import { FlowRouter } from 'meteor/kadira:flow-router'; import { Template } from 'meteor/templating'; -import s from 'underscore.string'; +import _ from 'underscore'; -import { EmojiCustom } from '../../../models'; import { RocketChatTabBar, SideNav, TabBar } from '../../../ui-utils'; +import { APIClient } from '../../../utils/client'; + +const LIST_SIZE = 50; +const DEBOUNCE_TIME_TO_SEARCH_IN_MS = 500; Template.adminEmoji.helpers({ searchText() { const instance = Template.instance(); return instance.filter && instance.filter.get(); }, - isReady() { - if (Template.instance().ready != null) { - return Template.instance().ready.get(); - } - return undefined; - }, customemoji() { - return Template.instance().customemoji(); + return Template.instance().emojis.get(); }, isLoading() { - if (Template.instance().ready != null) { - if (!Template.instance().ready.get()) { - return 'btn-loading'; - } - } - }, - hasMore() { - if (Template.instance().limit != null) { - if (typeof Template.instance().customemoji === 'function') { - return Template.instance().limit.get() === Template.instance().customemoji().length; - } - } - return false; + return Template.instance().isLoading.get(); }, flexData() { return { @@ -48,26 +33,32 @@ Template.adminEmoji.helpers({ if ((currentTarget.offsetHeight + currentTarget.scrollTop) < (currentTarget.scrollHeight - 100)) { return; } - if (instance.limit.get() > instance.customemoji().length) { - return false; + const emojis = instance.emojis.get(); + if (instance.total.get() > emojis.length) { + instance.offset.set(instance.offset.get() + LIST_SIZE); } - instance.limit.set(instance.limit.get() + 50); }; }, onTableItemClick() { const instance = Template.instance(); return function({ _id }) { - instance.tabBarData.set(EmojiCustom.findOne({ _id })); + instance.tabBarData.set({ + emoji: instance.emojis.get().find((emoji) => emoji._id === _id), + onSuccess: instance.onSuccessCallback, + }); instance.tabBar.open('admin-emoji-info'); }; }, }); -Template.adminEmoji.onCreated(function() { +Template.adminEmoji.onCreated(async function() { const instance = this; - this.limit = new ReactiveVar(50); + this.emojis = new ReactiveVar([]); + this.offset = new ReactiveVar(0); + this.total = new ReactiveVar(0); this.filter = new ReactiveVar(''); - this.ready = new ReactiveVar(false); + this.query = new ReactiveVar({}); + this.isLoading = new ReactiveVar(false); this.tabBar = new RocketChatTabBar(); this.tabBar.showGroup(FlowRouter.current().route.name); @@ -90,27 +81,40 @@ Template.adminEmoji.onCreated(function() { template: 'adminEmojiInfo', order: 2, }); - - this.autorun(function() { - const limit = instance.limit != null ? instance.limit.get() : 0; - const subscription = instance.subscribe('fullEmojiData', '', limit); - instance.ready.set(subscription.ready()); + this.onSuccessCallback = () => { + this.offset.set(0); + return this.loadEmojis(this.query.get(), this.offset.get()); + }; + this.tabBarData.set({ + onSuccess: instance.onSuccessCallback, }); - this.customemoji = function() { - const filter = instance.filter != null ? s.trim(instance.filter.get()) : ''; + this.loadEmojis = _.debounce(async (query, offset) => { + this.isLoading.set(true); + const { emojis, total } = await APIClient.v1.get(`emoji-custom.all?count=${ LIST_SIZE }&offset=${ offset }&query=${ JSON.stringify(query) }`); + this.total.set(total); + if (offset === 0) { + this.emojis.set(emojis); + } else { + this.emojis.set(this.emojis.get().concat(emojis)); + } + this.isLoading.set(false); + }, DEBOUNCE_TIME_TO_SEARCH_IN_MS); - let query = {}; + this.autorun(() => { + this.filter.get(); + this.offset.set(0); + }); + this.autorun(() => { + const filter = this.filter.get() && this.filter.get().trim(); + const offset = this.offset.get(); if (filter) { - const filterReg = new RegExp(s.escapeRegExp(filter), 'i'); - query = { $or: [{ name: filterReg }, { aliases: filterReg }] }; + const regex = { $regex: filter, $options: 'i' }; + return this.loadEmojis({ $or: [{ name: regex }, { aliases: regex }] }, offset); } - - const limit = instance.limit != null ? instance.limit.get() : 0; - - return EmojiCustom.find(query, { limit, sort: { name: 1 } }).fetch(); - }; + return this.loadEmojis({}, offset); + }); }); Template.adminEmoji.onRendered(() => diff --git a/app/emoji-custom/client/admin/emojiEdit.js b/app/emoji-custom/client/admin/emojiEdit.js index 0247f8e12a7..30134afbd9e 100644 --- a/app/emoji-custom/client/admin/emojiEdit.js +++ b/app/emoji-custom/client/admin/emojiEdit.js @@ -58,6 +58,7 @@ Template.emojiEdit.onCreated(function() { } this.tabBar = Template.currentData().tabBar; + this.onSuccess = Template.currentData().onSuccess; this.cancel = (form, name) => { form.reset(); @@ -143,6 +144,7 @@ Template.emojiEdit.onCreated(function() { } else { toastr.success(t('Custom_Emoji_Added_Successfully')); } + this.onSuccess(); this.cancel(form, emojiData.name); } diff --git a/app/emoji-custom/client/admin/emojiInfo.js b/app/emoji-custom/client/admin/emojiInfo.js index cb84c0053b6..141dc2116c9 100644 --- a/app/emoji-custom/client/admin/emojiInfo.js +++ b/app/emoji-custom/client/admin/emojiInfo.js @@ -29,6 +29,7 @@ Template.emojiInfo.helpers({ return { tabBar: this.tabBar, emoji: instance.emoji.get(), + onSuccess: instance.onSuccess, back(name) { instance.editingEmoji.set(); @@ -76,6 +77,7 @@ Template.emojiInfo.events({ timer: 2000, showConfirmButton: false, }); + instance.onSuccess(); instance.tabBar.close(); }); @@ -86,13 +88,13 @@ Template.emojiInfo.events({ 'click .edit-emoji'(e, instance) { e.stopPropagation(); e.preventDefault(); - instance.editingEmoji.set(instance.emoji.get()._id); }, }); Template.emojiInfo.onCreated(function() { this.emoji = new ReactiveVar(); + this.onSuccess = Template.currentData().onSuccess; this.editingEmoji = new ReactiveVar(); @@ -108,7 +110,7 @@ Template.emojiInfo.onCreated(function() { }); this.autorun(() => { - const data = Template.currentData(); + const data = Template.currentData().emoji; const emoji = this.emoji.get(); if (emoji != null && emoji.name != null) { this.loadedName.set(emoji.name); @@ -118,7 +120,7 @@ Template.emojiInfo.onCreated(function() { }); this.autorun(() => { - const data = Template.currentData(); + const data = Template.currentData().emoji; this.emoji.set(data); }); }); diff --git a/app/emoji-custom/server/publications/fullEmojiData.js b/app/emoji-custom/server/publications/fullEmojiData.js index b2771a914ae..f0e0d91c0ad 100644 --- a/app/emoji-custom/server/publications/fullEmojiData.js +++ b/app/emoji-custom/server/publications/fullEmojiData.js @@ -4,6 +4,7 @@ import s from 'underscore.string'; import { EmojiCustom } from '../../../models'; Meteor.publish('fullEmojiData', function(filter, limit) { + console.warn('The publication "fullEmojiData" is deprecated and will be removed after version v3.0.0'); if (!this.userId) { return this.ready(); } diff --git a/app/models/server/raw/EmojiCustom.js b/app/models/server/raw/EmojiCustom.js new file mode 100644 index 00000000000..80b81d41958 --- /dev/null +++ b/app/models/server/raw/EmojiCustom.js @@ -0,0 +1,5 @@ +import { BaseRaw } from './BaseRaw'; + +export class EmojiCustomRaw extends BaseRaw { + +} diff --git a/app/models/server/raw/index.js b/app/models/server/raw/index.js index 575d5a09e05..0c925d8632e 100644 --- a/app/models/server/raw/index.js +++ b/app/models/server/raw/index.js @@ -28,6 +28,8 @@ import LivechatExternalMessagesModel from '../models/LivechatExternalMessages'; import { LivechatExternalMessageRaw } from './LivechatExternalMessages'; import LivechatVisitorsModel from '../models/LivechatVisitors'; import { LivechatVisitorsRaw } from './LivechatVisitors'; +import EmojiCustomModel from '../models/EmojiCustom'; +import { EmojiCustomRaw } from './EmojiCustom'; import WebdavAccountsModel from '../models/WebdavAccounts'; import { WebdavAccountsRaw } from './WebdavAccounts'; import OAuthAppsModel from '../models/OAuthApps'; @@ -54,6 +56,7 @@ export const LivechatRooms = new LivechatRoomsRaw(LivechatRoomsModel.model.rawCo export const Messages = new MessagesRaw(MessagesModel.model.rawCollection()); export const LivechatExternalMessage = new LivechatExternalMessageRaw(LivechatExternalMessagesModel.model.rawCollection()); export const LivechatVisitors = new LivechatVisitorsRaw(LivechatVisitorsModel.model.rawCollection()); +export const EmojiCustom = new EmojiCustomRaw(EmojiCustomModel.model.rawCollection()); export const WebdavAccounts = new WebdavAccountsRaw(WebdavAccountsModel.model.rawCollection()); export const OAuthApps = new OAuthAppsRaw(OAuthAppsModel.model.rawCollection()); export const CustomSounds = new CustomSoundsRaw(CustomSoundsModel.model.rawCollection()); diff --git a/app/reactions/client/methods/setReaction.js b/app/reactions/client/methods/setReaction.js index 285dd9ce18c..db58a8b4ea6 100644 --- a/app/reactions/client/methods/setReaction.js +++ b/app/reactions/client/methods/setReaction.js @@ -1,7 +1,7 @@ import { Meteor } from 'meteor/meteor'; import _ from 'underscore'; -import { Messages, Rooms, Subscriptions, EmojiCustom } from '../../../models'; +import { Messages, Rooms, Subscriptions } from '../../../models'; import { callbacks } from '../../../callbacks'; import { emoji } from '../../../emoji'; @@ -34,7 +34,7 @@ Meteor.methods({ return false; } - if (!emoji.list[reaction] && EmojiCustom.findByNameOrAlias(reaction).count() === 0) { + if (!emoji.list[reaction]) { return false; } diff --git a/tests/end-to-end/api/12-emoji-custom.js b/tests/end-to-end/api/12-emoji-custom.js index c91141a50d4..813ff2f979d 100644 --- a/tests/end-to-end/api/12-emoji-custom.js +++ b/tests/end-to-end/api/12-emoji-custom.js @@ -250,6 +250,22 @@ describe('[EmojiCustom]', function() { }); }); + describe('[/emoji-custom.all]', () => { + it('should return emojis', (done) => { + request.get(api('emoji-custom.all')) + .set(credentials) + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('emojis').and.to.be.an('array'); + expect(res.body).to.have.property('total'); + expect(res.body).to.have.property('offset'); + expect(res.body).to.have.property('count'); + }) + .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'))