diff --git a/.meteor/packages b/.meteor/packages index 951eadb8980c3..08df4bc7ceba9 100644 --- a/.meteor/packages +++ b/.meteor/packages @@ -185,3 +185,6 @@ todda00:friendly-slugs yasaricli:slugify yasinuslu:blaze-meta rocketchat:version-check + +rocketchat:search +chatpal:search diff --git a/.meteor/versions b/.meteor/versions index 3fa982c52f2d7..888581723dc45 100644 --- a/.meteor/versions +++ b/.meteor/versions @@ -21,6 +21,7 @@ caching-compiler@1.1.11 caching-html-compiler@1.1.2 callback-hook@1.1.0 cfs:http-methods@0.0.32 +chatpal:search@0.0.1 check@1.3.1 coffeescript@1.0.17 dandv:caret-position@2.1.1 @@ -200,6 +201,7 @@ rocketchat:postcss@1.0.0 rocketchat:push-notifications@0.0.1 rocketchat:reactions@0.0.1 rocketchat:sandstorm@0.0.1 +rocketchat:search@0.0.1 rocketchat:slackbridge@0.0.1 rocketchat:slashcommands-archive@0.0.1 rocketchat:slashcommands-asciiarts@0.0.1 diff --git a/packages/chatpal-search/client/route.js b/packages/chatpal-search/client/route.js new file mode 100644 index 0000000000000..80d0671372859 --- /dev/null +++ b/packages/chatpal-search/client/route.js @@ -0,0 +1,9 @@ +FlowRouter.route('/admin/chatpal', { + name: 'chatpal-admin', + action() { + return BlazeLayout.render('main', { + center: 'ChatpalAdmin', + pageTitle: t('Chatpal_AdminPage') + }); + } +}); diff --git a/packages/chatpal-search/client/style.css b/packages/chatpal-search/client/style.css new file mode 100644 index 0000000000000..6678d30934e1f --- /dev/null +++ b/packages/chatpal-search/client/style.css @@ -0,0 +1,310 @@ +.chatpal-admin-link { + color:red !important; + text-decoration: underline !important; +} + +.chatpalProvider .search-form { + padding: 0 24px; +} + +.chatpal_search-loader { + position: relative; + margin-top: 50px; +} + +.chatpal-search-container { + display: flex; + flex-direction: column; + flex: 1; +} + +.chatpal-search-result { + position: relative; + overflow: auto; + overflow-x: hidden; + padding: 0 24px 24px; + background-color: #f1f1f1; + flex: 1; +} + +.chatpal-search-typefilter { + display: flex; +} + +.chatpal-search-typefilter li { + cursor: pointer; + display: flex; + flex: 0 0 50%; + border-bottom: 4px solid white; + height: 35px; + justify-content: center; + padding-top: 10px; +} + +.chatpal-search-typefilter li.selected { + border-bottom: 4px solid #125074; +} + +.chatpal-admin-header { + font-size: 18px; + margin-bottom: 20px; +} + +.chatpal-search-result-header { + margin: 20px 0; + font-weight: bold; +} + +.chatpal-search-result-single { + background-color: white; + padding: 10px; + border: 1px solid #e6e6e6; + margin-bottom: 10px; + min-height: 92px; + position: relative; +} + + +.chatpal-search-result-user { + background-color: white; + padding: 10px; + border: 1px solid #e6e6e6; + margin-bottom: 10px; + min-height: 40px; + position: relative; +} + +.chatpal-search-result-user .chatpal-avatar { + width: 36px; + height: 36px; + position: absolute; +} + +.chatpal-search-result-user .chatpal-avatar .chatpal-avatar-image { + background-repeat: no-repeat; + background-size: contain; + width: 100%; + height: 100%; +} + +.chatpal-search-result-user h2 { + padding-left: 45px; + margin-bottom: 5px; +} + +.chatpal-search-result-user .direct-message { + padding-left: 45px; + font-size: 14px; + font-weight: bold; +} + +.chatpal-channel { + flex: 1 1 auto; +} + +.chatpal-search-result-user .chatpal-channel { + font-weight: bold; +} + +.chatpal-show-more-messages { + font-size: 14px; + font-weight: bold; + color: #125074; + text-align: center; + cursor: pointer; + margin-bottom: 20px; +} + +.chatpal-show-more-messages:hover { + text-decoration: underline; +} + +.chatpal-search-result-user .direct-message a { + color: #125074; +} + +.chatpal-search-result-user .direct-message a:hover { + text-decoration: underline; +} + +.chatpal-search-result-single h2 { + margin-bottom: 20px; + display: flex; + align-items: center; +} + +.chatpal-search-result-single .chatpal-avatar { + width: 36px; + height: 36px; + position: absolute; +} + +.chatpal-search-result-single .chatpal-avatar .chatpal-avatar-image { + background-repeat: no-repeat; + background-size: contain; + width: 100%; + height: 100%; +} + +.chatpal-search-result-single .chatpal-name { + padding: 0 0 0 46px; + font-size: 14px; + color: #444; + font-weight: bold; +} + +.chatpal-search-result-single .chatpal-date { + font-size: 12px; + color: #a0a0a0; + font-weight: normal; +} + +.chatpal-search-result-single .chatpal-time { + font-size: 12px; + color: #a0a0a0; + margin-left: 3px; +} + +.chatpal-search-result-single .chatpal-message { + padding: 0 0 0 46px; + line-height: 20px; + margin-top: 5px; + overflow-x: hidden; +} + +.chatpal-search-result-single .chatpal-message em { + font-style: normal; + background-color: #FAF9C8; +} + +.chatpal-search-result-single .chatpal-link { + display: none; + flex: 20; + color: #125074; +} + +.chatpal-search-result-single .chatpal-link:hover { + text-decoration: underline; +} + +.chatpal-search-result-single:hover .chatpal-link { + display: inline-block; +} + +.chatpal-paging { + text-align: center; + margin: 30px 0 50px 0; +} + +.chatpal-paging-text { + display: inline-block; + position: relative; + top: -2px; +} + +.chatpal-paging .chatpal-paging-button { + color: #125074; + cursor: pointer; + font-size: 20px; + display: inline-block; +} + +.chatpal-paging .chatpal-paging-prev { + padding-right: 10px; +} + +.chatpal-paging .chatpal-paging-next { + padding-left: 10px; +} + +.chatpal-search-info { + margin-top: 40px; + padding: 10px; +} + +.chatpal-search-welcome { + text-align: center; + padding-top: 40px; +} + +.chatpal-search-result-room h2 { + padding-left: 0 !important; +} + +.chatpal-search-result-list em { + font-style: normal; + background-color: #FAF9C8; +} + +.chatpal-search-result-room .chatpal-link { + display: none; + margin-left: 3px; + color: #125074; +} + +.chatpal-search-result-room:hover .chatpal-link { + display: inline-block; +} + +.chatpal-search-result-room .chatpal-link:hover{ + text-decoration: underline; +} + +.chatpal-search-pills div { + background-color: #f1f1f1; + padding: 2px 5px; + display: inline-block; + margin-right: 5px; + border-radius: 3px; + margin-top: 5px; +} + +.apikey h2 { + margin-top: 20px; +} + +.apikey .key { + background-color: #f1f1f1; + position: relative; + margin: 20px 0; + font-size: 20px; + text-align: center; + padding: 10px; +} + +.apikey a { + text-decoration: underline; +} + +.chatpal-suggestion { + padding: 10px; + display: flex; + justify-items: center; +} + +.chatpal-suggestion-text { + flex: 1 1 auto; +} + +.chatpal-suggestion-hint { + display: none; +} + +.rocket-search-suggestion-item.active .chatpal-suggestion-hint { + display: block; +} + +.chatpalProvider .rc-input__icon { + width: 26px; + left: 10px; + top: 3px; +} + +.chatpalProvider .rc-input__icon-svg { + font-size: 26px !important; +} + +.chatpalProvider .rocket-search-suggestion-item.active, .chatpalProvider .rocket-search-suggestion-item:hover { + background-color: #125074; + color: white; +} diff --git a/packages/chatpal-search/client/template/admin.html b/packages/chatpal-search/client/template/admin.html new file mode 100644 index 0000000000000..5235c4c4993a5 --- /dev/null +++ b/packages/chatpal-search/client/template/admin.html @@ -0,0 +1,55 @@ + diff --git a/packages/chatpal-search/client/template/admin.js b/packages/chatpal-search/client/template/admin.js new file mode 100644 index 0000000000000..05c2fe571b366 --- /dev/null +++ b/packages/chatpal-search/client/template/admin.js @@ -0,0 +1,63 @@ +import toastr from 'toastr'; + +Template.ChatpalAdmin.onCreated(function() { + + const re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; + + this.validateEmail = (email) => { + return re.test(email.toLowerCase()); + }; + + this.apiKey = new ReactiveVar(); + + const lang = RocketChat.settings.get('Language'); + + this.lang = (lang === 'de' || lang === 'en') ? lang : 'en'; + + this.tac = new ReactiveVar(); + + Meteor.call('chatpalUtilsGetTaC', this.lang, (err, data) => { + this.tac.set(data); + }); +}); + +Template.ChatpalAdmin.events({ + 'submit form'(e, t) { + e.preventDefault(); + + const email = e.target.email.value; + const tac = e.target.readtac.checked; + + if (!tac) { return toastr.error(TAPi18n.__('Chatpal_ERROR_TAC_must_be_checked')); } + if (!email || email === '') { return toastr.error(TAPi18n.__('Chatpal_ERROR_Email_must_be_set')); } + if (!t.validateEmail(email)) { return toastr.error(TAPi18n.__('Chatpal_ERROR_Email_must_be_valid')); } + + //TODO register + try { + Meteor.call('chatpalUtilsCreateKey', email, (err, key) => { + if (!key) { return toastr.error(TAPi18n.__('Chatpal_ERROR_username_already_exists')); } + + toastr.info(TAPi18n.__('Chatpal_created_key_successfully')); + + t.apiKey.set(key); + }); + + } catch (e) { + console.log(e); + toastr.error(TAPi18n.__('Chatpal_ERROR_username_already_exists'));//TODO error messages + } + } +}); + +//template +Template.ChatpalAdmin.helpers({ + apiKey() { + return Template.instance().apiKey.get(); + }, + isAdmin() { + return RocketChat.authz.hasRole(Meteor.userId(), 'admin'); + }, + tac() { + return Template.instance().tac.get(); + } +}); diff --git a/packages/chatpal-search/client/template/result.html b/packages/chatpal-search/client/template/result.html new file mode 100644 index 0000000000000..3246eb6148e82 --- /dev/null +++ b/packages/chatpal-search/client/template/result.html @@ -0,0 +1,153 @@ + + + + + + + diff --git a/packages/chatpal-search/client/template/result.js b/packages/chatpal-search/client/template/result.js new file mode 100644 index 0000000000000..b78a899560aa7 --- /dev/null +++ b/packages/chatpal-search/client/template/result.js @@ -0,0 +1,128 @@ +import moment from 'moment'; + +Template.ChatpalSearchResultTemplate.onCreated(function() { + this.badRequest = new ReactiveVar(false); + this.resultType = new ReactiveVar(this.data.settings.DefaultResultType); + this.data.parentPayload.resultType = this.resultType.get(); +}); + +Template.ChatpalSearchResultTemplate.events = { + 'click .chatpal-search-typefilter li'(evt, t) { + t.data.parentPayload.resultType = evt.currentTarget.getAttribute('value'); + t.data.payload.start = 0; + t.resultType.set(t.data.parentPayload.resultType); + t.data.search(); + }, + 'click .chatpal-paging-prev'(env, t) { + t.data.payload.start -= t.data.settings.PageSize; + t.data.search(); + }, + 'click .chatpal-paging-next'(env, t) { + t.data.payload.start = (t.data.payload.start || 0) + t.data.settings.PageSize; + t.data.search(); + }, + 'click .chatpal-show-more-messages'(evt, t) { + t.data.parentPayload.resultType = 'Messages'; + t.data.payload.start = 0; + t.data.payload.rows = t.data.settings.PageSize; + t.resultType.set(t.data.parentPayload.resultType); + t.data.search(); + } +}; + +Template.ChatpalSearchResultTemplate.helpers({ + result() { + return Template.instance().data.result.get(); + }, + searching() { + return Template.instance().data.searching.get(); + }, + resultType() { + return Template.instance().resultType.get(); + }, + navSelected(type) { + return Template.instance().resultType.get() === type ? 'selected' : ''; + }, + resultsFoundForAllSearch() { + const result = Template.instance().data.result.get(); + + if (!result) { return true; } + + return result.message.numFound > 0 || result.user.numFound > 0 || result.room.numFound > 0; + }, + moreMessagesThanDisplayed() { + const result = Template.instance().data.result.get(); + + return result.message.docs.length < result.message.numFound; + }, + resultNumFound() { + const result = Template.instance().data.result.get(); + if (result) { + switch (result.message.numFound) { + case 0: + return TAPi18n.__('Chatpal_no_search_results'); + case 1: + return TAPi18n.__('Chatpal_one_search_result'); + default: + return TAPi18n.__('Chatpal_search_results', result.message.numFound); + } + } + }, + resultPaging() { + const result = Template.instance().data.result.get(); + const pageSize = Template.instance().data.settings.PageSize; + if (result) { + return { + currentPage: 1 + result.message.start / pageSize, + numOfPages: Math.ceil(result.message.numFound / pageSize) + }; + } + } +}); + +Template.ChatpalSearchSingleMessage.helpers({ + roomIcon() { + const room = Session.get(`roomData${ this.rid }`); + if (room && room.t === 'd') { + return 'at'; + } + return RocketChat.roomTypes.getIcon(room && room.t); + }, + + roomLink() { + const subscription = RocketChat.models.Subscriptions.findOne({rid: this.rid}); + return RocketChat.roomTypes.getRouteLink(subscription.t, subscription); + }, + + roomName() { + const room = Session.get(`roomData${ this.rid }`); + return RocketChat.roomTypes.getRoomName(room.t, room); + }, + + time() { + return moment(this.created).format(RocketChat.settings.get('Message_TimeFormat')); + }, + date() { + return moment(this.created).format(RocketChat.settings.get('Message_DateFormat')); + } +}); + +Template.ChatpalSearchSingleRoom.helpers({ + roomIcon() { + const room = Session.get(`roomData${ this._id }`); + if (room && room.t === 'd') { + return 'at'; + } + return RocketChat.roomTypes.getIcon(room && room.t); + }, + roomLink() { + const subscription = RocketChat.models.Subscriptions.findOne({rid: this._id}); + return RocketChat.roomTypes.getRouteLink(subscription.t, subscription); + } +}); + +Template.ChatpalSearchSingleUser.helpers({ + cleanUsername() { + return this.user_username.replace(/<\/?em>/ig, ''); + } +}); diff --git a/packages/chatpal-search/client/template/suggestion.html b/packages/chatpal-search/client/template/suggestion.html new file mode 100644 index 0000000000000..b003193c8af37 --- /dev/null +++ b/packages/chatpal-search/client/template/suggestion.html @@ -0,0 +1,9 @@ + diff --git a/packages/chatpal-search/client/template/suggestion.js b/packages/chatpal-search/client/template/suggestion.js new file mode 100644 index 0000000000000..85f07f26c18c0 --- /dev/null +++ b/packages/chatpal-search/client/template/suggestion.js @@ -0,0 +1,7 @@ +Template.ChatpalSuggestionItemTemplate.onCreated(function() { + if (this.data.type === 'link') { + this.data.action = () => { + console.log('an example for an external link'); + }; + } +}); diff --git a/packages/chatpal-search/package.js b/packages/chatpal-search/package.js new file mode 100644 index 0000000000000..4c340c4416584 --- /dev/null +++ b/packages/chatpal-search/package.js @@ -0,0 +1,41 @@ +Package.describe({ + name: 'chatpal:search', + version: '0.0.1', + summary: 'Chatpal Search Provider', + git: '' +}); + +Package.onUse(function(api) { + api.use([ + 'ecmascript', + 'templating', + 'rocketchat:lib', + 'rocketchat:logger', + 'rocketchat:search', + 'kadira:flow-router', + 'meteorhacks:inject-initial' + ]); + + api.addFiles('server/asset/chatpal-enter.svg', 'server', {isAsset:true}); + api.addFiles('server/asset/chatpal-logo-icon-lightblue.svg', 'server', {isAsset:true}); + api.addFiles('server/asset/chatpal-logo-icon-darkblue.svg', 'server', {isAsset:true}); + + api.addFiles([ + 'server/provider/provider.js', + 'server/provider/index.js', + 'server/utils/logger.js', + 'server/utils/utils.js', + 'server/asset/config.js' + ], 'server'); + + api.addFiles([ + 'client/template/suggestion.html', + 'client/template/suggestion.js', + 'client/template/result.html', + 'client/template/result.js', + 'client/template/admin.html', + 'client/template/admin.js', + 'client/style.css', + 'client/route.js' + ], 'client'); +}); diff --git a/packages/chatpal-search/server/asset/chatpal-enter.svg b/packages/chatpal-search/server/asset/chatpal-enter.svg new file mode 100644 index 0000000000000..fe2bad32e76f6 --- /dev/null +++ b/packages/chatpal-search/server/asset/chatpal-enter.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/chatpal-search/server/asset/chatpal-logo-icon-darkblue.svg b/packages/chatpal-search/server/asset/chatpal-logo-icon-darkblue.svg new file mode 100644 index 0000000000000..bdd8d83274540 --- /dev/null +++ b/packages/chatpal-search/server/asset/chatpal-logo-icon-darkblue.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/packages/chatpal-search/server/asset/chatpal-logo-icon-lightblue.svg b/packages/chatpal-search/server/asset/chatpal-logo-icon-lightblue.svg new file mode 100644 index 0000000000000..7a13ff8dc8ba7 --- /dev/null +++ b/packages/chatpal-search/server/asset/chatpal-logo-icon-lightblue.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/packages/chatpal-search/server/asset/config.js b/packages/chatpal-search/server/asset/config.js new file mode 100644 index 0000000000000..db2c834140fe8 --- /dev/null +++ b/packages/chatpal-search/server/asset/config.js @@ -0,0 +1,5 @@ +/* globals Inject */ + +Inject.rawBody('chatpal-enter', Assets.getText('server/asset/chatpal-enter.svg')); +Inject.rawBody('chatpal-logo-icon-lightblue', Assets.getText('server/asset/chatpal-logo-icon-lightblue.svg')); +Inject.rawBody('chatpal-logo-icon-darkblue', Assets.getText('server/asset/chatpal-logo-icon-darkblue.svg')); diff --git a/packages/chatpal-search/server/provider/index.js b/packages/chatpal-search/server/provider/index.js new file mode 100644 index 0000000000000..213efecbc648a --- /dev/null +++ b/packages/chatpal-search/server/provider/index.js @@ -0,0 +1,449 @@ +import ChatpalLogger from '../utils/logger'; +import { Random } from 'meteor/random'; + +/** + * Enables HTTP functions on Chatpal Backend + */ +class Backend { + + constructor(options) { + this._options = options; + } + + /** + * index a set of Sorl documents + * @param docs + * @returns {boolean} + */ + index(docs) { + const options = { + data:docs, + params:{language:this._options.language}, + ...this._options.httpOptions + }; + + try { + + const response = HTTP.call('POST', `${ this._options.baseurl }${ this._options.updatepath }`, options); + + if (response.statusCode >= 200 && response.statusCode < 300) { + ChatpalLogger.debug(`indexed ${ docs.length } documents`, JSON.stringify(response.data, null, 2)); + } else { + throw new Error(response); + } + + } catch (e) { + //TODO how to deal with this + ChatpalLogger.error('indexing failed', JSON.stringify(e, null, 2)); + return false; + } + + } + + /** + * remove an entry by type and id + * @param type + * @param id + * @returns {boolean} + */ + remove(type, id) { + ChatpalLogger.debug(`Remove ${ type }(${ id }) from Index`); + + const options = { + data:{ + delete: { + query: `id:${ id } AND type:${ type }` + }, + commit:{} + }, + ...this._options.httpOptions + }; + + try { + const response = HTTP.call('POST', this._options.baseurl + this._options.clearpath, options); + + return response.statusCode >= 200 && response.statusCode < 300; + } catch (e) { + return false; + } + } + + count(type) { + return this.query({type, rows:0, text:'*'})[type].numFound; + } + + /** + * query with params + * @param params + * @param callback + */ + query(params, callback) { + + const options = { + params, + ...this._options.httpOptions + }; + + ChatpalLogger.debug('query: ', JSON.stringify(options, null, 2)); + + try { + if (callback) { + HTTP.call('POST', this._options.baseurl + this._options.searchpath, options, (err, result) => { + if (err) { return callback(err); } + + callback(undefined, result.data); + }); + } else { + + const response = HTTP.call('POST', this._options.baseurl + this._options.searchpath, options); + + if (response.statusCode >= 200 && response.statusCode < 300) { + return response.data; + } else { + throw new Error(response); + } + } + } catch (e) { + ChatpalLogger.error('query failed', JSON.stringify(e, null, 2)); + throw e; + } + } + + suggest(params, callback) { + + const options = { + params, + ...this._options.httpOptions + }; + + HTTP.call('POST', this._options.baseurl + this._options.suggestionpath, options, (err, result) => { + if (err) { return callback(err); } + + try { + callback(undefined, result.data.suggestion); + } catch (e) { + callback(e); + } + }); + } + + clear() { + ChatpalLogger.debug('Clear Index'); + + const options = { + data:{ + delete: { + query: '*:*' + }, + commit:{} + }, ...this._options.httpOptions + }; + + try { + const response = HTTP.call('POST', this._options.baseurl + this._options.clearpath, options); + + return response.statusCode >= 200 && response.statusCode < 300; + } catch (e) { + return false; + } + } + + /** + * statically ping with configuration + * @param options + * @returns {boolean} + */ + static ping(config) { + + const options = { + params: { + stats:true + }, + ...config.httpOptions + }; + + try { + const response = HTTP.call('GET', config.baseurl + config.pingpath, options); + + if (response.statusCode >= 200 && response.statusCode < 300) { + return response.data.stats; + } else { + return false; + } + } catch (e) { + return false; + } + } + +} + +/** + * Enabled batch indexing + */ +class BatchIndexer { + + constructor(size, func, ...rest) { + this._size = size; + this._func = func; + this._rest = rest; + this._values = []; + } + + add(value) { + this._values.push(value); + if (this._values.length === this._size) { + this.flush(); + } + } + + flush() { + this._func(this._values, this._rest);//TODO if flush does not work + this._values = []; + } +} + +/** + * Provides index functions to chatpal provider + */ +export default class Index { + + /** + * Creates Index Stub + * @param options + * @param clear if a complete reindex should be done + */ + constructor(options, clear, date) { + + this._id = Random.id(); + + this._backend = new Backend(options); + + this._options = options; + + this._batchIndexer = new BatchIndexer(this._options.batchSize || 100, (values) => this._backend.index(values)); + + this._bootstrap(clear, date); + } + + /** + * prepare solr documents + * @param type + * @param doc + * @returns {*} + * @private + */ + _getIndexDocument(type, doc) { + switch (type) { + case 'message': + return { + id: doc._id, + rid: doc.rid, + user: doc.u._id, + created: doc.ts, + updated: doc._updatedAt, + text: doc.msg, + type + }; + case 'room': + return { + id: doc._id, + rid: doc._id, + created: doc.createdAt, + updated: doc.lm ? doc.lm : doc._updatedAt, + type, + room_name: doc.name, + room_announcement: doc.announcement, + room_description: doc.description, + room_topic: doc.topic + }; + case 'user': + return { + id: doc._id, + created: doc.createdAt, + updated: doc._updatedAt, + type, + user_username: doc.username, + user_name: doc.name, + user_email: doc.emails && doc.emails.map((e) => { return e.address; }) + }; + default: throw new Error(`Cannot index type '${ type }'`); + } + } + + /** + * return true if there are messages in the databases which has been created before *date* + * @param date + * @returns {boolean} + * @private + */ + _existsDataOlderThan(date) { + return RocketChat.models.Messages.model.find({ts:{$lt: new Date(date)}, t:{$exists:false}}, {limit:1}).fetch().length > 0; + } + + _doesRoomCountDiffer() { + return RocketChat.models.Rooms.find({t:{$ne:'d'}}).count() !== this._backend.count('room'); + } + + _doesUserCountDiffer() { + return Meteor.users.find({active:true}).count() !== this._backend.count('user'); + } + + /** + * Index users by using a database cursor + */ + _indexUsers() { + const cursor = Meteor.users.find({active:true}); + + ChatpalLogger.debug(`Start indexing ${ cursor.count() } users`); + + cursor.forEach((user) => { + this.indexDoc('user', user, false); + }); + + ChatpalLogger.info(`Users indexed successfully (index-id: ${ this._id })`); + } + + /** + * Index rooms by database cursor + * @private + */ + _indexRooms() { + const cursor = RocketChat.models.Rooms.find({t:{$ne:'d'}}); + + ChatpalLogger.debug(`Start indexing ${ cursor.count() } rooms`); + + cursor.forEach((room) => { + this.indexDoc('room', room, false); + }); + + ChatpalLogger.info(`Rooms indexed successfully (index-id: ${ this._id })`); + } + + _indexMessages(date, gap) { + + const start = new Date(date - gap); + const end = new Date(date); + + const cursor = RocketChat.models.Messages.model.find({ts:{$gt: start, $lt: end}, t:{$exists:false}}); + + ChatpalLogger.debug(`Start indexing ${ cursor.count() } messages between ${ start.toString() } and ${ end.toString() }`); + + cursor.forEach((message) => { + this.indexDoc('message', message, false); + }); + + ChatpalLogger.info(`Messages between ${ start.toString() } and ${ end.toString() } indexed successfully (index-id: ${ this._id })`); + + return start.getTime(); + } + + _run(date, resolve, reject) { + + this._running = true; + + if (this._existsDataOlderThan(date) && !this._break) { + + Meteor.setTimeout(() => { + date = this._indexMessages(date, (this._options.windowSize || 24) * 3600000); + + this._run(date, resolve, reject); + + }, this._options.timeout || 1000); + } else if (this._break) { + ChatpalLogger.info(`stopped bootstrap (index-id: ${ this._id })`); + + this._batchIndexer.flush(); + + this._running = false; + + resolve(); + } else { + + ChatpalLogger.info(`No messages older than already indexed date ${ new Date(date).toString() }`); + + if (this._doesUserCountDiffer() && !this._break) { + this._indexUsers(); + } else { + ChatpalLogger.info('Users already indexed'); + } + + if (this._doesRoomCountDiffer() && !this._break) { + this._indexRooms(); + } else { + ChatpalLogger.info('Rooms already indexed'); + } + + this._batchIndexer.flush(); + + ChatpalLogger.info(`finished bootstrap (index-id: ${ this._id })`); + + this._running = false; + + resolve(); + } + } + + _bootstrap(clear, date) { + + ChatpalLogger.info('Start bootstrapping'); + + return new Promise((resolve, reject) => { + + if (clear) { + this._backend.clear(); + date = new Date().getTime(); + } + + this._run(date, resolve, reject); + + }); + } + + static ping(options) { + return Backend.ping(options); + } + + stop() { + this._break = true; + } + + reindex() { + if (!this._running) { + this._bootstrap(true); + } + } + + indexDoc(type, doc, flush = true) { + this._batchIndexer.add(this._getIndexDocument(type, doc)); + + if (flush) { this._batchIndexer.flush(); } + + return true; + } + + removeDoc(type, id) { + return this._backend.remove(type, id); + } + + query(text, language, acl, type, start, rows, callback, params = {}) { + this._backend.query({ + text, + language, + acl, + type, + start, + rows, + ...params + }, callback); + } + + suggest(text, language, acl, type, callback) { + this._backend.suggest({ + text, + language, + acl, + type + }, callback); + } + +} diff --git a/packages/chatpal-search/server/provider/provider.js b/packages/chatpal-search/server/provider/provider.js new file mode 100644 index 0000000000000..d7092e75888d0 --- /dev/null +++ b/packages/chatpal-search/server/provider/provider.js @@ -0,0 +1,347 @@ +import {searchProviderService} from 'meteor/rocketchat:search'; +import {SearchProvider} from 'meteor/rocketchat:search'; +import Index from './index'; +import ChatpalLogger from '../utils/logger'; + +/** + * The chatpal search provider enables chatpal search. An appropriate backedn has to be specified by settings. + */ +class ChatpalProvider extends SearchProvider { + + /** + * Create chatpal provider with some settings for backend and ui + */ + constructor() { + super('chatpalProvider'); + + this.chatpalBaseUrl = 'https://beta.chatpal.io/v1'; + + this._settings.add('Backend', 'select', 'cloud', { + values:[ + {key: 'cloud', i18nLabel: 'Cloud Service'}, + {key: 'onsite', i18nLabel: 'On-Site'} + ], + i18nLabel: 'Chatpal_Backend', + i18nDescription: 'Chatpal_Backend_Description' + }); + this._settings.add('API_Key', 'string', '', { + enableQuery:[{ + _id: 'Search.chatpalProvider.Backend', + value: 'cloud' + }], + i18nLabel: 'Chatpal_API_Key', + i18nDescription: 'Chatpal_API_Key_Description' + }); + this._settings.add('Base_URL', 'string', '', { + enableQuery:[{ + _id: 'Search.chatpalProvider.Backend', + value: 'onsite' + }], + i18nLabel: 'Chatpal_Base_URL', + i18nDescription: 'Chatpal_Base_URL_Description' + }); + this._settings.add('HTTP_Headers', 'string', '', { + enableQuery:[{ + _id: 'Search.chatpalProvider.Backend', + value: 'onsite' + }], + multiline: true, + i18nLabel: 'Chatpal_HTTP_Headers', + i18nDescription: 'Chatpal_HTTP_Headers_Description' + }); + this._settings.add('Main_Language', 'select', 'en', { + values: [ + {key: 'en', i18nLabel: 'English'}, + {key: 'none', i18nLabel: 'Language_Not_set'}, + {key: 'cs', i18nLabel: 'Czech'}, + {key: 'de', i18nLabel: 'Deutsch'}, + {key: 'el', i18nLabel: 'Greek'}, + {key: 'es', i18nLabel: 'Spanish'}, + {key: 'fi', i18nLabel: 'Finish'}, + {key: 'fr', i18nLabel: 'French'}, + {key: 'hu', i18nLabel: 'Hungarian'}, + {key: 'it', i18nLabel: 'Italian'}, + {key: 'nl', i18nLabel: 'Dutsch'}, + {key: 'pl', i18nLabel: 'Polish'}, + {key: 'pt', i18nLabel: 'Portuguese'}, + {key: 'pt_BR', i18nLabel: 'Brasilian'}, + {key: 'ro', i18nLabel: 'Romanian'}, + {key: 'ru', i18nLabel: 'Russian'}, + {key: 'sv', i18nLabel: 'Swedisch'}, + {key: 'tr', i18nLabel: 'Turkish'}, + {key: 'uk', i18nLabel: 'Ukrainian'} + ], + i18nLabel: 'Chatpal_Main_Language', + i18nDescription: 'Chatpal_Main_Language_Description' + }); + this._settings.add('DefaultResultType', 'select', 'All', { + values: [ + {key: 'All', i18nLabel: 'All'}, + {key: 'Messages', i18nLabel: 'Messages'} + ], + i18nLabel: 'Chatpal_Default_Result_Type', + i18nDescription: 'Chatpal_Default_Result_Type_Description' + }); + this._settings.add('PageSize', 'int', 15, { + i18nLabel: 'Search_Page_Size' + }); + this._settings.add('SuggestionEnabled', 'boolean', true, { + i18nLabel: 'Chatpal_Suggestion_Enabled', + alert: 'This feature is currently in beta and will be extended in the future' + }); + this._settings.add('BatchSize', 'int', 100, { + i18nLabel: 'Chatpal_Batch_Size', + i18nDescription: 'Chatpal_Batch_Size_Description' + }); + this._settings.add('TimeoutSize', 'int', 5000, { + i18nLabel: 'Chatpal_Timeout_Size', + i18nDescription: 'Chatpal_Timeout_Size_Description' + }); + this._settings.add('WindowSize', 'int', 48, { + i18nLabel: 'Chatpal_Window_Size', + i18nDescription: 'Chatpal_Window_Size_Description' + }); + } + + get i18nLabel() { + return 'Chatpal Provider'; + } + + get iconName() { + return 'chatpal-logo-icon-darkblue'; + } + + get resultTemplate() { + return 'ChatpalSearchResultTemplate'; + } + + get suggestionItemTemplate() { + return 'ChatpalSuggestionItemTemplate'; + } + + get supportsSuggestions() { + return this._settings.get('SuggestionEnabled'); + } + + /** + * indexing for messages, rooms and users + * @inheritDoc + */ + on(name, value, payload) { + + if (!this.index) { + this.indexFail = true; + return false; + } + + switch (name) { + case 'message.save': return this.index.indexDoc('message', payload); + case 'user.save': return this.index.indexDoc('user', payload); + case 'room.save': return this.index.indexDoc('room', payload); + case 'message.delete': return this.index.removeDoc('message', value); + case 'user.delete': return this.index.removeDoc('user', value); + case 'room.delete': return this.index.removeDoc('room', value); + } + + return true; + } + + /** + * Check if the index has to be deleted and completely new reindexed + * @param reason the reason for the provider start + * @returns {boolean} + * @private + */ + _checkForClear(reason) { + + if (reason === 'startup') { return false; } + + if (reason === 'switch') { return true; } + + return this._indexConfig.backendtype !== this._settings.get('Backend') || + (this._indexConfig.backendtype === 'onsite' && this._indexConfig.baseurl !== (this._settings.get('Base_URL').endsWith('/') ? this._settings.get('Base_URL').slice(0, -1) : this._settings.get('Base_URL'))) || + (this._indexConfig.backendtype === 'cloud' && this._indexConfig.httpOptions.headers['X-Api-Key'] !== this._settings.get('API_Key')) || + this._indexConfig.language !== this._settings.get('Main_Language'); + } + + /** + * parse string to object that can be used as header for HTTP calls + * @returns {{}} + * @private + */ + _parseHeaders() { + const headers = {}; + const sh = this._settings.get('HTTP_Headers').split('\n'); + sh.forEach(function(d) { + const ds = d.split(':'); + if (ds.length === 2 && ds[0].trim() !== '') { + headers[ds[0]] = ds[1]; + } + }); + return headers; + } + + /** + * ping if configuration has been set correctly + * @param config + * @param resolve if ping was successfull + * @param reject if some error occurs + * @param timeout until ping is repeated + * @private + */ + _ping(config, resolve, reject, timeout = 5000) { + + const maxTimeout = 200000; + + const stats = Index.ping(config); + + if (stats) { + ChatpalLogger.debug('ping was successfull'); + resolve({config, stats}); + } else { + + ChatpalLogger.warn(`ping failed, retry in ${ timeout } ms`); + + this._pingTimeout = Meteor.setTimeout(() => { + this._ping(config, resolve, reject, Math.min(maxTimeout, 2*timeout)); + }, timeout); + } + + } + + /** + * Get index config based on settings + * @param callback + * @private + */ + _getIndexConfig() { + + return new Promise((resolve, reject) => { + const config = { + backendtype: this._settings.get('Backend') + }; + + if (this._settings.get('Backend') === 'cloud') { + config.baseurl = this.chatpalBaseUrl; + config.language = this._settings.get('Main_Language'); + config.searchpath = '/search/search'; + config.updatepath = '/search/update'; + config.pingpath = '/search/ping'; + config.clearpath = '/search/clear'; + config.suggestionpath = '/search/suggest'; + config.httpOptions = { + headers: { + 'X-Api-Key': this._settings.get('API_Key') + } + }; + } else { + config.baseurl = this._settings.get('Base_URL').endsWith('/') ? this._settings.get('Base_URL').slice(0, -1) : this._settings.get('Base_URL'); + config.language = this._settings.get('Main_Language'); + config.searchpath = '/chatpal/search'; + config.updatepath = '/chatpal/update'; + config.pingpath = '/chatpal/ping'; + config.clearpath = '/chatpal/clear'; + config.suggestionpath = '/chatpal/suggest'; + config.httpOptions = { + headers: this._parseHeaders() + }; + } + + config.batchSize = this._settings.get('BatchSize'); + config.timeout = this._settings.get('TimeoutSize'); + config.windowSize = this._settings.get('WindowSize'); + + this._ping(config, resolve, reject); + }); + + } + + /** + * @inheritDoc + * @param callback + */ + stop(resolve) { + ChatpalLogger.info('Provider stopped'); + Meteor.clearTimeout(this._pingTimeout); + this.indexFail = false; + this.index && this.index.stop(); + resolve(); + } + + /** + * @inheritDoc + * @param reason + * @param resolve + * @param reject + */ + start(reason, resolve, reject) { + + const clear = this._checkForClear(reason); + + ChatpalLogger.debug(`clear = ${ clear } with reason '${ reason }'`); + + this._getIndexConfig().then((server) => { + this._indexConfig = server.config; + + this._stats = server.stats; + + ChatpalLogger.debug('config:', JSON.stringify(this._indexConfig, null, 2)); + ChatpalLogger.debug('stats:', JSON.stringify(this._stats, null, 2)); + + this.index = new Index(this._indexConfig, this.indexFail || clear, this._stats.message.oldest || new Date().valueOf()); + + resolve(); + }, reject); + } + + /** + * returns a list of rooms that are allowed to see by current user + * @param context + * @private + */ + _getAcl(context) { + return RocketChat.models.Subscriptions.find({'u._id': context.uid}).fetch().map(room => room.rid); + } + + /** + * @inheritDoc + * @returns {*} + */ + search(text, context, payload, callback) { + + if (!this.index) { return callback({msg:'Chatpal_currently_not_active'}); } + + const type = payload.resultType === 'All' ? ['message', 'user', 'room'] : ['message']; + + this.index.query( + text, + this._settings.get('Main_Language'), + this._getAcl(context), + type, + payload.start || 0, + payload.rows || this._settings.get('PageSize'), + callback + ); + + } + + /** + * @inheritDoc + */ + suggest(text, context, payload, callback) { + + if (!this.index) { return callback({msg:'Chatpal_currently_not_active'}); } + + const type = payload.resultType === 'All' ? ['message', 'user', 'room'] : ['message']; + + this.index.suggest( + text, + this._settings.get('Main_Language'), + this._getAcl(context), + type, + callback + ); + } +} + +searchProviderService.register(new ChatpalProvider()); diff --git a/packages/chatpal-search/server/utils/logger.js b/packages/chatpal-search/server/utils/logger.js new file mode 100644 index 0000000000000..2e5278a11924d --- /dev/null +++ b/packages/chatpal-search/server/utils/logger.js @@ -0,0 +1,2 @@ +const ChatpalLogger = new Logger('Chatpal Logger', {}); +export default ChatpalLogger; diff --git a/packages/chatpal-search/server/utils/utils.js b/packages/chatpal-search/server/utils/utils.js new file mode 100644 index 0000000000000..0c90cf564cc16 --- /dev/null +++ b/packages/chatpal-search/server/utils/utils.js @@ -0,0 +1,26 @@ +Meteor.methods({ + 'chatpalUtilsCreateKey'(email) { + try { + const response = HTTP.call('POST', 'https://beta.chatpal.io/v1/account', {data: {email, tier: 'free'}}); + if (response.statusCode === 201) { + return response.data.key; + } else { + return false; + } + } catch (e) { + return false; + } + }, + 'chatpalUtilsGetTaC'(lang) { + try { + const response = HTTP.call('GET', `https://beta.chatpal.io/v1/terms/${ lang }.html`); + if (response.statusCode === 200) { + return response.content; + } else { + return undefined; + } + } catch (e) { + return false; + } + } +}); diff --git a/packages/rocketchat-api/server/v1/chat.js b/packages/rocketchat-api/server/v1/chat.js index 6e3486ca34ab4..601300c84aed1 100644 --- a/packages/rocketchat-api/server/v1/chat.js +++ b/packages/rocketchat-api/server/v1/chat.js @@ -137,10 +137,10 @@ RocketChat.API.v1.addRoute('chat.search', { authRequired: true }, { } let result; - Meteor.runAsUser(this.userId, () => result = Meteor.call('messageSearch', searchText, roomId, limit)); + Meteor.runAsUser(this.userId, () => result = Meteor.call('messageSearch', searchText, roomId, limit).message.docs); return RocketChat.API.v1.success({ - messages: result.messages + messages: result }); } }); diff --git a/packages/rocketchat-i18n/i18n/ca.i18n.json b/packages/rocketchat-i18n/i18n/ca.i18n.json index 371ddd44d96fe..c6386c33b5d82 100644 --- a/packages/rocketchat-i18n/i18n/ca.i18n.json +++ b/packages/rocketchat-i18n/i18n/ca.i18n.json @@ -2211,7 +2211,7 @@ "You_are_logged_in_as": "Sessió iniciada com", "You_are_not_authorized_to_view_this_page": "No està autoritzat a veure aquesta pàgina.", "You_can_change_a_different_avatar_too": "Es pot ignorar l'avatar d'aquesta integració.", - "You_can_search_using_RegExp_eg": "Es pot cercar utilitzant expressions regulars, p. ex:", + "You_can_search_using_RegExp_eg": "Es pot cercar utilitzant expressions regulars, p. ex: /^text$/i", "You_can_use_an_emoji_as_avatar": "També es pot utilitzar un emoji com a avatar.", "You_can_use_webhooks_to_easily_integrate_livechat_with_your_CRM": "Es poden utilitzar webhooks per a integrar fàcilment el xat en viu amb el teu CRM.", "You_cant_leave_a_livechat_room_Please_use_the_close_button": "No es pot abandonar una sala de xat en viu. Si us plau, utilitzi el botó de tancar.", diff --git a/packages/rocketchat-i18n/i18n/de-AT.i18n.json b/packages/rocketchat-i18n/i18n/de-AT.i18n.json index de393ebacf6b2..8bba10fee4e5c 100644 --- a/packages/rocketchat-i18n/i18n/de-AT.i18n.json +++ b/packages/rocketchat-i18n/i18n/de-AT.i18n.json @@ -2211,7 +2211,7 @@ "You_are_logged_in_as": "Sie sind angemeldet als", "You_are_not_authorized_to_view_this_page": "Sie sind nicht berechtigt, diese Seite zu sehen.", "You_can_change_a_different_avatar_too": "Sie können das aktuell verwendete Profilbild überschreiben, um von dieser Integration zu veröffentlichen.", - "You_can_search_using_RegExp_eg": "Sie können RegExp zum Suchen verwenden. z.B.", + "You_can_search_using_RegExp_eg": "Sie können RegExp zum Suchen verwenden. z.B. /^text$/i", "You_can_use_an_emoji_as_avatar": "Sie können auch einen Emoji als Profilbild verwenden.", "You_can_use_webhooks_to_easily_integrate_livechat_with_your_CRM": "Sie können webhooks verwenden, um den livechat mit Ihrem CRM zu integrieren.", "You_cant_leave_a_livechat_room_Please_use_the_close_button": "Sie können keinen Livechat-Raum verlassen. Bitte verwenden Sie die Schaltfläche zum Schließen.", diff --git a/packages/rocketchat-i18n/i18n/de.i18n.json b/packages/rocketchat-i18n/i18n/de.i18n.json index a60321b80af60..f2f7a2654b796 100644 --- a/packages/rocketchat-i18n/i18n/de.i18n.json +++ b/packages/rocketchat-i18n/i18n/de.i18n.json @@ -391,6 +391,52 @@ "Chat_button": "Chat-Button", "Chat_closed": "Chat geschlossen", "Chat_closed_successfully": "Chat erfolgreich geschlossen", + "Chatpal_No_Results":"Kein Ergebnis", + "Chatpal_More":"Mehr", + "Chatpal_Messages":"Nachrichten", + "Chatpal_Rooms":"Räume", + "Chatpal_Users":"User", + "Chatpal_Search_Results":"Sichergebnisse", + "Chatpal_All_Results":"Alle", + "Chatpal_Messages_Only":"Nachrichten", + "Chatpal_HTTP_Headers": "Http Headers", + "Chatpal_HTTP_Headers_Description": "Liste von HTTP Headers, ein header pro Zeile. Format: name:value", + "Chatpal_API_Key": "API Key", + "Chatpal_API_Key_Description": "Sie haben noch keinen API Key? Hier gehts zum Key!", + "Chatpal_no_search_results":"Kein Ergebnis", + "Chatpal_one_search_result":"1 Ergebnis", + "Chatpal_search_results":"%s Ergebnisse", + "Chatpal_search_page_of":"Seite %s von %s", + "Chatpal_go_to_message":"Zur Nachricht", + "Chatpal_Welcome":"Viel Spaß beim finden!", + "Chatpal_go_to_user":"Sende Nachricht", + "Chatpal_go_to_room":"Zum Kanal", + "Chatpal_Backend":"Backend Typ", + "Chatpal_Backend_Description":"Wählen Sie, ob sie Chatpal als Service oder als On-Site Installation benutzen wollen", + "Chatpal_Suggestion_Enabled":"Suchvorschläge aktiviert", + "Chatpal_Base_URL":"Basis Url", + "Chatpal_Base_URL_Description":"Auf github finden Sie eine Beschreibung, wie Sie Chatpal lokal installieren. Die URL muss absolut sein und auf den chatpal core zeigen, z.B. http://localhost:8983/solr/chatpal.", + "Chatpal_Main_Language":"Hauptsprache", + "Chatpal_Main_Language_Description":"Die Sprache, die in den meisten Nachrichten verwendet wird", + "Chatpal_Default_Result_Type":"Vordefinierter Resultattyp", + "Chatpal_Default_Result_Type_Description":"Wählen Sie, ob sie Chatpal als Service oder als On-Site Installation benutzen wollen.", + "Chatpal_AdminPage":"Chatpal Adminseite", + "Chatpal_Email_Address":"Email Adresse", + "Chatpal_Terms_and_Conditions":"Geschäftsbedingungen", + "Chatpal_TAC_read":"Ich habe die Geschäftsbedingungen gelesen", + "Chatpal_create_key":"Api Key erzeugen", + "Chatpal_Batch_Size":"Index Batchgröße", + "Chatpal_Timeout_Size":"Index Timeout", + "Chatpal_Window_Size":"Index Fenstergröße", + "Chatpal_Batch_Size_Description":"Anzahl der Indexdokumente pro Batch (beim Bootstrapping)", + "Chatpal_Timeout_Size_Description":"Zeit zwischen 2 Indexfenster in ms (beim Bootstrapping)", + "Chatpal_Window_Size_Description":"Größe der Indexfenster in h (beim Bootstrapping)", + "Chatpal_ERROR_TAC_must_be_checked":"Die Geschäftsbedingungen müssen akzeptiert werden", + "Chatpal_ERROR_Email_must_be_set":"ine E-Mail Adresse muss angegeben werden", + "Chatpal_ERROR_Email_must_be_valid":"ine E-Mail Adresse mussvalide sein", + "Chatpal_ERROR_username_already_exists":"Benutzername existiert bereits", + "Chatpal_created_key_successfully":"API-Key erfolgreich erstellt", + "Chatpal_run_search":"Suche", "Chat_Now": "Jetzt chatten", "Chat_window": "Chatfenster", "Chatops_Enabled": "ChatOps aktivieren", @@ -2212,7 +2258,7 @@ "You_are_logged_in_as": "Du bist angemeldet als", "You_are_not_authorized_to_view_this_page": "Sie sind nicht berechtigt, diese Seite zu sehen", "You_can_change_a_different_avatar_too": "Sie können für Post dieser Integration ein anderes Profilbild verwenden", - "You_can_search_using_RegExp_eg": "Sie können einen regulären Ausdruck zum Suchen verwenden. z.B.", + "You_can_search_using_RegExp_eg": "Sie können einen regulären Ausdruck zum Suchen verwenden. z.B. /^text$/i", "You_can_use_an_emoji_as_avatar": "Sie können auch einen Emoji als Profilbild verwenden", "You_can_use_webhooks_to_easily_integrate_livechat_with_your_CRM": "Sie können Webhooks verwenden, um den Livechat mit Ihrem CRM zu integrieren.", "You_cant_leave_a_livechat_room_Please_use_the_close_button": "Sie können keinen Livechat-Raum verlassen. Bitte Schließen Sie die Anfrage ", @@ -2242,4 +2288,4 @@ "your_message_optional": "Ihre optionale Nachricht", "Your_password_is_wrong": "Falsches Passwort", "Your_push_was_sent_to_s_devices": "Eine Push-Nachricht wurde an %s Geräte gesendet." -} \ No newline at end of file +} diff --git a/packages/rocketchat-i18n/i18n/el.i18n.json b/packages/rocketchat-i18n/i18n/el.i18n.json index 02efaf43bdc5f..5e3d5d18b8f38 100644 --- a/packages/rocketchat-i18n/i18n/el.i18n.json +++ b/packages/rocketchat-i18n/i18n/el.i18n.json @@ -2211,7 +2211,7 @@ "You_are_logged_in_as": "Έχετε συνδεθεί ως", "You_are_not_authorized_to_view_this_page": "Δεν έχετε δικαίωμα να δείτε αυτή τη σελίδα.", "You_can_change_a_different_avatar_too": "Μπορείτε να παρακάμψετε το avatar που χρησιμοποιείται για να δημοσιεύσετε από αυτή την ένταξη.", - "You_can_search_using_RegExp_eg": "Μπορείτε να κάνετε αναζήτηση χρησιμοποιώντας RegExp. π.χ.", + "You_can_search_using_RegExp_eg": "Μπορείτε να κάνετε αναζήτηση χρησιμοποιώντας RegExp. π.χ. /^text$/i", "You_can_use_an_emoji_as_avatar": "Μπορείτε επίσης να χρησιμοποιήσετε ένα emoji ως avatar.", "You_can_use_webhooks_to_easily_integrate_livechat_with_your_CRM": "Μπορείτε να χρησιμοποιήσετε webhooks να ενσωματώσουν εύκολα livechat με CRM σας.", "You_cant_leave_a_livechat_room_Please_use_the_close_button": "Δεν μπορείτε να αφήσετε ένα δωμάτιο livechat. Παρακαλούμε, χρησιμοποιήστε το κουμπί Κλείσιμο.", diff --git a/packages/rocketchat-i18n/i18n/en.i18n.json b/packages/rocketchat-i18n/i18n/en.i18n.json index 8aab13363db55..4633741fefdc7 100644 --- a/packages/rocketchat-i18n/i18n/en.i18n.json +++ b/packages/rocketchat-i18n/i18n/en.i18n.json @@ -380,6 +380,52 @@ "CAS_Sync_User_Data_FieldMap_Description": "Use this JSON input to build internal attributes (key) from external attributes (value). External attribute names enclosed with '%' will interpolated in value strings.
Example, `{\"email\":\"%email%\", \"name\":\"%firstname%, %lastname%\"}`

The attribute map is always interpolated. In CAS 1.0 only the `username` attribute is available. Available internal attributes are: username, name, email, rooms; rooms is a comma separated list of rooms to join upon user creation e.g: {\"rooms\": \"%team%,%department%\"} would join CAS users on creation to their team and department channel.", "CAS_version": "CAS Version", "CAS_version_Description": "Only use a supported CAS version supported by your CAS SSO service.", + "Chatpal_No_Results":"No Results", + "Chatpal_More":"More", + "Chatpal_Messages":"Messages", + "Chatpal_Rooms":"Rooms", + "Chatpal_Users":"Users", + "Chatpal_Search_Results":"Search Results", + "Chatpal_All_Results":"All", + "Chatpal_Messages_Only":"Messages", + "Chatpal_HTTP_Headers": "Http Headers", + "Chatpal_HTTP_Headers_Description": "List of HTTP Headers, one header per line. Format: name:value", + "Chatpal_API_Key": "API Key", + "Chatpal_API_Key_Description": "You don't yet have an API Key? Get one!", + "Chatpal_no_search_results":"No result", + "Chatpal_one_search_result":"Found 1 result", + "Chatpal_search_results":"Found %s results", + "Chatpal_search_page_of":"Page %s of %s", + "Chatpal_go_to_message":"Jump", + "Chatpal_Welcome":"Enjoy your search!", + "Chatpal_go_to_user":"Send direct message", + "Chatpal_go_to_room":"Jump", + "Chatpal_Backend":"Backend Type", + "Chatpal_Backend_Description":"Select if you want to use Chatpal as a Service or as On-Site Installation", + "Chatpal_Suggestion_Enabled":"Suggestions enabled", + "Chatpal_Base_URL":"Base Url", + "Chatpal_Base_URL_Description":"Find some description how to run a local instance on github. The URL must be absolue and point to the chatpal core, e.g. http://localhost:8983/solr/chatpal.", + "Chatpal_Main_Language":"Main Language", + "Chatpal_Main_Language_Description":"The language that is used most in conversations", + "Chatpal_Default_Result_Type":"Default Result Type", + "Chatpal_Default_Result_Type_Description":"Defines which result type is shown by result. All means that an overview for all types is provided.", + "Chatpal_AdminPage":"Chatpal Admin Page", + "Chatpal_Email_Address":"Email Address", + "Chatpal_Terms_and_Conditions":"Terms and Conditions", + "Chatpal_TAC_read":"I have read the terms and conditions", + "Chatpal_create_key":"Create Key", + "Chatpal_Batch_Size":"Index Batch Size", + "Chatpal_Timeout_Size":"Index Timeout", + "Chatpal_Window_Size":"Index Window Size", + "Chatpal_Batch_Size_Description":"The batch size of index documents (on bootstrapping)", + "Chatpal_Timeout_Size_Description":"The time between 2 index windows in ms (on bootstrapping)", + "Chatpal_Window_Size_Description":"The size of index windows in hours (on bootstrapping)", + "Chatpal_ERROR_TAC_must_be_checked":"Terms and Conditions must be checked", + "Chatpal_ERROR_Email_must_be_set":"Email must be set", + "Chatpal_ERROR_Email_must_be_valid":"Email must be valid", + "Chatpal_ERROR_username_already_exists":"Username already exists", + "Chatpal_created_key_successfully":"API-Key created successfully", + "Chatpal_run_search":"Search", "CDN_PREFIX": "CDN Prefix", "Certificates_and_Keys": "Certificates and Keys", "Change_Room_Type": "Changing the Room Type", @@ -1072,6 +1118,7 @@ "Label": "Label", "Language": "Language", "Language_Version": "English Version", + "Language_Not_set":"No specific", "Last_login": "Last login", "Last_Message_At": "Last Message At", "Last_seen": "Last seen", @@ -1725,6 +1772,10 @@ "Search_by_username": "Search by username", "Search_Messages": "Search Messages", "Search_Private_Groups": "Search Private Groups", + "Search_Provider": "Search Provider", + "Search_Page_Size": "Page Size", + "Search_current_provider_not_active":"Current Search Provider is not active", + "Search_message_search_failed":"Search request failed", "seconds": "seconds", "Secret_token": "Secret Token", "Security": "Security", @@ -2240,7 +2291,7 @@ "You_are_logged_in_as": "You are logged in as", "You_are_not_authorized_to_view_this_page": "You are not authorized to view this page.", "You_can_change_a_different_avatar_too": "You can override the avatar used to post from this integration.", - "You_can_search_using_RegExp_eg": "You can search using RegExp. e.g.", + "You_can_search_using_RegExp_eg": "You can search using RegExp. e.g. /^text$/i", "You_can_use_an_emoji_as_avatar": "You can also use an emoji as an avatar.", "You_can_use_webhooks_to_easily_integrate_livechat_with_your_CRM": "You can use webhooks to easily integrate livechat with your CRM.", "You_cant_leave_a_livechat_room_Please_use_the_close_button": "You can't leave a livechat room. Please, use the close button.", diff --git a/packages/rocketchat-i18n/i18n/es.i18n.json b/packages/rocketchat-i18n/i18n/es.i18n.json index 4697d953a0ffc..14ce85f78f742 100644 --- a/packages/rocketchat-i18n/i18n/es.i18n.json +++ b/packages/rocketchat-i18n/i18n/es.i18n.json @@ -2211,7 +2211,7 @@ "You_are_logged_in_as": "Ha iniciado sesión como", "You_are_not_authorized_to_view_this_page": "No está autorizado para ver esta página.", "You_can_change_a_different_avatar_too": "Puedes anular el avatar usado para publicar desde esta integración.", - "You_can_search_using_RegExp_eg": "Puede buscar utilizando RegExp. por ejemplo", + "You_can_search_using_RegExp_eg": "Puede buscar utilizando RegExp. por ejemplo /^text$/i", "You_can_use_an_emoji_as_avatar": "También puede utilizar un emoji como avatar.", "You_can_use_webhooks_to_easily_integrate_livechat_with_your_CRM": "Se puede utilizar para integrar fácilmente WebHooks chat directo con su CRM.", "You_cant_leave_a_livechat_room_Please_use_the_close_button": "No puedes salir de una sala de chat en vivo. Por favor, utilice el botón de cierre.", diff --git a/packages/rocketchat-i18n/i18n/fi.i18n.json b/packages/rocketchat-i18n/i18n/fi.i18n.json index 7cdaa6cd2f130..5840b12c30923 100644 --- a/packages/rocketchat-i18n/i18n/fi.i18n.json +++ b/packages/rocketchat-i18n/i18n/fi.i18n.json @@ -2211,7 +2211,7 @@ "You_are_logged_in_as": "Olet kirjautunut sisään käyttäjänä", "You_are_not_authorized_to_view_this_page": "Sinulla ei ole oikeuksia tarkastella tätä sivua.", "You_can_change_a_different_avatar_too": "Voit vaihtaa tähän eri avatarin tätä integraatiota varten", - "You_can_search_using_RegExp_eg": "Voit etsiä käyttämällä RegExp-lausekkeita esim.", + "You_can_search_using_RegExp_eg": "Voit etsiä käyttämällä RegExp-lausekkeita esim. /^text$/i", "You_can_use_an_emoji_as_avatar": "Voit käyttää myös emojia avatarina.", "You_can_use_webhooks_to_easily_integrate_livechat_with_your_CRM": "Voit käyttää webhookkeja integroidaksesi livechatin asiakastietojärjestelmäsi kanssa.", "You_cant_leave_a_livechat_room_Please_use_the_close_button": "Et voi lähteä livechat-huoneesta. Ole hyvä ja käytä \"sulje\"-painiketta.", diff --git a/packages/rocketchat-i18n/i18n/fr.i18n.json b/packages/rocketchat-i18n/i18n/fr.i18n.json index 67c8c4a165a04..a47773af62b1f 100644 --- a/packages/rocketchat-i18n/i18n/fr.i18n.json +++ b/packages/rocketchat-i18n/i18n/fr.i18n.json @@ -2211,7 +2211,7 @@ "You_are_logged_in_as": "Vous êtes connecté en tant que", "You_are_not_authorized_to_view_this_page": "Vous n'avez pas l'autorisation de voir cette page.", "You_can_change_a_different_avatar_too": "Vous pouvez écraser l'avatar utilisé pour publier depuis cette intégration.", - "You_can_search_using_RegExp_eg": "Vous pouvez rechercher en utilisant une expression régulière. par exemple", + "You_can_search_using_RegExp_eg": "Vous pouvez rechercher en utilisant une expression régulière. par exemple /^text$/i", "You_can_use_an_emoji_as_avatar": "Vous pouvez également utiliser une émoticone comme avatar.", "You_can_use_webhooks_to_easily_integrate_livechat_with_your_CRM": "Vous pouvez utiliser les webhooks pour intégrer facilement un chat en direct avec votre CRM.", "You_cant_leave_a_livechat_room_Please_use_the_close_button": "Vous ne pouvez pas quitter un chat en direct. S'il vous plaît, utilisez le bouton de fermeture.", diff --git a/packages/rocketchat-i18n/i18n/hr.i18n.json b/packages/rocketchat-i18n/i18n/hr.i18n.json index a28663758c511..ce7ea9dee8589 100644 --- a/packages/rocketchat-i18n/i18n/hr.i18n.json +++ b/packages/rocketchat-i18n/i18n/hr.i18n.json @@ -2211,7 +2211,7 @@ "You_are_logged_in_as": "Prijavljeni ste kao", "You_are_not_authorized_to_view_this_page": "Nemate dopuštenje za pregled ove stranice.", "You_can_change_a_different_avatar_too": "Možete zamijeniti avatara koji se koristi uz objavu iz ove integracije.", - "You_can_search_using_RegExp_eg": "Možete pretraživati pomoću regularnog izraza. npr", + "You_can_search_using_RegExp_eg": "Možete pretraživati pomoću regularnog izraza. npr /^text$/i", "You_can_use_an_emoji_as_avatar": "Također možete koristiti i emotikon kao avatar.", "You_can_use_webhooks_to_easily_integrate_livechat_with_your_CRM": "Možete koristiti webhooks lako integrirati LiveChat sa svojim CRM.", "You_cant_leave_a_livechat_room_Please_use_the_close_button": "Ne možete napustiti LiveChat sobu. Molim, koristite gumb za zatvaranje.", diff --git a/packages/rocketchat-i18n/i18n/hu.i18n.json b/packages/rocketchat-i18n/i18n/hu.i18n.json index eec7744ef9edc..c4d4117a98684 100644 --- a/packages/rocketchat-i18n/i18n/hu.i18n.json +++ b/packages/rocketchat-i18n/i18n/hu.i18n.json @@ -2211,7 +2211,7 @@ "You_are_logged_in_as": "Be van jelentkezve a", "You_are_not_authorized_to_view_this_page": "Ön nem jogosult az oldal megtekintésére.", "You_can_change_a_different_avatar_too": "Ön felülbírálhatja az avatar közzétevő ebből integráció.", - "You_can_search_using_RegExp_eg": "Reguláris kifejezés segítségével is kereshet, például:", + "You_can_search_using_RegExp_eg": "Reguláris kifejezés segítségével is kereshet, például: /^text$/i", "You_can_use_an_emoji_as_avatar": "Ön is használja a hangulatjel, mint egy avatar.", "You_can_use_webhooks_to_easily_integrate_livechat_with_your_CRM": "Használhatja webhooks könnyen integrálható LiveChat a CRM.", "You_cant_leave_a_livechat_room_Please_use_the_close_button": "Nem hagyhatja a LiveChat szobában. Kérjük, használja a bezárás gombot.", diff --git a/packages/rocketchat-i18n/i18n/id.i18n.json b/packages/rocketchat-i18n/i18n/id.i18n.json index 018424f597940..4d9dca1bab228 100644 --- a/packages/rocketchat-i18n/i18n/id.i18n.json +++ b/packages/rocketchat-i18n/i18n/id.i18n.json @@ -2211,7 +2211,7 @@ "You_are_logged_in_as": "Kamu masuk sebagai", "You_are_not_authorized_to_view_this_page": "Anda tidak memiliki izin untuk melihat halaman ini.", "You_can_change_a_different_avatar_too": "Anda dapat mengganti avatar digunakan untuk mengirim dari integrasi ini.", - "You_can_search_using_RegExp_eg": "Anda dapat mencari menggunakan RegExp. misalnya", + "You_can_search_using_RegExp_eg": "Anda dapat mencari menggunakan RegExp. misalnya /^text$/i", "You_can_use_an_emoji_as_avatar": "Anda juga dapat menggunakan emoji sebagai avatar.", "You_can_use_webhooks_to_easily_integrate_livechat_with_your_CRM": "Anda dapat menggunakan webhooks dengan mudah mengintegrasikan livechat dengan CRM Anda.", "You_cant_leave_a_livechat_room_Please_use_the_close_button": "Anda tidak bisa meninggalkan ruang livechat. Silakan, gunakan tombol tutup.", diff --git a/packages/rocketchat-i18n/i18n/it.i18n.json b/packages/rocketchat-i18n/i18n/it.i18n.json index 6a9b3e9b72f6d..7acd579a55133 100644 --- a/packages/rocketchat-i18n/i18n/it.i18n.json +++ b/packages/rocketchat-i18n/i18n/it.i18n.json @@ -2202,7 +2202,7 @@ "You_are_logged_in_as": "Sei loggato come", "You_are_not_authorized_to_view_this_page": "Non sei autorizzato a vedere questa pagina.", "You_can_change_a_different_avatar_too": "Puoi sovrascrivere l'avatar utilizzando questo post dalla integrazione.", - "You_can_search_using_RegExp_eg": "È possibile cercare usando espressioni regolari. Per esempio", + "You_can_search_using_RegExp_eg": "È possibile cercare usando espressioni regolari. Per esempio /^text$/i", "You_can_use_an_emoji_as_avatar": "È inoltre possibile utilizzare un emoji come avatar.", "You_can_use_webhooks_to_easily_integrate_livechat_with_your_CRM": "Puoi usare i webhooks per integrare facilmente Livechat con il tuo CRM.", "You_cant_leave_a_livechat_room_Please_use_the_close_button": "Non puoi lasciare il canale livechat. Si prega di usare il pulsante di chiusura.", diff --git a/packages/rocketchat-i18n/i18n/ja.i18n.json b/packages/rocketchat-i18n/i18n/ja.i18n.json index 202464efd8e8a..e4e03bb5caaf4 100644 --- a/packages/rocketchat-i18n/i18n/ja.i18n.json +++ b/packages/rocketchat-i18n/i18n/ja.i18n.json @@ -2211,7 +2211,7 @@ "You_are_logged_in_as": "次のユーザーとしてログインしています", "You_are_not_authorized_to_view_this_page": "このページを閲覧する権限がありません", "You_can_change_a_different_avatar_too": "この連携の投稿で使用されるアバターを上書きできます。", - "You_can_search_using_RegExp_eg": "正規表現による検索も可能です。例:", + "You_can_search_using_RegExp_eg": "正規表現による検索も可能です。例: /^text$/i", "You_can_use_an_emoji_as_avatar": "アバターとして絵文字を使用できます。", "You_can_use_webhooks_to_easily_integrate_livechat_with_your_CRM": "あなたは簡単にあなたのCRMとライブチャットを統合するウェブフックを使用することができます。", "You_cant_leave_a_livechat_room_Please_use_the_close_button": "あなたはライブチャットルームを残すことはできません。 、閉じるボタンを使用してください。", diff --git a/packages/rocketchat-i18n/i18n/ko.i18n.json b/packages/rocketchat-i18n/i18n/ko.i18n.json index bc432e70c704c..04be7de361c4f 100644 --- a/packages/rocketchat-i18n/i18n/ko.i18n.json +++ b/packages/rocketchat-i18n/i18n/ko.i18n.json @@ -2211,7 +2211,7 @@ "You_are_logged_in_as": "당신은로 로그인", "You_are_not_authorized_to_view_this_page": "당신은 이 페이지를 볼 수 있는 권한이 없습니다.", "You_can_change_a_different_avatar_too": "이 통합을 게시하는 데 사용되는 아바타를 대체 할 수 있습니다.", - "You_can_search_using_RegExp_eg": "당신은 정규식을 사용하여 검색 할 수 있습니다. 예를 들어,", + "You_can_search_using_RegExp_eg": "당신은 정규식을 사용하여 검색 할 수 있습니다. 예를 들어, /^text$/i", "You_can_use_an_emoji_as_avatar": "또한 아바타로 이모티콘을 사용할 수 있습니다.", "You_can_use_webhooks_to_easily_integrate_livechat_with_your_CRM": "당신은 쉽게 당신의 CRM과 LIVECHAT를 통합하는 webhooks를 사용할 수 있습니다.", "You_cant_leave_a_livechat_room_Please_use_the_close_button": "당신은 LIVECHAT 방을 떠날 수 없다. , 닫기 버튼을 사용하십시오.", diff --git a/packages/rocketchat-i18n/i18n/ku.i18n.json b/packages/rocketchat-i18n/i18n/ku.i18n.json index aac44655d081e..56b433b6c04c6 100644 --- a/packages/rocketchat-i18n/i18n/ku.i18n.json +++ b/packages/rocketchat-i18n/i18n/ku.i18n.json @@ -2211,7 +2211,7 @@ "You_are_logged_in_as": "Tu li ku têkevî", "You_are_not_authorized_to_view_this_page": "Tu ji bo dîtina vê rûpelê destûr ne.", "You_can_change_a_different_avatar_too": "Hûn dikarin ji avatar bikaranîn to post ji vê întegrasyonê override.", - "You_can_search_using_RegExp_eg": "Hûn dikarin bi bikaranîna RegExp bigere. eg", + "You_can_search_using_RegExp_eg": "Hûn dikarin bi bikaranîna RegExp bigere. eg /^text$/i", "You_can_use_an_emoji_as_avatar": "Tu dikarî an emoji wek avatar bi kar tînin.", "You_can_use_webhooks_to_easily_integrate_livechat_with_your_CRM": "Tu dikarî webhooks bi hêsanî entegre livechat bi CRM xwe bi kar tînin.", "You_cant_leave_a_livechat_room_Please_use_the_close_button": "Tu dikarî li odeya livechat bihêle ne. Ji kerema xwe, bi kar tînin li bişkojka bigire.", diff --git a/packages/rocketchat-i18n/i18n/lo.i18n.json b/packages/rocketchat-i18n/i18n/lo.i18n.json index 1970d187c13c7..ea50db0277e1c 100644 --- a/packages/rocketchat-i18n/i18n/lo.i18n.json +++ b/packages/rocketchat-i18n/i18n/lo.i18n.json @@ -2211,7 +2211,7 @@ "You_are_logged_in_as": "ເຈົ້າຍັງບໍ່ໄດ້ເຂົ້າສູ່ລະບົບໃນຖານະ", "You_are_not_authorized_to_view_this_page": "ເຈົ້າຍັງບໍ່ໄດ້ອະນຸຍາດໃຫ້ເບິ່ງຫນ້ານີ້.", "You_can_change_a_different_avatar_too": "ທ່ານສາມາດແທນ avatar ນໍາໃຊ້ເພື່ອສະຈາກການເຊື່ອມໂຍງນີ້.", - "You_can_search_using_RegExp_eg": "ທ່ານສາມາດຄົ້ນຫາໂດຍໃຊ້ RegExp. ຕົວຢ່າງ:", + "You_can_search_using_RegExp_eg": "ທ່ານສາມາດຄົ້ນຫາໂດຍໃຊ້ RegExp. ຕົວຢ່າງ: /^text$/i", "You_can_use_an_emoji_as_avatar": "ນອກນັ້ນທ່ານຍັງສາມາດນໍາໃຊ້ສັນຍາລັກຕ່າງໆເປັນ avatar.", "You_can_use_webhooks_to_easily_integrate_livechat_with_your_CRM": "ທ່ານສາມາດນໍາໃຊ້ webhooks ໄດ້ຢ່າງງ່າຍດາຍປະ LiveChat ກັບ CRM ຂອງທ່ານ.", "You_cant_leave_a_livechat_room_Please_use_the_close_button": "ທ່ານບໍ່ສາມາດອອກຈາກຫ້ອງ LiveChat ເປັນ. ກະລຸນາ, ການນໍາໃຊ້ປຸ່ມປິດ.", diff --git a/packages/rocketchat-i18n/i18n/ms-MY.i18n.json b/packages/rocketchat-i18n/i18n/ms-MY.i18n.json index d6297c47279b6..ee1c0bc699da4 100644 --- a/packages/rocketchat-i18n/i18n/ms-MY.i18n.json +++ b/packages/rocketchat-i18n/i18n/ms-MY.i18n.json @@ -2211,7 +2211,7 @@ "You_are_logged_in_as": "Anda log masuk sebagai", "You_are_not_authorized_to_view_this_page": "Anda tiada kebenaran untuk melihat halaman ini.", "You_can_change_a_different_avatar_too": "Anda boleh mengatasi avatar yang digunakan untuk hantar daripada integrasi ini.", - "You_can_search_using_RegExp_eg": "Anda boleh mencari menggunakan RegExp. contohnya", + "You_can_search_using_RegExp_eg": "Anda boleh mencari menggunakan RegExp. contohnya /^text$/i", "You_can_use_an_emoji_as_avatar": "Anda juga boleh menggunakan emoji sebagai avatar.", "You_can_use_webhooks_to_easily_integrate_livechat_with_your_CRM": "Anda boleh menggunakan webhooks dengan mudah mengintegrasikan LiveChat dengan CRM anda.", "You_cant_leave_a_livechat_room_Please_use_the_close_button": "Anda tidak boleh meninggalkan bilik LiveChat. Sila, gunakan butang rapat.", diff --git a/packages/rocketchat-i18n/i18n/nl.i18n.json b/packages/rocketchat-i18n/i18n/nl.i18n.json index c4674b73d25d1..1012dcb6c2389 100644 --- a/packages/rocketchat-i18n/i18n/nl.i18n.json +++ b/packages/rocketchat-i18n/i18n/nl.i18n.json @@ -2211,7 +2211,7 @@ "You_are_logged_in_as": "Je bent ingelogd als", "You_are_not_authorized_to_view_this_page": "U bent niet bevoegd om deze pagina te bekijken.", "You_can_change_a_different_avatar_too": "U kunt de afbeelding die bij berichten gebruikt wordt vervangen.", - "You_can_search_using_RegExp_eg": "U kunt zoeken met behulp van RegExp. bv", + "You_can_search_using_RegExp_eg": "U kunt zoeken met behulp van RegExp. bv /^text$/i", "You_can_use_an_emoji_as_avatar": "U kunt ook een emoji gebruiken als een afbeelding.", "You_can_use_webhooks_to_easily_integrate_livechat_with_your_CRM": "U kunt Webhooks gebruiken om eenvoudig livechat integreren met uw CRM.", "You_cant_leave_a_livechat_room_Please_use_the_close_button": "U kunt een livechat kamer niet verlaten. Gelieve gebruik te maken van de sluitknop.", diff --git a/packages/rocketchat-i18n/i18n/pl.i18n.json b/packages/rocketchat-i18n/i18n/pl.i18n.json index a10150792749c..69684d78b1938 100644 --- a/packages/rocketchat-i18n/i18n/pl.i18n.json +++ b/packages/rocketchat-i18n/i18n/pl.i18n.json @@ -2211,7 +2211,7 @@ "You_are_logged_in_as": "Jesteś zalogowany jako", "You_are_not_authorized_to_view_this_page": "Nie masz uprawnień, aby zobaczyć tę stronę.", "You_can_change_a_different_avatar_too": "Można zastąpić awatar używany do wysyłania z tej integracji.", - "You_can_search_using_RegExp_eg": "Można wyszukiwać za pomocą wyrażeń regularnych, np.", + "You_can_search_using_RegExp_eg": "Można wyszukiwać za pomocą wyrażeń regularnych, np. /^text$/i", "You_can_use_an_emoji_as_avatar": "Można również używać emotikony jako awatar.", "You_can_use_webhooks_to_easily_integrate_livechat_with_your_CRM": "Można użyć webhooks na łatwą integrację LiveChat z CRM.", "You_cant_leave_a_livechat_room_Please_use_the_close_button": "Nie można opuścić salę LiveChat. Proszę użyć przycisku zamykania.", diff --git a/packages/rocketchat-i18n/i18n/pt-BR.i18n.json b/packages/rocketchat-i18n/i18n/pt-BR.i18n.json index 5a8e1f56bdd9d..f428c9529e70b 100644 --- a/packages/rocketchat-i18n/i18n/pt-BR.i18n.json +++ b/packages/rocketchat-i18n/i18n/pt-BR.i18n.json @@ -2213,7 +2213,7 @@ "You_are_logged_in_as": "Vocês está logado como", "You_are_not_authorized_to_view_this_page": "Você não possui permissão para visualizar esta página.", "You_can_change_a_different_avatar_too": "Você pode substituir o avatar usado para enviar a partir desta integração.", - "You_can_search_using_RegExp_eg": "Você pode pesquisar usando expressões regulares, por exemplo:", + "You_can_search_using_RegExp_eg": "Você pode pesquisar usando expressões regulares, por exemplo: /^text$/i", "You_can_use_an_emoji_as_avatar": "Você também pode usar um emoji como um avatar.", "You_can_use_webhooks_to_easily_integrate_livechat_with_your_CRM": "Você pode usar webhooks para integrar facilmente o livechat com seu CRM.", "You_cant_leave_a_livechat_room_Please_use_the_close_button": "Você não pode sair de uma sala de livechat. Por favor, use o botão de fechar sala.", diff --git a/packages/rocketchat-i18n/i18n/pt.i18n.json b/packages/rocketchat-i18n/i18n/pt.i18n.json index c11a8b257c8ab..8dc62565a62ad 100644 --- a/packages/rocketchat-i18n/i18n/pt.i18n.json +++ b/packages/rocketchat-i18n/i18n/pt.i18n.json @@ -2213,7 +2213,7 @@ "You_are_logged_in_as": "Vocês está logado como", "You_are_not_authorized_to_view_this_page": "Você não possui permissão para visualizar esta página.", "You_can_change_a_different_avatar_too": "Você pode substituir o avatar usado para enviar a partir desta integração.", - "You_can_search_using_RegExp_eg": "Você pode pesquisar usando expressões regulares, por exemplo:", + "You_can_search_using_RegExp_eg": "Você pode pesquisar usando expressões regulares, por exemplo: /^text$/i", "You_can_use_an_emoji_as_avatar": "Você também pode usar um emoji como um avatar.", "You_can_use_webhooks_to_easily_integrate_livechat_with_your_CRM": "Você pode usar webhooks para integrar facilmente o livechat com seu CRM.", "You_cant_leave_a_livechat_room_Please_use_the_close_button": "Você não pode sair de uma sala de livechat. Por favor, use o botão de fechar sala.", diff --git a/packages/rocketchat-i18n/i18n/ro.i18n.json b/packages/rocketchat-i18n/i18n/ro.i18n.json index c0d545a1315fc..8d2bfecb5931e 100644 --- a/packages/rocketchat-i18n/i18n/ro.i18n.json +++ b/packages/rocketchat-i18n/i18n/ro.i18n.json @@ -2211,7 +2211,7 @@ "You_are_logged_in_as": "Sunteți autentificat ca ", "You_are_not_authorized_to_view_this_page": "Nu sunteți autorizat pentru a vizualiza această pagină.", "You_can_change_a_different_avatar_too": "Puteți înlocui avatarul folosit pentru a posta din această integrare.", - "You_can_search_using_RegExp_eg": "Puteți căuta utilizând regexp. de exemplu", + "You_can_search_using_RegExp_eg": "Puteți căuta utilizând regexp. de exemplu /^text$/i", "You_can_use_an_emoji_as_avatar": "Puteți utiliza un emoji ca avatar", "You_can_use_webhooks_to_easily_integrate_livechat_with_your_CRM": "Aveți posibilitatea să utilizați webhooks pentru a se integra cu ușurință LiveChat cu CRM.", "You_cant_leave_a_livechat_room_Please_use_the_close_button": "Nu poți lăsa o cameră LiveChat. Vă rugăm să utilizați butonul închidere.", diff --git a/packages/rocketchat-i18n/i18n/ru.i18n.json b/packages/rocketchat-i18n/i18n/ru.i18n.json index 4eef88b86f4fd..7a9a61aaffc87 100644 --- a/packages/rocketchat-i18n/i18n/ru.i18n.json +++ b/packages/rocketchat-i18n/i18n/ru.i18n.json @@ -2211,7 +2211,7 @@ "You_are_logged_in_as": "Вы вошли как", "You_are_not_authorized_to_view_this_page": "Недостаточно прав для просмотра страницы.", "You_can_change_a_different_avatar_too": "Вы можете заменить аватар, используемый в интеграции.", - "You_can_search_using_RegExp_eg": "Искать можно используя RegExp, например:", + "You_can_search_using_RegExp_eg": "Искать можно используя RegExp, например: /^text$/i", "You_can_use_an_emoji_as_avatar": "Вы также можете использовать эмодзи в качестве аватара.", "You_can_use_webhooks_to_easily_integrate_livechat_with_your_CRM": "Вы можете использовать webhooks для легкой интеграции Livechat с вашей CRM.", "You_cant_leave_a_livechat_room_Please_use_the_close_button": "Вы не можете покинуть Livechat комнату. Пожалуйста, используйте кнопку закрыть.", diff --git a/packages/rocketchat-i18n/i18n/sq.i18n.json b/packages/rocketchat-i18n/i18n/sq.i18n.json index eaa4857b4850f..696531a2cfffc 100644 --- a/packages/rocketchat-i18n/i18n/sq.i18n.json +++ b/packages/rocketchat-i18n/i18n/sq.i18n.json @@ -2211,7 +2211,7 @@ "You_are_logged_in_as": "Ju jeni regjistruar si", "You_are_not_authorized_to_view_this_page": "Ju nuk jeni i autorizuar për të parë këtë faqe.", "You_can_change_a_different_avatar_too": "Ju mund të pranoj avatar përdorur për të postuar nga ky integrim.", - "You_can_search_using_RegExp_eg": "Ju mund të kërkoni duke përdorur RegExp. p.sh.", + "You_can_search_using_RegExp_eg": "Ju mund të kërkoni duke përdorur RegExp. p.sh. /^text$/i", "You_can_use_an_emoji_as_avatar": "Ju gjithashtu mund të përdorni një emoji si një avatar.", "You_can_use_webhooks_to_easily_integrate_livechat_with_your_CRM": "Ju mund të përdorni webhooks të lehtë të integrohen LiveChat me CRM tuaj.", "You_cant_leave_a_livechat_room_Please_use_the_close_button": "Ju nuk mund të lënë një dhomë LiveChat. Ju lutem, përdorni butonin ngushtë.", diff --git a/packages/rocketchat-i18n/i18n/sr.i18n.json b/packages/rocketchat-i18n/i18n/sr.i18n.json index 3e9e44e3e151b..e39381c917a0a 100644 --- a/packages/rocketchat-i18n/i18n/sr.i18n.json +++ b/packages/rocketchat-i18n/i18n/sr.i18n.json @@ -2211,7 +2211,7 @@ "You_are_logged_in_as": "Пријављени сте као", "You_are_not_authorized_to_view_this_page": "Нисте ауторизовани да видите ову страницу.", "You_can_change_a_different_avatar_too": "Можете премостити аватар користити за постављање из ове интеграције.", - "You_can_search_using_RegExp_eg": "Можете да претражујете помоћу регекп. на пример", + "You_can_search_using_RegExp_eg": "Можете да претражујете помоћу регекп. на пример /^text$/i", "You_can_use_an_emoji_as_avatar": "Такође можете да користите емотикона као аватар.", "You_can_use_webhooks_to_easily_integrate_livechat_with_your_CRM": "Можете користити вебхоокс да лако интегришу ливеЦхат са ЦРМ.", "You_cant_leave_a_livechat_room_Please_use_the_close_button": "Не можете оставити ЛивеЦхат собу. Молимо вас, користите дугме за затварање.", diff --git a/packages/rocketchat-i18n/i18n/sv.i18n.json b/packages/rocketchat-i18n/i18n/sv.i18n.json index 75d4a4241fd3b..7ef9149117e37 100644 --- a/packages/rocketchat-i18n/i18n/sv.i18n.json +++ b/packages/rocketchat-i18n/i18n/sv.i18n.json @@ -2212,7 +2212,7 @@ "You_are_logged_in_as": "Du är inloggad som", "You_are_not_authorized_to_view_this_page": "Du är inte auktoriserad att se denna sida.", "You_can_change_a_different_avatar_too": "Du kan åsidosätta avatar som används för att lägga upp från denna integration.\n", - "You_can_search_using_RegExp_eg": "Du kan söka med RegExp. t.ex", + "You_can_search_using_RegExp_eg": "Du kan söka med RegExp. t.ex /^text$/i", "You_can_use_an_emoji_as_avatar": "Du kan också använda en emoji som avatar.", "You_can_use_webhooks_to_easily_integrate_livechat_with_your_CRM": "Du kan använda webhooks att enkelt integrera livechat med CRM.", "You_cant_leave_a_livechat_room_Please_use_the_close_button": "Du kan inte lämna en livechat rum. Vänligen använd stängknappen.", diff --git a/packages/rocketchat-i18n/i18n/ta-IN.i18n.json b/packages/rocketchat-i18n/i18n/ta-IN.i18n.json index cc0896946f7db..f824415a764d7 100644 --- a/packages/rocketchat-i18n/i18n/ta-IN.i18n.json +++ b/packages/rocketchat-i18n/i18n/ta-IN.i18n.json @@ -2211,7 +2211,7 @@ "You_are_logged_in_as": "நீங்கள் என பதிவு", "You_are_not_authorized_to_view_this_page": "இந்த பக்கத்தை பார்க்க உங்களுக்கு அதிகாரம் இல்லை.", "You_can_change_a_different_avatar_too": "இந்த ஒருங்கிணைப்பு இருந்து பதிவு செய்ய பயன்படுத்தப்படும் சின்னம் புறக்கணிக்க முடியாது.", - "You_can_search_using_RegExp_eg": "நீங்கள் RegExp பயன்படுத்தி தேடலாம். எ.கா.", + "You_can_search_using_RegExp_eg": "நீங்கள் RegExp பயன்படுத்தி தேடலாம். எ.கா. /^text$/i", "You_can_use_an_emoji_as_avatar": "நீங்கள் ஒரு அவதாரமாக ஈமோஜியை பயன்படுத்த முடியும்.", "You_can_use_webhooks_to_easily_integrate_livechat_with_your_CRM": "நீங்கள் எளிதாக உங்கள் CRM உடன் livechat ஒருங்கிணைக்க webhooks பயன்படுத்த முடியும்.", "You_cant_leave_a_livechat_room_Please_use_the_close_button": "நீங்கள் ஒரு livechat அறையில் விட்டு போக முடியாது. தயவு செய்து, நெருங்கிய பொத்தானை பயன்படுத்த.", diff --git a/packages/rocketchat-i18n/i18n/tr.i18n.json b/packages/rocketchat-i18n/i18n/tr.i18n.json index 50ed9d1be42ee..5157b62ffd2d6 100644 --- a/packages/rocketchat-i18n/i18n/tr.i18n.json +++ b/packages/rocketchat-i18n/i18n/tr.i18n.json @@ -2211,7 +2211,7 @@ "You_are_logged_in_as": "Sen olarak oturum", "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_search_using_RegExp_eg": "Sen RegExp kullanarak arama yapabilirsiniz. örneğin", + "You_can_search_using_RegExp_eg": "Sen RegExp kullanarak arama yapabilirsiniz. örneğin /^text$/i", "You_can_use_an_emoji_as_avatar": "Ayrıca bir avatar olarak emoji kullanabilirsiniz.", "You_can_use_webhooks_to_easily_integrate_livechat_with_your_CRM": "Kolayca CRM ile LiveChat entegre etmek için webhooks kullanabilirsiniz.", "You_cant_leave_a_livechat_room_Please_use_the_close_button": "Bir livechat odadan olamaz. , Kapat düğmesini kullanın.", diff --git a/packages/rocketchat-i18n/i18n/uk.i18n.json b/packages/rocketchat-i18n/i18n/uk.i18n.json index fb94c09bcfbf0..1165dae53c0f2 100644 --- a/packages/rocketchat-i18n/i18n/uk.i18n.json +++ b/packages/rocketchat-i18n/i18n/uk.i18n.json @@ -2211,7 +2211,7 @@ "You_are_logged_in_as": "Ви увійшли як", "You_are_not_authorized_to_view_this_page": "Ви не авторизовані для перегляду цієї сторінки.", "You_can_change_a_different_avatar_too": "Ви можете змінити аватар використовуваний для відправки від цієї інтеграції.", - "You_can_search_using_RegExp_eg": "Ви можете здійснювати пошук, використовуючи RegExp. наприклад,", + "You_can_search_using_RegExp_eg": "Ви можете здійснювати пошук, використовуючи RegExp. наприклад, /^text$/i", "You_can_use_an_emoji_as_avatar": "Ви можете також використовувати смайлик в якості аватара.", "You_can_use_webhooks_to_easily_integrate_livechat_with_your_CRM": "Ви можете використовувати webhooks легко інтегрувати з LiveChat CRM.", "You_cant_leave_a_livechat_room_Please_use_the_close_button": "Ви не можете залишити Livechat кімнату. Будь ласка, використовуйте кнопку закриття.", diff --git a/packages/rocketchat-i18n/i18n/zh-TW.i18n.json b/packages/rocketchat-i18n/i18n/zh-TW.i18n.json index 532b037045a09..7a2d9e27a2314 100644 --- a/packages/rocketchat-i18n/i18n/zh-TW.i18n.json +++ b/packages/rocketchat-i18n/i18n/zh-TW.i18n.json @@ -2211,7 +2211,7 @@ "You_are_logged_in_as": "您登錄為", "You_are_not_authorized_to_view_this_page": "您無權查看此頁。", "You_can_change_a_different_avatar_too": "您可以覆蓋從這次整合後的分身。", - "You_can_search_using_RegExp_eg": "您可以搜索使用正則表達式。例如", + "You_can_search_using_RegExp_eg": "您可以搜索使用正則表達式。例如 /^text$/i", "You_can_use_an_emoji_as_avatar": "您還可以使用表情符號作為頭像。", "You_can_use_webhooks_to_easily_integrate_livechat_with_your_CRM": "您可以使用網絡掛接輕鬆ID登錄你的CRM集成。", "You_cant_leave_a_livechat_room_Please_use_the_close_button": "你不能離開房間ID登錄。請使用關閉按鈕。", diff --git a/packages/rocketchat-i18n/i18n/zh.i18n.json b/packages/rocketchat-i18n/i18n/zh.i18n.json index 446f244badaa6..48359e440ab1c 100644 --- a/packages/rocketchat-i18n/i18n/zh.i18n.json +++ b/packages/rocketchat-i18n/i18n/zh.i18n.json @@ -2211,7 +2211,7 @@ "You_are_logged_in_as": "您已登录为", "You_are_not_authorized_to_view_this_page": "您无权查看此页面。", "You_can_change_a_different_avatar_too": "您可以覆盖从这次整合后的分身。", - "You_can_search_using_RegExp_eg": "您可以使用正则表达式搜索。例如:", + "You_can_search_using_RegExp_eg": "您可以使用正则表达式搜索。例如:/^text$/i", "You_can_use_an_emoji_as_avatar": "您也可以使用表情符号作为头像。", "You_can_use_webhooks_to_easily_integrate_livechat_with_your_CRM": "您可以使用网页钩子方便地将在线聊天集成到您的客户关系管理系统。", "You_cant_leave_a_livechat_room_Please_use_the_close_button": "你不能离开房间。请使用关闭按钮。", diff --git a/packages/rocketchat-lib/client/defaultTabBars.js b/packages/rocketchat-lib/client/defaultTabBars.js index 5d8cf7c0f91b5..a323397a78738 100644 --- a/packages/rocketchat-lib/client/defaultTabBars.js +++ b/packages/rocketchat-lib/client/defaultTabBars.js @@ -1,9 +1,9 @@ RocketChat.TabBar.addButton({ groups: ['channel', 'group', 'direct'], - id: 'message-search', + id: 'rocket-search', i18nTitle: 'Search_Messages', icon: 'magnifier', - template: 'messageSearch', + template: 'RocketSearch', order: 1 }); diff --git a/packages/rocketchat-lib/server/startup/settings.js b/packages/rocketchat-lib/server/startup/settings.js index fb8b9079508af..a471b8bda3cfa 100644 --- a/packages/rocketchat-lib/server/startup/settings.js +++ b/packages/rocketchat-lib/server/startup/settings.js @@ -1369,12 +1369,6 @@ RocketChat.settings.addGroup('Message', function() { 'public': true }); - this.add('Message_GlobalSearch', false, { - type: 'boolean', - 'public': true, - alert: 'This feature is currently in beta and could decrease the application performance! Please report bugs to github.com/RocketChat/Rocket.Chat/issues' - }); - this.add('Message_ErasureType', 'Delete', { type: 'select', 'public': true, diff --git a/packages/rocketchat-livechat/.app/package-lock.json b/packages/rocketchat-livechat/.app/package-lock.json new file mode 100644 index 0000000000000..ed59acf2f209a --- /dev/null +++ b/packages/rocketchat-livechat/.app/package-lock.json @@ -0,0 +1,874 @@ +{ + "name": "rocketchat-livechat", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@babel/runtime": { + "version": "7.0.0-beta.40", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.0.0-beta.40.tgz", + "integrity": "sha512-vIM68NUCWauZJTFoVUG1lggva1I8FLB9zFKwWG7Xjin4FkHpEKJv2y4x1DGVPVt93S5/zHSBj1bXYEuxOkFGzg==", + "requires": { + "core-js": "2.5.3", + "regenerator-runtime": "0.11.1" + } + }, + "abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" + }, + "ajv": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", + "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", + "requires": { + "co": "4.6.0", + "fast-deep-equal": "1.1.0", + "fast-json-stable-stringify": "2.0.0", + "json-schema-traverse": "0.3.1" + } + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + }, + "aproba": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" + }, + "are-we-there-yet": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.4.tgz", + "integrity": "sha1-u13KOCu5TwXhUZQ3PRb9O6HKEQ0=", + "requires": { + "delegates": "1.0.0", + "readable-stream": "2.3.5" + } + }, + "asn1": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", + "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=" + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, + "autolinker": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/autolinker/-/autolinker-1.6.2.tgz", + "integrity": "sha512-IKLGtYFb3jzGTtgCpb4bm//1sXmmmgmr0msKshhYoc7EsWmLCFvuyxLcEIfcZ5gbCgZGXrnXkOkcBblOFEnlog==" + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" + }, + "aws4": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz", + "integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4=" + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + }, + "bcrypt": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-1.0.3.tgz", + "integrity": "sha512-pRyDdo73C8Nim3jwFJ7DWe3TZCgwDfWZ6nHS5LSdU77kWbj1frruvdndP02AOavtD4y8v6Fp2dolbHgp4SDrfg==", + "requires": { + "nan": "2.6.2", + "node-pre-gyp": "0.6.36" + } + }, + "bcrypt-pbkdf": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz", + "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=", + "optional": true, + "requires": { + "tweetnacl": "0.14.5" + } + }, + "block-stream": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz", + "integrity": "sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=", + "requires": { + "inherits": "2.0.3" + } + }, + "boom": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/boom/-/boom-4.3.1.tgz", + "integrity": "sha1-T4owBctKfjiJ90kDD9JbluAdLjE=", + "requires": { + "hoek": "4.2.1" + } + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "requires": { + "balanced-match": "1.0.0", + "concat-map": "0.0.1" + } + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" + }, + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" + }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" + }, + "combined-stream": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz", + "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=", + "requires": { + "delayed-stream": "1.0.0" + } + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" + }, + "core-js": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.3.tgz", + "integrity": "sha1-isw4NFgk8W2DZbfJtCWRaOjtYD4=" + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "cryptiles": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-3.1.2.tgz", + "integrity": "sha1-qJ+7Ig9c4l7FboxKqKT9e1sNKf4=", + "requires": { + "boom": "5.2.0" + }, + "dependencies": { + "boom": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/boom/-/boom-5.2.0.tgz", + "integrity": "sha512-Z5BTk6ZRe4tXXQlkqftmsAUANpXmuwlsF5Oov8ThoMbQRzdGTA1ngYRW160GexgOgjsFOKJz0LYhoNi+2AMBUw==", + "requires": { + "hoek": "4.2.1" + } + } + } + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "requires": { + "assert-plus": "1.0.0" + } + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "deep-extend": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.4.2.tgz", + "integrity": "sha1-SLaZwn4zS/ifEIkr5DL25MfTSn8=" + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + }, + "delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" + }, + "ecc-jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", + "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", + "optional": true, + "requires": { + "jsbn": "0.1.1" + } + }, + "extend": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", + "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=" + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" + }, + "fast-deep-equal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", + "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=" + }, + "fast-json-stable-stringify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" + }, + "form-data": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.2.tgz", + "integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=", + "requires": { + "asynckit": "0.4.0", + "combined-stream": "1.0.6", + "mime-types": "2.1.18" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + }, + "fstream": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.11.tgz", + "integrity": "sha1-XB+x8RdHcRTwYyoOtLcbPLD9MXE=", + "requires": { + "graceful-fs": "4.1.11", + "inherits": "2.0.3", + "mkdirp": "0.5.1", + "rimraf": "2.6.2" + } + }, + "fstream-ignore": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/fstream-ignore/-/fstream-ignore-1.0.5.tgz", + "integrity": "sha1-nDHa40dnAY/h0kmyTa2mfQktoQU=", + "requires": { + "fstream": "1.0.11", + "inherits": "2.0.3", + "minimatch": "3.0.4" + } + }, + "gauge": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", + "requires": { + "aproba": "1.2.0", + "console-control-strings": "1.1.0", + "has-unicode": "2.0.1", + "object-assign": "4.1.1", + "signal-exit": "3.0.2", + "string-width": "1.0.2", + "strip-ansi": "3.0.1", + "wide-align": "1.1.2" + } + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "requires": { + "assert-plus": "1.0.0" + } + }, + "glob": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "requires": { + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + } + }, + "graceful-fs": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", + "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=" + }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" + }, + "har-validator": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz", + "integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=", + "requires": { + "ajv": "5.5.2", + "har-schema": "2.0.0" + } + }, + "has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" + }, + "hawk": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/hawk/-/hawk-6.0.2.tgz", + "integrity": "sha512-miowhl2+U7Qle4vdLqDdPt9m09K6yZhkLDTWGoUiUzrQCn+mHHSmfJgAyGaLRZbPmTqfFFjRV1QWCW0VWUJBbQ==", + "requires": { + "boom": "4.3.1", + "cryptiles": "3.1.2", + "hoek": "4.2.1", + "sntp": "2.1.0" + } + }, + "hoek": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-4.2.1.tgz", + "integrity": "sha512-QLg82fGkfnJ/4iy1xZ81/9SIJiq1NGFUMGs6ParyjBZr6jW2Ufj/snDqTHixNlHdPNwN2RLVD0Pi3igeK9+JfA==" + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "requires": { + "assert-plus": "1.0.0", + "jsprim": "1.4.1", + "sshpk": "1.14.1" + } + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "requires": { + "once": "1.4.0", + "wrappy": "1.0.2" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "ini": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "requires": { + "number-is-nan": "1.0.1" + } + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" + }, + "jquery": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.3.1.tgz", + "integrity": "sha512-Ubldcmxp5np52/ENotGxlLe6aGMvmF4R8S6tZjsP6Knsaxd/xp3Zrh50cG93lR6nPXyUFwzN3ZSOQI0wRJNdGg==" + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "optional": true + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" + }, + "json-schema-traverse": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", + "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=" + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" + }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "mime-db": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", + "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==" + }, + "mime-types": { + "version": "2.1.18", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", + "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", + "requires": { + "mime-db": "1.33.0" + } + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "requires": { + "brace-expansion": "1.1.11" + } + }, + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "requires": { + "minimist": "0.0.8" + } + }, + "moment": { + "version": "2.21.0", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.21.0.tgz", + "integrity": "sha512-TCZ36BjURTeFTM/CwRcViQlfkMvL1/vFISuNLO5GkcVm1+QHfbSiNqZuWeMFjj1/3+uAjXswgRk30j1kkLYJBQ==" + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "nan": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.6.2.tgz", + "integrity": "sha1-5P805slf37WuzAjeZZb0NgWn20U=" + }, + "node-pre-gyp": { + "version": "0.6.36", + "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.6.36.tgz", + "integrity": "sha1-22BBEst04NR3VU6bUFsXq936t4Y=", + "requires": { + "mkdirp": "0.5.1", + "nopt": "4.0.1", + "npmlog": "4.1.2", + "rc": "1.2.6", + "request": "2.85.0", + "rimraf": "2.6.2", + "semver": "5.5.0", + "tar": "2.2.1", + "tar-pack": "3.4.1" + } + }, + "nopt": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz", + "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", + "requires": { + "abbrev": "1.1.1", + "osenv": "0.1.5" + } + }, + "npmlog": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", + "requires": { + "are-we-there-yet": "1.1.4", + "console-control-strings": "1.1.0", + "gauge": "2.7.4", + "set-blocking": "2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" + }, + "oauth-sign": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", + "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=" + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1.0.2" + } + }, + "os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" + }, + "osenv": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", + "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", + "requires": { + "os-homedir": "1.0.2", + "os-tmpdir": "1.0.2" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" + }, + "process-nextick-args": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" + }, + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" + }, + "qs": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", + "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==" + }, + "rc": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.6.tgz", + "integrity": "sha1-6xiYnG1PTxYsOZ953dKfODVWgJI=", + "requires": { + "deep-extend": "0.4.2", + "ini": "1.3.5", + "minimist": "1.2.0", + "strip-json-comments": "2.0.1" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" + } + } + }, + "readable-stream": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.5.tgz", + "integrity": "sha512-tK0yDhrkygt/knjowCUiWP9YdV7c5R+8cR0r/kt9ZhBU906Fs6RpQJCEilamRJj1Nx2rWI6LkW9gKqjTkshhEw==", + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "2.0.0", + "safe-buffer": "5.1.1", + "string_decoder": "1.0.3", + "util-deprecate": "1.0.2" + } + }, + "regenerator-runtime": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==" + }, + "request": { + "version": "2.85.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.85.0.tgz", + "integrity": "sha512-8H7Ehijd4js+s6wuVPLjwORxD4zeuyjYugprdOXlPSqaApmL/QOy+EB/beICHVCHkGMKNh5rvihb5ov+IDw4mg==", + "requires": { + "aws-sign2": "0.7.0", + "aws4": "1.6.0", + "caseless": "0.12.0", + "combined-stream": "1.0.6", + "extend": "3.0.1", + "forever-agent": "0.6.1", + "form-data": "2.3.2", + "har-validator": "5.0.3", + "hawk": "6.0.2", + "http-signature": "1.2.0", + "is-typedarray": "1.0.0", + "isstream": "0.1.2", + "json-stringify-safe": "5.0.1", + "mime-types": "2.1.18", + "oauth-sign": "0.8.2", + "performance-now": "2.1.0", + "qs": "6.5.1", + "safe-buffer": "5.1.1", + "stringstream": "0.0.5", + "tough-cookie": "2.3.4", + "tunnel-agent": "0.6.0", + "uuid": "3.2.1" + } + }, + "rimraf": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", + "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", + "requires": { + "glob": "7.1.2" + } + }, + "safe-buffer": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", + "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" + }, + "semver": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", + "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==" + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" + }, + "signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" + }, + "sntp": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/sntp/-/sntp-2.1.0.tgz", + "integrity": "sha512-FL1b58BDrqS3A11lJ0zEdnJ3UOKqVxawAkF3k7F0CVN7VQ34aZrV+G8BZ1WC9ZL7NyrwsW0oviwsWDgRuVYtJg==", + "requires": { + "hoek": "4.2.1" + } + }, + "sprintf-js": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.1.tgz", + "integrity": "sha1-Nr54Mgr+WAH2zqPueLblqrlA6gw=" + }, + "sshpk": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.14.1.tgz", + "integrity": "sha1-Ew9Zde3a2WPx1W+SuaxsUfqfg+s=", + "requires": { + "asn1": "0.2.3", + "assert-plus": "1.0.0", + "bcrypt-pbkdf": "1.0.1", + "dashdash": "1.14.1", + "ecc-jsbn": "0.1.1", + "getpass": "0.1.7", + "jsbn": "0.1.1", + "tweetnacl": "0.14.5" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "requires": { + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" + } + }, + "string_decoder": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", + "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", + "requires": { + "safe-buffer": "5.1.1" + } + }, + "stringstream": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", + "integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg=" + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "requires": { + "ansi-regex": "2.1.1" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" + }, + "tar": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz", + "integrity": "sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE=", + "requires": { + "block-stream": "0.0.9", + "fstream": "1.0.11", + "inherits": "2.0.3" + } + }, + "tar-pack": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/tar-pack/-/tar-pack-3.4.1.tgz", + "integrity": "sha512-PPRybI9+jM5tjtCbN2cxmmRU7YmqT3Zv/UDy48tAh2XRkLa9bAORtSWLkVc13+GJF+cdTh1yEnHEk3cpTaL5Kg==", + "requires": { + "debug": "2.6.9", + "fstream": "1.0.11", + "fstream-ignore": "1.0.5", + "once": "1.4.0", + "readable-stream": "2.3.5", + "rimraf": "2.6.2", + "tar": "2.2.1", + "uid-number": "0.0.6" + } + }, + "toastr": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/toastr/-/toastr-2.1.4.tgz", + "integrity": "sha1-i0O+ZPudDEFIcURvLbjoyk6V8YE=", + "requires": { + "jquery": "3.3.1" + } + }, + "tough-cookie": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.4.tgz", + "integrity": "sha512-TZ6TTfI5NtZnuyy/Kecv+CnoROnyXn2DN97LontgQpCwsX2XyLYCC0ENhYkehSOwAp8rTQKc/NUIF7BkQ5rKLA==", + "requires": { + "punycode": "1.4.1" + } + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "requires": { + "safe-buffer": "5.1.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "optional": true + }, + "uid-number": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/uid-number/-/uid-number-0.0.6.tgz", + "integrity": "sha1-DqEOgDXo61uOREnwbaHHMGY7qoE=" + }, + "underscore": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz", + "integrity": "sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI=" + }, + "underscore.string": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-3.3.4.tgz", + "integrity": "sha1-LCo/n4PmR2L9xF5s6sZRQoZCE9s=", + "requires": { + "sprintf-js": "1.1.1", + "util-deprecate": "1.0.2" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "uuid": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz", + "integrity": "sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA==" + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "requires": { + "assert-plus": "1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "1.3.0" + } + }, + "wide-align": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.2.tgz", + "integrity": "sha512-ijDLlyQ7s6x1JgCLur53osjm/UXUYD9+0PbYKrBsYisYXzCxN+HC3mYDNy/dWdmf3AwqwU3CXwDCvsNgGK1S0w==", + "requires": { + "string-width": "1.0.2" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + } + } +} diff --git a/packages/rocketchat-search/README.md b/packages/rocketchat-search/README.md new file mode 100644 index 0000000000000..7029a8aaa74f4 --- /dev/null +++ b/packages/rocketchat-search/README.md @@ -0,0 +1,66 @@ +## Rocketchat Search + +This module enables search for messages and other things within Rocket.Chat. +It provides the basic infrastructure for *Search Providers*, which enables everybody to easily add another +search (e.g. with special functions) to the Rocket.Chat infrastructure. In addition it provides a defautl implementation +based on MongoDB. + +### Providers + +A new Provider just extends the provider class and registers itself in the *SearchProviderService*. +```ecmascript 6 +class MyProvider extends SearchProvider { + constructor() { + super('myProvider'); //a unique id for the provider + }; + + search(text, context, payload, callback) { + //do some search and call the callback with the result + }; +} + +searchProviderService.register(new MyProvider()); +``` + +### Settings +In order to enable Settings within the admin UI for your own provider, you can add it (e.g. in the constructor). +```ecmascript 6 +this._settings.add('PageSize', 'int', 15, { + i18nLabel: 'Search_Page_Size' + }); +``` +The setting values are loaded, when you use your provider. The values can be easily accessed. +```ecmascript 6 +this._settings.get('PageSize') +``` + +### Search UI +Search provider can have their own result template. The template is loaded with data. +```ecmascript 6 +{ + searching, //reactive var if search results are loading + result, //reactive var with the result + text, //reactive var with the search text + settings //the settings of the provider, + parentPayload, //the main search payload (not reset for new searches) + payload, //the payload (reseted when new search is issed from search field) + search //the search function +} +``` + +### Search Result +In order to provide a proper validation of the results the search function of the provider must follow at the following (extendable) format: +```ecmascript 6 +{ + message: { + docs:[{_id},...] + }, + room: { + docs:[{_id},...] + }, + user: { + docs:[{_id},...] + } +} +``` + diff --git a/packages/rocketchat-search/client/provider/result.html b/packages/rocketchat-search/client/provider/result.html new file mode 100644 index 0000000000000..40123414769b1 --- /dev/null +++ b/packages/rocketchat-search/client/provider/result.html @@ -0,0 +1,31 @@ + diff --git a/packages/rocketchat-search/client/provider/result.js b/packages/rocketchat-search/client/provider/result.js new file mode 100644 index 0000000000000..5cb78c01408f3 --- /dev/null +++ b/packages/rocketchat-search/client/provider/result.js @@ -0,0 +1,84 @@ +/* globals FlowRouter, RoomHistoryManager */ +import _ from 'underscore'; + +Meteor.startup(function() { + RocketChat.MessageAction.addButton({ + id: 'jump-to-search-message', + icon: 'jump', + label: 'Jump_to_message', + context: ['search'], + action() { + const message = this._arguments[1]; + if (Session.get('openedRoom') === message.rid) { + return RoomHistoryManager.getSurroundingMessages(message, 50); + } + + FlowRouter.goToRoomById(message.rid); + // RocketChat.MessageAction.hideDropDown(); + + if (window.matchMedia('(max-width: 500px)').matches) { + Template.instance().tabBar.close(); + } + + window.setTimeout(() => { + RoomHistoryManager.getSurroundingMessages(message, 50); + }, 400); + // 400ms is popular among game devs as a good delay before transition starts + // ie. 50, 100, 200, 400, 800 are the favored timings + }, + order: 100, + group: 'menu' + }); +}); + +Template.DefaultSearchResultTemplate.onCreated(function() { + const self = this; + + //paging + this.pageSize = this.data.settings.PageSize; + + //global search + this.globalSearchEnabled = this.data.settings.GlobalSearchEnabled; + this.data.parentPayload.searchAll = this.globalSearchEnabled; + + this.hasMore = new ReactiveVar(true); + + this.autorun(() => { + const result = this.data.result.get(); + self.hasMore.set(!(result && result.message.docs.length < (self.data.payload.limit || self.pageSize))); + }); +}); + +Template.DefaultSearchResultTemplate.events({ + 'change #global-search'(e, t) { + t.data.parentPayload.searchAll = e.target.checked; + t.data.payload.limit = t.pageSize; + t.data.result.set(undefined); + t.data.search(); + + }, + 'scroll .rocket-default-search-results': _.throttle(function(e, t) { + if (e.target.scrollTop >= (e.target.scrollHeight - e.target.clientHeight) && t.hasMore.get()) { + t.data.payload.limit = (t.data.payload.limit || t.pageSize) + t.pageSize; + t.data.search(); + } + }, 200) +}); + +Template.DefaultSearchResultTemplate.helpers({ + result() { + return Template.instance().data.result.get(); + }, + globalSearchEnabled() { + return Template.instance().globalSearchEnabled; + }, + searching() { + return Template.instance().data.searching.get(); + }, + hasMore() { + return Template.instance().hasMore.get(); + }, + message() { + return { customClass: 'search', actionContext: 'search', ...this}; + } +}); diff --git a/packages/rocketchat-search/client/provider/suggestion.html b/packages/rocketchat-search/client/provider/suggestion.html new file mode 100644 index 0000000000000..1aeaf6469ca02 --- /dev/null +++ b/packages/rocketchat-search/client/provider/suggestion.html @@ -0,0 +1,5 @@ + diff --git a/packages/rocketchat-search/client/search/search.html b/packages/rocketchat-search/client/search/search.html new file mode 100644 index 0000000000000..5f2bb57aeb38d --- /dev/null +++ b/packages/rocketchat-search/client/search/search.html @@ -0,0 +1,44 @@ + diff --git a/packages/rocketchat-search/client/search/search.js b/packages/rocketchat-search/client/search/search.js new file mode 100644 index 0000000000000..b13d453294237 --- /dev/null +++ b/packages/rocketchat-search/client/search/search.js @@ -0,0 +1,186 @@ +/* globals ReactiveVar, TAPi18n */ +import toastr from 'toastr'; +import _ from 'underscore'; + +Template.RocketSearch.onCreated(function() { + + this.provider = new ReactiveVar(); + this.isActive = new ReactiveVar(false); + this.error = new ReactiveVar(); + this.suggestions = new ReactiveVar(); + this.suggestionActive = new ReactiveVar(); + + Meteor.call('rocketchatSearch.getProvider', (error, provider) => { + if (!error && provider) { + this.scope.settings = provider.settings; + this.provider.set(provider); + this.isActive.set(true); + } else { + this.error.set('Search_current_provider_not_active'); + } + }); + + const _search = () => { + + const _p = Object.assign({}, this.scope.parentPayload, this.scope.payload); + + if (this.scope.text.get()) { + + this.scope.searching.set(true); + + Meteor.call('rocketchatSearch.search', this.scope.text.get(), {rid:Session.get('openedRoom'), uid:Meteor.userId()}, _p, (err, result) => { + if (err) { + toastr.error(TAPi18n.__('Search_message_search_failed')); + this.scope.searching.set(false); + } else { + this.scope.searching.set(false); + this.scope.result.set(result); + } + }); + } + }; + + this.scope = { + searching: new ReactiveVar(false), + result: new ReactiveVar(), + text: new ReactiveVar(), + settings: {}, + parentPayload: {}, + payload: {}, + search: _search + }; + + this.search = (value) => { + this.scope.result.set(undefined); + this.scope.payload = {}; + this.scope.text.set(value); + _search(); + }; + + this.suggest = (value) => { + this.suggestions.set(); + + const _p = Object.assign({}, this.scope.parentPayload, this.scope.payload); + + Meteor.call('rocketchatSearch.suggest', value, {rid:Session.get('openedRoom'), uid:Meteor.userId()}, this.scope.parentPayload, _p, (err, result) => { + if (err) { + //TODO what should happen + } else { + this.suggestionActive.set(undefined); + this.suggestions.set(result); + } + }); + }; + +}); + +Template.RocketSearch.events = { + 'keydown #message-search'(evt, t) { + if (evt.keyCode === 13) { + if (t.suggestionActive.get() !== undefined) { + const suggestion = t.suggestions.get()[t.suggestionActive.get()]; + if (suggestion.action) { + const value = suggestion.action(); + if (value) { + t.search(value); + } + } else { + t.search(suggestion.text); + } + } else { + t.search(evt.target.value.trim()); + } + t.suggestions.set(); + return evt.preventDefault(); + } + + const suggestions = t.suggestions.get(); + const suggestionActive = t.suggestionActive.get(); + + if (evt.keyCode === 40 && suggestions) { + t.suggestionActive.set((suggestionActive !== undefined && suggestionActive < suggestions.length-1) ? suggestionActive +1 : 0); + return; + } + + if (evt.keyCode === 38 && suggestions) { + t.suggestionActive.set((suggestionActive !== undefined && suggestionActive === 0) ? suggestions.length-1 : suggestionActive-1); + return; + } + }, + 'keyup #message-search': _.debounce(function(evt, t) { + + if (evt.keyCode === 13) { + return evt.preventDefault(); + } + + const value = evt.target.value; + + if (evt.keyCode === 40 || evt.keyCode === 38) { + return evt.preventDefault(); + } + + if (!t.provider.get().supportsSuggestions) { + t.search(value); + } else { + t.suggest(value); + } + return; + }, 300), + 'click .rocket-search-suggestion-item'(e, t) { + if (this.action) { + const value = this.action(); + if (value) { + t.search(value); + } else { + t.suggestions.set(); + } + } else { + t.search(this.text); + } + }, + 'mouseenter .rocket-search-suggestion-item'(e, t) { + t.suggestionActive.set(t.suggestions.get().indexOf(this)); + } +}; + +Template.RocketSearch.helpers({ + error() { + return Template.instance().error.get(); + }, + provider() { + return Template.instance().provider.get(); + }, + scope() { + return Template.instance().scope; + }, + text() { + return Template.instance().scope.text.get(); + }, + isActive() { + return Template.instance().isActive.get(); + }, + suggestions() { + return Template.instance().suggestions.get(); + }, + suggestionActive() { + return Template.instance().suggestionActive.get(); + }, + suggestionSelected(index) { + return Template.instance().suggestionActive.get() === index ? 'active' : ''; + } + +}); + +//add closer to suggestions +Template.RocketSearch.onRendered(function() { + $(document).on(`click.suggestionclose.${ this.data.rid }`, ()=>{ + //if (e.target.id !== 'rocket-search-suggestions' && !$(e.target).parents('#rocket-search-suggestions').length) { + this.suggestions.set(); + //} + }); +}); + +Template.RocketSearch.onDestroyed(function() { + $(document).off(`click.suggestionclose.${ this.data.rid }`); +}); + diff --git a/packages/rocketchat-search/client/style/style.css b/packages/rocketchat-search/client/style/style.css new file mode 100644 index 0000000000000..be5fd2a11511e --- /dev/null +++ b/packages/rocketchat-search/client/style/style.css @@ -0,0 +1,65 @@ +.rocket-search { + padding: 0 !important; + display: flex; + flex: 1; +} + +.defaultProvider { + padding: 24px; +} + +.rocket-search-tab { + display: flex; + flex-direction: column; + flex: 1; +} + +.rocket-default-search-results { + overflow: auto; + overflow-x: hidden; +} + +.rocket-search-result { + display: flex; + flex-direction: column; + flex: 1; +} + +.rocket-default-search-settings { + padding-bottom: 20px; +} + +.rocket-default-search-results .list .message { + padding-left: 70px !important; + padding-right: 8px !important; +} + +.rocket-search-error { + padding: 0 24px; +} + +.rocket-default-search-results .js-list { + overflow: hidden; +} + +#rocket-search-suggestions { + position: absolute; + top: 58px; + left: 24px; + padding: 10px 0; + background-color: white; + right: 24px; + z-index: 1000; + box-shadow: 0 1px 10px #aaa; + border-radius: 0px; +} + +.rocket-search-suggestion-item { + width: 100%; + cursor: pointer; +} + +.rocket-search-suggestion-item.active, .rocket-search-suggestion-item:hover { + background-color: var(--button-primary-background); + color: white; +} diff --git a/packages/rocketchat-search/package.js b/packages/rocketchat-search/package.js new file mode 100644 index 0000000000000..f4385e84e2d36 --- /dev/null +++ b/packages/rocketchat-search/package.js @@ -0,0 +1,27 @@ +Package.describe({ + name: 'rocketchat:search', + version: '0.0.1', + summary: 'Rocketchat Search Providers', + git: '' +}); + +Package.onUse(function(api) { + api.use([ + 'ecmascript', + 'templating', + 'rocketchat:lib', + 'rocketchat:logger', + 'kadira:flow-router' + ]); + + api.addFiles([ + 'client/search/search.html', + 'client/search/search.js', + 'client/provider/suggestion.html', + 'client/provider/result.html', + 'client/provider/result.js', + 'client/style/style.css' + ], 'client'); + + api.mainModule('server/index.js', 'server'); +}); diff --git a/packages/rocketchat-search/server/events/events.js b/packages/rocketchat-search/server/events/events.js new file mode 100644 index 0000000000000..cb562bf4008b4 --- /dev/null +++ b/packages/rocketchat-search/server/events/events.js @@ -0,0 +1,52 @@ +import {searchProviderService} from '../service/providerService'; +import SearchLogger from '../logger/logger'; + +class EventService { + + /*eslint no-unused-vars: [2, { "args": "none" }]*/ + _pushError(name, value, payload) { + //TODO implement a (performant) cache + SearchLogger.debug(`Error on event '${ name }' with id '${ value }'`); + } + + promoteEvent(name, value, payload) { + if (!(searchProviderService.activeProvider && searchProviderService.activeProvider.on(name, value, payload))) { + this._pushError(name, value, payload); + } + } +} + +const eventService = new EventService(); + +/** + * Listen to message changes via Hooks + */ +RocketChat.callbacks.add('afterSaveMessage', function(m) { + eventService.promoteEvent('message.save', m._id, m); +}); + +RocketChat.callbacks.add('afterDeleteMessage', function(m) { + eventService.promoteEvent('message.delete', m._id); +}); + +/** + * Listen to user and room changes via cursor + */ + +RocketChat.models.Users.on('changed', (type, user)=>{ + if (type === 'inserted' || type === 'updated') { + eventService.promoteEvent('user.save', user._id, user); + } + if (type === 'removed') { + eventService.promoteEvent('user.delete', user._id); + } +}); + +RocketChat.models.Rooms.on('changed', (type, room)=>{ + if (type === 'inserted' || type === 'updated') { + eventService.promoteEvent('room.save', room._id, room); + } + if (type === 'removed') { + eventService.promoteEvent('room.delete', room._id); + } +}); diff --git a/packages/rocketchat-search/server/index.js b/packages/rocketchat-search/server/index.js new file mode 100644 index 0000000000000..bb40ec189f552 --- /dev/null +++ b/packages/rocketchat-search/server/index.js @@ -0,0 +1,13 @@ +import './model/provider'; +import './service/providerService.js'; +import './service/validationService.js'; +import './events/events.js'; +import './provider/defaultProvider.js'; + +import {searchProviderService} from './service/providerService'; +import SearchProvider from './model/provider'; + +export { + searchProviderService, + SearchProvider +}; diff --git a/packages/rocketchat-search/server/logger/logger.js b/packages/rocketchat-search/server/logger/logger.js new file mode 100644 index 0000000000000..484c6368dc3e4 --- /dev/null +++ b/packages/rocketchat-search/server/logger/logger.js @@ -0,0 +1,2 @@ +const SearchLogger = new Logger('Search Logger', {}); +export default SearchLogger; diff --git a/packages/rocketchat-search/server/model/provider.js b/packages/rocketchat-search/server/model/provider.js new file mode 100644 index 0000000000000..9023d2fac3a2a --- /dev/null +++ b/packages/rocketchat-search/server/model/provider.js @@ -0,0 +1,176 @@ +/*eslint no-unused-vars: [2, { "args": "none" }]*/ +import SearchLogger from '../logger/logger'; + +/** + * Setting Object in order to manage settings loading for providers and admin ui display + */ +class Setting { + constructor(basekey, key, type, defaultValue, options = {}) { + this._basekey = basekey; + this.key = key; + this.type = type; + this.defaultValue = defaultValue; + this.options = options; + this._value = undefined; + } + + get value() { + return this._value; + } + + /** + * Id is generated based on baseKey and key + * @returns {string} + */ + get id() { + return `Search.${ this._basekey }.${ this.key }`; + } + + load() { + this._value = RocketChat.settings.get(this.id); + + if (this._value === undefined) { this._value = this.defaultValue; } + } + +} + +/** + * Settings Object allows to manage Setting Objects + */ +class Settings { + constructor(basekey) { + this.basekey = basekey; + this.settings = {}; + } + + add(key, type, defaultValue, options) { + this.settings[key] = new Setting(this.basekey, key, type, defaultValue, options); + } + + list() { + return Object.keys(this.settings).map(key => this.settings[key]); + } + + map() { + return this.settings; + } + + /** + * return the value for key + * @param key + */ + get(key) { + if (!this.settings[key]) { throw new Error('Setting is not set'); } + return this.settings[key].value; + } + + /** + * load currently stored values of all settings + */ + load() { + Object.keys(this.settings).forEach((key) => { + this.settings[key].load(); + }); + } +} + +export default class SearchProvider { + + /** + * Create search provider, key must match /^[a-z0-9]+$/ + * @param key + */ + constructor(key) { + + if (!key.match(/^[A-z0-9]+$/)) { throw new Error(`cannot instantiate provider: ${ key } does not match key-pattern`); } + + SearchLogger.info(`create search provider ${ key }`); + + this._key = key; + this._settings = new Settings(key); + } + + /*--- basic params ---*/ + get key() { + return this._key; + } + + get i18nLabel() { + return undefined; + } + + get i18nDescription() { + return undefined; + } + + get iconName() { + return 'magnifier'; + } + + get settings() { + return this._settings.list(); + } + + get settingsAsMap() { + return this._settings.map(); + } + + /*--- templates ---*/ + get resultTemplate() { + return 'DefaultSearchResultTemplate'; + } + + get suggestionItemTemplate() { + return 'DefaultSuggestionItemTemplate'; + } + + /*--- search functions ---*/ + /** + * Search using the current search provider and check if results are valid for the user. The search result has + * the format {messages:{start:0,numFound:1,docs:[{...}]},users:{...},rooms:{...}} + * @param text the search text + * @param context the context (uid, rid) + * @param payload custom payload (e.g. for paging) + * @param callback is used to return result an can be called with (error,result) + */ + search(text, context, payload, callback) { + throw new Error('Function search has to be implemented'); + } + + /** + * Returns an ordered list of suggestions. The result should have at least the form [{text:string}] + * @param text + * @param context + * @param payload + * @param callback + */ + suggest(text, context, payload, callback) { + callback(null, []); + } + + get supportsSuggestions() { + return false; + } + + /*--- triggers ---*/ + on(name, value) { + return true; + } + + /*--- livecycle ---*/ + run(reason, callback) { + return new Promise((resolve, reject) => { + this._settings.load(); + this.start(reason, resolve, reject); + }); + } + + start(reason, resolve) { + resolve(); + } + + stop(resolve) { + resolve(); + } +} + diff --git a/packages/rocketchat-search/server/provider/defaultProvider.js b/packages/rocketchat-search/server/provider/defaultProvider.js new file mode 100644 index 0000000000000..5f457eca7f827 --- /dev/null +++ b/packages/rocketchat-search/server/provider/defaultProvider.js @@ -0,0 +1,47 @@ +import {searchProviderService} from '../service/providerService'; +import SearchProvider from '../model/provider'; + +/** + * Implements the default provider (based on mongo db search) + */ +class DefaultProvider extends SearchProvider { + + /** + * Enable settings: GlobalSearchEnabled, PageSize + */ + constructor() { + super('defaultProvider'); + this._settings.add('GlobalSearchEnabled', 'boolean', false, { + i18nLabel: 'Global_Search', + alert: 'This feature is currently in beta and could decrease the application performance! Please report bugs to github.com/RocketChat/Rocket.Chat/issues' + }); + this._settings.add('PageSize', 'int', 10, { + i18nLabel: 'Search_Page_Size' + }); + } + + get i18nLabel() { + return 'Default provider'; + } + + get i18nDescription() { + return 'You_can_search_using_RegExp_eg'; + } + + /** + * {@inheritDoc} + * Uses Meteor function 'messageSearch' + */ + search(text, context, payload = {}, callback) { + + const _rid = payload.searchAll ? undefined : context.rid; + + const _limit = payload.limit || this._settings.get('PageSize'); + + Meteor.call('messageSearch', text, _rid, _limit, callback); + + } +} + +//register provider +searchProviderService.register(new DefaultProvider()); diff --git a/packages/rocketchat-search/server/service/providerService.js b/packages/rocketchat-search/server/service/providerService.js new file mode 100644 index 0000000000000..3f852021c94dc --- /dev/null +++ b/packages/rocketchat-search/server/service/providerService.js @@ -0,0 +1,217 @@ +/* globals RocketChat */ +import _ from 'underscore'; + +import {validationService} from '../service/validationService'; +import SearchLogger from '../logger/logger'; + +class SearchProviderService { + + constructor() { + this.providers = {}; + this.activeProvider = undefined; + } + + /** + * Stop current provider (if there is one) and start the new + * @param id the id of the provider which should be started + * @param cb a possible callback if provider is active or not (currently not in use) + */ + use(id) { + + return new Promise((resolve, reject) => { + if (!this.providers[id]) { throw new Error(`provider ${ id } cannot be found`); } + + let reason = 'switch'; + + if (!this.activeProvider) { + reason = 'startup'; + } else if (this.activeProvider.key === this.providers[id].key) { + reason = 'update'; + } + + const stopProvider = () => { + return new Promise((resolve, reject) => { + if (this.activeProvider) { + + SearchLogger.debug(`Stopping provider '${ this.activeProvider.key }'`); + + this.activeProvider.stop(resolve, reject); + } else { + resolve(); + } + }); + }; + + stopProvider().then(() => { + this.activeProvider = undefined; + + SearchLogger.debug(`Start provider '${ id }'`); + + try { + + this.providers[id].run(reason).then(() => { + this.activeProvider = this.providers[id]; + resolve(); + }, reject); + + } catch (e) { + reject(e); + } + }, reject); + + }); + + } + + /** + * Registers a search provider on system startup + * @param provider + */ + register(provider) { + this.providers[provider.key] = provider; + } + + /** + * Starts the service (loads provider settings for admin ui, add lister not setting changes, enable current provider + */ + start() { + SearchLogger.debug('Load data for all providers'); + + const providers = this.providers; + + //add settings for admininistration + RocketChat.settings.addGroup('Search', function() { + + const self = this; + + self.add('Search.Provider', 'defaultProvider', { + type: 'select', + values: Object.keys(providers).map((key) => { return {key, i18nLabel: providers[key].i18nLabel}; }), + public: true, + i18nLabel: 'Search_Provider' + }); + + Object.keys(providers) + .filter((key) => providers[key].settings && providers[key].settings.length > 0) + .forEach(function(key) { + self.section(providers[key].i18nLabel, function() { + providers[key].settings.forEach((setting) => { + + const _options = { + type: setting.type, + ...setting.options + }; + + _options.enableQuery = _options.enableQuery || []; + + _options.enableQuery.push({ + _id: 'Search.Provider', + value: key + }); + + this.add(setting.id, setting.defaultValue, _options); + }); + }); + }); + }); + + //add listener to react on setting changes + const configProvider = _.debounce(Meteor.bindEnvironment(() => { + const providerId = RocketChat.settings.get('Search.Provider'); + + if (providerId) { + this.use(providerId);//TODO do something with success and errors + } + + }), 1000); + + RocketChat.settings.get(/^Search\./, configProvider); + } + +} + +export const searchProviderService = new SearchProviderService(); + +Meteor.startup(() => { + searchProviderService.start(); +}); + +Meteor.methods({ + /** + * Search using the current search provider and check if results are valid for the user. The search result has + * the format {messages:{start:0,numFound:1,docs:[{...}]},users:{...},rooms:{...}} + * @param text the search text + * @param context the context (uid, rid) + * @param payload custom payload (e.g. for paging) + * @returns {*} + */ + 'rocketchatSearch.search'(text, context, payload) { + + return new Promise((resolve, reject) => { + + payload = payload !== null ? payload : undefined;//TODO is this cleanup necessary? + + try { + + if (!searchProviderService.activeProvider) { + throw new Error('Provider currently not active'); + } + + SearchLogger.debug('search: ', `\n\tText:${ text }\n\tContext:${ JSON.stringify(context) }\n\tPayload:${ JSON.stringify(payload) }`); + + searchProviderService.activeProvider.search(text, context, payload, (error, data) => { + if (error) { + reject(error); + } else { + resolve(validationService.validateSearchResult(data)); + } + }); + } catch (e) { + reject(e); + } + }); + }, + 'rocketchatSearch.suggest'(text, context, payload) { + + return new Promise((resolve, reject) => { + payload = payload !== null ? payload : undefined;//TODO is this cleanup necessary? + + try { + + if (!searchProviderService.activeProvider) { throw new Error('Provider currently not active'); } + + SearchLogger.debug('suggest: ', `\n\tText:${ text }\n\tContext:${ JSON.stringify(context) }\n\tPayload:${ JSON.stringify(payload) }`); + + searchProviderService.activeProvider.suggest(text, context, payload, (error, data) => { + if (error) { + reject(error); + } else { + resolve(data); + } + }); + } catch (e) { + reject(e); + } + }); + }, + /** + * Get the current provider with key, description, resultTemplate, suggestionItemTemplate and settings (as Map) + * @returns {*} + */ + 'rocketchatSearch.getProvider'() { + if (!searchProviderService.activeProvider) { return undefined; } + + return { + key: searchProviderService.activeProvider.key, + description: searchProviderService.activeProvider.i18nDescription, + icon: searchProviderService.activeProvider.iconName, + resultTemplate: searchProviderService.activeProvider.resultTemplate, + supportsSuggestions: searchProviderService.activeProvider.supportsSuggestions, + suggestionItemTemplate: searchProviderService.activeProvider.suggestionItemTemplate, + settings: _.mapObject(searchProviderService.activeProvider.settingsAsMap, (setting) => { + return setting.value; + }) + }; + } +}); + diff --git a/packages/rocketchat-search/server/service/validationService.js b/packages/rocketchat-search/server/service/validationService.js new file mode 100644 index 0000000000000..b2db2d32e952a --- /dev/null +++ b/packages/rocketchat-search/server/service/validationService.js @@ -0,0 +1,73 @@ +import SearchLogger from '../logger/logger'; + +class ValidationService { + constructor() {} + + validateSearchResult(result) { + + const subscriptionCache = {}; + + const getSubscription = (rid, uid) => { + if (!subscriptionCache.hasOwnProperty(rid)) { + subscriptionCache[rid] = Meteor.call('canAccessRoom', rid, uid); + } + + return subscriptionCache[rid]; + }; + + const userCache = {}; + + const getUsername = (uid) => { + if (!userCache.hasOwnProperty(uid)) { + try { + userCache[uid] = RocketChat.models.Users.findById(uid).fetch()[0].username; + } catch (e) { + userCache[uid] = undefined; + } + } + return userCache[uid]; + }; + + const uid = Meteor.userId(); + //get subscription for message + if (result.message) { + result.message.docs.forEach((msg) => { + + const subscription = getSubscription(msg.rid, uid); + + if (subscription) { + msg.r = {name: subscription.name, t: subscription.t}; + msg.username = getUsername(msg.user); + msg.valid = true; + SearchLogger.debug(`user ${ uid } can access ${ msg.rid } ( ${ subscription.t === 'd' ? subscription.username : subscription.name } )`); + } else { + SearchLogger.debug(`user ${ uid } can NOT access ${ msg.rid }`); + } + }); + + result.message.docs.filter((msg) => { + return msg.valid; + }); + } + + if (result.room) { + result.room.docs.forEach((room) => { + const subscription = getSubscription(room._id, uid); + if (subscription) { + room.valid = true; + SearchLogger.debug(`user ${ uid } can access ${ room._id } ( ${ subscription.t === 'd' ? subscription.username : subscription.name } )`); + } else { + SearchLogger.debug(`user ${ uid } can NOT access ${ room._id }`); + } + }); + + result.room.docs.filter((room) => { + return room.valid; + }); + } + + return result; + } +} + +export const validationService = new ValidationService(); diff --git a/packages/rocketchat-ui-flextab/client/tabs/messageSearch.html b/packages/rocketchat-ui-flextab/client/tabs/messageSearch.html deleted file mode 100644 index 91b5d256cc5d1..0000000000000 --- a/packages/rocketchat-ui-flextab/client/tabs/messageSearch.html +++ /dev/null @@ -1,47 +0,0 @@ - diff --git a/packages/rocketchat-ui-flextab/client/tabs/messageSearch.js b/packages/rocketchat-ui-flextab/client/tabs/messageSearch.js deleted file mode 100644 index 5fd559a1f1882..0000000000000 --- a/packages/rocketchat-ui-flextab/client/tabs/messageSearch.js +++ /dev/null @@ -1,132 +0,0 @@ -import _ from 'underscore'; - -Meteor.startup(function() { - RocketChat.MessageAction.addButton({ - id: 'jump-to-search-message', - icon: 'jump', - label: 'Jump_to_message', - context: [ - 'search' - ], - action() { - const message = this._arguments[1]; - if (Session.get('openedRoom') === message.rid) { - return RoomHistoryManager.getSurroundingMessages(message, 50); - } - - FlowRouter.goToRoomById(message.rid); - // RocketChat.MessageAction.hideDropDown(); - - if (window.matchMedia('(max-width: 500px)').matches) { - Template.instance().tabBar.close(); - } - - window.setTimeout(() => { - RoomHistoryManager.getSurroundingMessages(message, 50); - }, 400); - // 400ms is popular among game devs as a good delay before transition starts - // ie. 50, 100, 200, 400, 800 are the favored timings - }, - order: 100, - group: 'menu' - }); -}); - - -Template.messageSearch.helpers({ - tSearchMessages() { - return t('Search_Messages'); - }, - - searchResultMessages() { - const searchResult = Template.instance().searchResult.get(); - if (searchResult) { - return searchResult.messages; - } - }, - - hasMore() { - return Template.instance().hasMore.get(); - }, - - currentSearchTerm() { - return Template.instance().currentSearchTerm.get(); - }, - - ready() { - return Template.instance().ready.get(); - }, - - message() { - return _.extend(this, { customClass: 'search', actionContext: 'search'}); - }, - - allowGlobalSearch() { - return RocketChat.settings.get('Message_GlobalSearch'); - } -}); - -Template.messageSearch.events({ - 'keydown #message-search'(e) { - if (e.keyCode === 13) { - return e.preventDefault(); - } - }, - - 'keyup #message-search': _.debounce(function(e, t) { - const value = e.target.value.trim(); - if ((value === '') && t.currentSearchTerm.get()) { - t.currentSearchTerm.set(''); - t.searchResult.set(undefined); - t.hasMore.set(false); - return; - } else if (value === t.currentSearchTerm.get()) { - return; - } - const globalSearch = $('#global-search').is(':checked'); - t.hasMore.set(true); - t.limit.set(20); - return t.search(globalSearch); - }, 500), - - 'click .load-more button'(e, t) { - t.limit.set(t.limit.get() + 20); - return t.search(); - }, - - 'scroll .js-list': _.throttle(function(e, t) { - if (e.target.scrollTop >= (e.target.scrollHeight - e.target.clientHeight)) { - t.limit.set(t.limit.get() + 20); - return t.search(); - } - }, 200) -}); - -Template.messageSearch.onCreated(function() { - this.currentSearchTerm = new ReactiveVar(''); - this.searchResult = new ReactiveVar(); - this.hasMore = new ReactiveVar(true); - this.limit = new ReactiveVar(20); - this.ready = new ReactiveVar(true); - - return this.search = (globalSearch = false) => { - this.ready.set(false); - const value = this.$('#message-search').val(); - return Tracker.nonreactive(() => { - return Meteor.call('messageSearch', value, (globalSearch) ? undefined: Session.get('openedRoom'), this.limit.get(), (error, result) => { - this.currentSearchTerm.set(value); - this.ready.set(true); - if ((result != null) && (((result.messages !== null ? result.messages.length : undefined) > 0) || ((result.users != null ? result.users.length : undefined) > 0) || ((result.channels != null ? result.channels.length : undefined) > 0))) { - this.searchResult.set(result); - if (((result.messages != null ? result.messages.length : undefined) + (result.users != null ? result.users.length : undefined) + (result.channels != null ? result.channels.length : undefined)) < this.limit.get()) { - return this.hasMore.set(false); - } - } else { - return this.searchResult.set(); - } - } - ); - } - ); - }; -}); diff --git a/packages/rocketchat-ui-flextab/package.js b/packages/rocketchat-ui-flextab/package.js index 520a9ac304ddd..176a4fc93eebf 100644 --- a/packages/rocketchat-ui-flextab/package.js +++ b/packages/rocketchat-ui-flextab/package.js @@ -21,7 +21,6 @@ Package.onUse(function(api) { api.addFiles('client/flexTabBar.html', 'client'); api.addFiles('client/tabs/inviteUsers.html', 'client'); api.addFiles('client/tabs/membersList.html', 'client'); - api.addFiles('client/tabs/messageSearch.html', 'client'); api.addFiles('client/tabs/uploadedFilesList.html', 'client'); api.addFiles('client/tabs/userEdit.html', 'client'); api.addFiles('client/tabs/userInfo.html', 'client'); @@ -29,7 +28,6 @@ Package.onUse(function(api) { api.addFiles('client/flexTabBar.js', 'client'); api.addFiles('client/tabs/inviteUsers.js', 'client'); api.addFiles('client/tabs/membersList.js', 'client'); - api.addFiles('client/tabs/messageSearch.js', 'client'); api.addFiles('client/tabs/uploadedFilesList.js', 'client'); api.addFiles('client/tabs/userEdit.js', 'client'); api.addFiles('client/tabs/userInfo.js', 'client'); diff --git a/server/methods/messageSearch.js b/server/methods/messageSearch.js index 2d84ca0af10e5..d2750e3b6dd4a 100644 --- a/server/methods/messageSearch.js +++ b/server/methods/messageSearch.js @@ -8,9 +8,9 @@ Meteor.methods({ // TODO: Evaluate why we are returning `users` and `channels`, as the only thing that gets set is the `messages`. const result = { - messages: [], - users: [], - channels: [] + message: { + docs:[] + } }; const currentUserId = Meteor.userId(); @@ -25,7 +25,7 @@ Meteor.methods({ if (!Meteor.call('canAccessRoom', rid, currentUserId)) { return result; } - } else if (RocketChat.settings.get('Message_GlobalSearch') !== true) { + } else if (RocketChat.settings.get('Search.defaultProvider.GlobalSearchEnabled') !== true) { return result; } @@ -226,7 +226,7 @@ Meteor.methods({ }; } - result.messages = RocketChat.models.Messages.find(query, options).fetch(); + result.message.docs = RocketChat.models.Messages.find(query, options).fetch(); } return result; diff --git a/server/startup/migrations/v114.js b/server/startup/migrations/v114.js new file mode 100644 index 0000000000000..c393adc2bb6bc --- /dev/null +++ b/server/startup/migrations/v114.js @@ -0,0 +1,18 @@ +RocketChat.Migrations.add({ + version: 114, + up() { + if (RocketChat && RocketChat.models) { + if (RocketChat.models.Settings) { + const setting = RocketChat.models.Settings.findOne({ _id: 'Message_GlobalSearch' }); + if (setting && setting.value) { + RocketChat.models.Settings.upsert( + { _id: 'Search.defaultProvider.GlobalSearchEnabled' }, + { $set: { value: setting.value } } + ); + + RocketChat.models.Settings.removeById('Message_GlobalSearch'); + } + } + } + } +}); diff --git a/tests/pageobjects/flex-tab.page.js b/tests/pageobjects/flex-tab.page.js index b6add0dff6f9a..12f9b4661abda 100644 --- a/tests/pageobjects/flex-tab.page.js +++ b/tests/pageobjects/flex-tab.page.js @@ -44,7 +44,7 @@ class FlexTab extends Page { // Search Tab get searchTab() { return browser.element('.tab-button:not(.hidden) .tab-button-icon--magnifier'); } - get searchTabContent() { return browser.element('.search-messages-list'); } + get searchTabContent() { return browser.element('.rocket-search-result'); } get messageSearchBar() { return browser.element('#message-search'); } get searchResult() { return browser.element('.new-day'); }