[NEW] Custom User Status (#13933)
Co-Authored-By: Tasso Evangelista <tasso@tassoevan.me> Co-Authored-By: Guilherme Gazzo <guilhermegazzo@gmail.com> Co-Authored-By: wreiske <wreiske@mieweb.com>pull/14852/head
parent
b954bb8d3c
commit
fbb47b6783
@ -0,0 +1,45 @@ |
|||||||
|
import { Meteor } from 'meteor/meteor'; |
||||||
|
import s from 'underscore.string'; |
||||||
|
|
||||||
|
import { Users } from '../../../models'; |
||||||
|
import { Notifications } from '../../../notifications'; |
||||||
|
import { hasPermission } from '../../../authorization'; |
||||||
|
import { RateLimiter } from '../lib'; |
||||||
|
|
||||||
|
export const _setStatusMessage = function(userId, statusMessage) { |
||||||
|
statusMessage = s.trim(statusMessage); |
||||||
|
if (statusMessage.length > 120) { |
||||||
|
statusMessage = statusMessage.substr(0, 120); |
||||||
|
} |
||||||
|
|
||||||
|
if (!userId) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
const user = Users.findOneById(userId); |
||||||
|
|
||||||
|
// User already has desired statusMessage, return
|
||||||
|
if (user.statusText === statusMessage) { |
||||||
|
return user; |
||||||
|
} |
||||||
|
|
||||||
|
// Set new statusMessage
|
||||||
|
Users.updateStatusText(user._id, statusMessage); |
||||||
|
user.statusText = statusMessage; |
||||||
|
|
||||||
|
Notifications.notifyLogged('Users:StatusMessageChanged', { |
||||||
|
_id: user._id, |
||||||
|
name: user.name, |
||||||
|
username: user.username, |
||||||
|
statusText: user.statusText, |
||||||
|
}); |
||||||
|
|
||||||
|
return true; |
||||||
|
}; |
||||||
|
|
||||||
|
export const setStatusMessage = RateLimiter.limitFunction(_setStatusMessage, 1, 60000, { |
||||||
|
0() { |
||||||
|
// Administrators have permission to change others status, so don't limit those
|
||||||
|
return !Meteor.userId() || !hasPermission(Meteor.userId(), 'edit-other-user-info'); |
||||||
|
}, |
||||||
|
}); |
||||||
@ -0,0 +1,10 @@ |
|||||||
|
import { Base } from './_Base'; |
||||||
|
|
||||||
|
class CustomUserStatus extends Base { |
||||||
|
constructor() { |
||||||
|
super(); |
||||||
|
this._initModel('custom_user_status'); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
export default new CustomUserStatus(); |
||||||
@ -0,0 +1,66 @@ |
|||||||
|
import { Base } from './_Base'; |
||||||
|
|
||||||
|
class CustomUserStatus extends Base { |
||||||
|
constructor() { |
||||||
|
super('custom_user_status'); |
||||||
|
|
||||||
|
this.tryEnsureIndex({ name: 1 }); |
||||||
|
} |
||||||
|
|
||||||
|
// find one
|
||||||
|
findOneById(_id, options) { |
||||||
|
return this.findOne(_id, options); |
||||||
|
} |
||||||
|
|
||||||
|
// find
|
||||||
|
findByName(name, options) { |
||||||
|
const query = { |
||||||
|
name, |
||||||
|
}; |
||||||
|
|
||||||
|
return this.find(query, options); |
||||||
|
} |
||||||
|
|
||||||
|
findByNameExceptId(name, except, options) { |
||||||
|
const query = { |
||||||
|
_id: { $nin: [except] }, |
||||||
|
name, |
||||||
|
}; |
||||||
|
|
||||||
|
return this.find(query, options); |
||||||
|
} |
||||||
|
|
||||||
|
// update
|
||||||
|
setName(_id, name) { |
||||||
|
const update = { |
||||||
|
$set: { |
||||||
|
name, |
||||||
|
}, |
||||||
|
}; |
||||||
|
|
||||||
|
return this.update({ _id }, update); |
||||||
|
} |
||||||
|
|
||||||
|
setStatusType(_id, statusType) { |
||||||
|
const update = { |
||||||
|
$set: { |
||||||
|
statusType, |
||||||
|
}, |
||||||
|
}; |
||||||
|
|
||||||
|
return this.update({ _id }, update); |
||||||
|
} |
||||||
|
|
||||||
|
// INSERT
|
||||||
|
create(data) { |
||||||
|
return this.insert(data); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
// REMOVE
|
||||||
|
removeById(_id) { |
||||||
|
return this.remove(_id); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
export default new CustomUserStatus(); |
||||||
@ -0,0 +1 @@ |
|||||||
|
import '../lib/status'; |
||||||
@ -0,0 +1,8 @@ |
|||||||
|
import { Meteor } from 'meteor/meteor'; |
||||||
|
|
||||||
|
if (Meteor.isClient) { |
||||||
|
module.exports = require('./client/index.js'); |
||||||
|
} |
||||||
|
if (Meteor.isServer) { |
||||||
|
module.exports = require('./server/index.js'); |
||||||
|
} |
||||||
@ -0,0 +1,46 @@ |
|||||||
|
import { Meteor } from 'meteor/meteor'; |
||||||
|
import { TAPi18n } from 'meteor/tap:i18n'; |
||||||
|
import { Random } from 'meteor/random'; |
||||||
|
|
||||||
|
import { handleError, slashCommands } from '../../utils'; |
||||||
|
import { hasPermission } from '../../authorization'; |
||||||
|
import { Notifications } from '../../notifications'; |
||||||
|
|
||||||
|
function Status(command, params, item) { |
||||||
|
if (command === 'status') { |
||||||
|
if ((Meteor.isClient && hasPermission('edit-other-user-info')) || (Meteor.isServer && hasPermission(Meteor.userId(), 'edit-other-user-info'))) { |
||||||
|
const user = Meteor.users.findOne(Meteor.userId()); |
||||||
|
|
||||||
|
Meteor.call('setUserStatus', null, params, (err) => { |
||||||
|
if (err) { |
||||||
|
if (Meteor.isClient) { |
||||||
|
return handleError(err); |
||||||
|
} |
||||||
|
|
||||||
|
if (err.error === 'error-not-allowed') { |
||||||
|
Notifications.notifyUser(Meteor.userId(), 'message', { |
||||||
|
_id: Random.id(), |
||||||
|
rid: item.rid, |
||||||
|
ts: new Date(), |
||||||
|
msg: TAPi18n.__('StatusMessage_Change_Disabled', null, user.language), |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
throw err; |
||||||
|
} else { |
||||||
|
Notifications.notifyUser(Meteor.userId(), 'message', { |
||||||
|
_id: Random.id(), |
||||||
|
rid: item.rid, |
||||||
|
ts: new Date(), |
||||||
|
msg: TAPi18n.__('StatusMessage_Changed_Successfully', null, user.language), |
||||||
|
}); |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
slashCommands.add('status', Status, { |
||||||
|
description: 'Slash_Status_Description', |
||||||
|
params: 'Slash_Status_Params', |
||||||
|
}); |
||||||
@ -0,0 +1 @@ |
|||||||
|
import '../lib/status'; |
||||||
@ -1,19 +1,18 @@ |
|||||||
<template name="selectDropdown"> |
<template name="selectDropdown"> |
||||||
<div class="rc-input"> |
<div class="rc-input rc-input--small"> |
||||||
<label class="rc-input__label"> |
<label class="rc-input__label"> |
||||||
<div class="rc-input__title">Invite people to channel</div> |
<div class="rc-input__title">{{title}}</div> |
||||||
<div class="rc-input__wrapper"> |
<div class="rc-input__wrapper"> |
||||||
<div class="rc-input__icon"> |
<select type="text" class="rc-input__element" placeholder="{{placeholder}}" name="{{name}}"> |
||||||
{{> icon block="rc-input__icon-svg" icon="at"}} |
{{#each option in options}} |
||||||
</div> |
{{#if option.selected}} |
||||||
{{ selectedUsers }} |
<option value={{option.value}} selected>{{option.title}}</option> |
||||||
<input type="text" class="rc-input__element" placeholder="Type user name" name="users"> |
{{else}} |
||||||
|
<option value={{option.value}}>{{option.title}}</option> |
||||||
|
{{/if}} |
||||||
|
{{/each}} |
||||||
|
</select> |
||||||
</div> |
</div> |
||||||
</label> |
</label> |
||||||
{{#if open}} |
|
||||||
<div class="fadeInDown"> |
|
||||||
{{ > popupList .}} |
|
||||||
</div> |
|
||||||
{{/if}} |
|
||||||
</div> |
</div> |
||||||
</template> |
</template> |
||||||
|
|||||||
@ -1,23 +0,0 @@ |
|||||||
import { ReactiveVar } from 'meteor/reactive-var'; |
|
||||||
import { Template } from 'meteor/templating'; |
|
||||||
|
|
||||||
Template.selectDropdown.events({ |
|
||||||
'focus input'(e, i) { |
|
||||||
i.open.set(true); |
|
||||||
console.log('asdasd'); |
|
||||||
}, |
|
||||||
'blur input'(e, i) { |
|
||||||
setTimeout(() => { |
|
||||||
i.open.set(false); |
|
||||||
}, 100); |
|
||||||
console.log('asdasd'); |
|
||||||
}, |
|
||||||
}); |
|
||||||
Template.selectDropdown.helpers({ |
|
||||||
open() { |
|
||||||
return Template.instance().open.get(); |
|
||||||
}, |
|
||||||
}); |
|
||||||
Template.selectDropdown.onCreated(function() { |
|
||||||
this.open = new ReactiveVar(false); |
|
||||||
}); |
|
||||||
@ -0,0 +1,45 @@ |
|||||||
|
.edit-status-type.rc-popover { |
||||||
|
&__item { |
||||||
|
&--online { |
||||||
|
color: var(--status-online); |
||||||
|
} |
||||||
|
|
||||||
|
&--away { |
||||||
|
color: var(--status-away); |
||||||
|
} |
||||||
|
|
||||||
|
&--busy { |
||||||
|
color: var(--status-busy); |
||||||
|
} |
||||||
|
|
||||||
|
&--offline { |
||||||
|
color: var(--status-invisible); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
.edit-status-type-icon { |
||||||
|
&--online { |
||||||
|
& .rc-icon { |
||||||
|
color: var(--status-online); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
&--away { |
||||||
|
& .rc-icon { |
||||||
|
color: var(--status-away); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
&--busy { |
||||||
|
& .rc-icon { |
||||||
|
color: var(--status-busy); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
&--offline { |
||||||
|
& .rc-icon { |
||||||
|
color: var(--status-invisible); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,30 @@ |
|||||||
|
<template name="editStatus"> |
||||||
|
<section class="edit-status"> |
||||||
|
<div class="edit-status__wrapper"> |
||||||
|
{{#if canChange}} |
||||||
|
<form id="edit-status" name="edit-status" class="edit-status__content"> |
||||||
|
<div class="edit-status__inputs"> |
||||||
|
<div class="rc-input {{#if invalidChannel}}rc-input--error{{/if}}"> |
||||||
|
<label class="rc-input__label"> |
||||||
|
<div class="rc-input__title">{{_ "StatusMessage"}}</div> |
||||||
|
<div class="rc-input__wrapper edit-status"> |
||||||
|
<div class="rc-input__icon edit-status-type-icon--{{statusType}}"> |
||||||
|
{{> icon block="rc-input__icon-svg" icon="circle"}} |
||||||
|
{{> icon block="rc-input__icon-svg" icon="arrow-down"}} |
||||||
|
</div> |
||||||
|
<input name="status" type="text" maxlength="120" class="rc-input__element" placeholder="{{_ "StatusMessage_Placeholder"}}" autofocus value="{{statusText}}"> |
||||||
|
<input type="hidden" name="statusType" value="{{statusType}}"> |
||||||
|
</div> |
||||||
|
</label> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</form> |
||||||
|
<div class="edit-status__inputs"> |
||||||
|
<input form='edit-status' class="rc-button rc-button--primary" type='submit' data-button="Save" value="{{_ "Save"}}" /> |
||||||
|
</div> |
||||||
|
{{else}} |
||||||
|
<div class="rc-input__description">{{_ "StatusMessage_Change_Disabled"}}</div> |
||||||
|
{{/if}} |
||||||
|
</div> |
||||||
|
</section> |
||||||
|
</template> |
||||||
@ -0,0 +1,113 @@ |
|||||||
|
import { Meteor } from 'meteor/meteor'; |
||||||
|
import { Template } from 'meteor/templating'; |
||||||
|
import toastr from 'toastr'; |
||||||
|
import s from 'underscore.string'; |
||||||
|
|
||||||
|
import { settings } from '../../../../settings'; |
||||||
|
import { t } from '../../../../utils'; |
||||||
|
import { popover } from '../../../../ui-utils'; |
||||||
|
|
||||||
|
Template.editStatus.helpers({ |
||||||
|
canChange() { |
||||||
|
return settings.get('Accounts_AllowUserStatusMessageChange'); |
||||||
|
}, |
||||||
|
statusType() { |
||||||
|
return Meteor.user().status; |
||||||
|
}, |
||||||
|
statusText() { |
||||||
|
return Meteor.user().statusText; |
||||||
|
}, |
||||||
|
}); |
||||||
|
|
||||||
|
Template.editStatus.events({ |
||||||
|
'click .edit-status .rc-input__icon'(e) { |
||||||
|
const options = [ |
||||||
|
{ |
||||||
|
icon: 'circle', |
||||||
|
name: t('Online'), |
||||||
|
modifier: 'online', |
||||||
|
action: () => { |
||||||
|
$('input[name=statusType]').val('online'); |
||||||
|
$('.edit-status .rc-input__icon').prop('class', 'rc-input__icon edit-status-type-icon--online'); |
||||||
|
}, |
||||||
|
}, |
||||||
|
{ |
||||||
|
icon: 'circle', |
||||||
|
name: t('Away'), |
||||||
|
modifier: 'away', |
||||||
|
action: () => { |
||||||
|
$('input[name=statusType]').val('away'); |
||||||
|
$('.edit-status .rc-input__icon').prop('class', 'rc-input__icon edit-status-type-icon--away'); |
||||||
|
}, |
||||||
|
}, |
||||||
|
{ |
||||||
|
icon: 'circle', |
||||||
|
name: t('Busy'), |
||||||
|
modifier: 'busy', |
||||||
|
action: () => { |
||||||
|
$('input[name=statusType]').val('busy'); |
||||||
|
$('.edit-status .rc-input__icon').prop('class', 'rc-input__icon edit-status-type-icon--busy'); |
||||||
|
}, |
||||||
|
}, |
||||||
|
{ |
||||||
|
icon: 'circle', |
||||||
|
name: t('Invisible'), |
||||||
|
modifier: 'offline', |
||||||
|
action: () => { |
||||||
|
$('input[name=statusType]').val('offline'); |
||||||
|
$('.edit-status .rc-input__icon').prop('class', 'rc-input__icon edit-status-type-icon--offline'); |
||||||
|
}, |
||||||
|
}, |
||||||
|
]; |
||||||
|
|
||||||
|
const config = { |
||||||
|
popoverClass: 'edit-status-type', |
||||||
|
columns: [ |
||||||
|
{ |
||||||
|
groups: [ |
||||||
|
{ |
||||||
|
items: options, |
||||||
|
}, |
||||||
|
], |
||||||
|
}, |
||||||
|
], |
||||||
|
currentTarget: e.currentTarget, |
||||||
|
offsetVertical: e.currentTarget.clientHeight, |
||||||
|
}; |
||||||
|
popover.open(config); |
||||||
|
}, |
||||||
|
|
||||||
|
'submit .edit-status__content'(e, instance) { |
||||||
|
e.preventDefault(); |
||||||
|
e.stopPropagation(); |
||||||
|
const statusText = s.trim(e.target.status.value); |
||||||
|
const statusType = e.target.statusType.value; |
||||||
|
|
||||||
|
if (statusText !== this.statusText) { |
||||||
|
if (statusText.length > 120) { |
||||||
|
toastr.remove(); |
||||||
|
toastr.error(t('StatusMessage_Too_Long')); |
||||||
|
return false; |
||||||
|
} |
||||||
|
if (!settings.get('Accounts_AllowUserStatusMessageChange')) { |
||||||
|
toastr.remove(); |
||||||
|
toastr.error(t('StatusMessage_Change_Disabled')); |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
if (statusText || statusText.length === 0) { |
||||||
|
Meteor.call('setUserStatus', statusType, statusText); |
||||||
|
if (instance.data.onSave) { |
||||||
|
instance.data.onSave(true); |
||||||
|
} |
||||||
|
return; |
||||||
|
} |
||||||
|
} |
||||||
|
return false; |
||||||
|
}, |
||||||
|
}); |
||||||
|
|
||||||
|
|
||||||
|
Template.editStatus.onRendered(function() { |
||||||
|
this.firstNode.querySelector('[name="status"]').focus(); |
||||||
|
}); |
||||||
@ -0,0 +1,72 @@ |
|||||||
|
<template name="adminUserStatus"> |
||||||
|
<div class="main-content-flex"> |
||||||
|
<section class="page-container page-list flex-tab-main-content"> |
||||||
|
{{> header sectionName="Custom_User_Status"}} |
||||||
|
<div class="content"> |
||||||
|
{{#unless hasPermission 'manage-user-status'}} |
||||||
|
<p>{{_ "You_are_not_authorized_to_view_this_page"}}</p> |
||||||
|
{{else}} |
||||||
|
<form class="search-form" role="form"> |
||||||
|
<div class="rc-input__wrapper"> |
||||||
|
<div class="rc-input__icon"> |
||||||
|
{{#if isReady}} {{> icon block="rc-input__icon-svg" icon="magnifier" }} {{else}} {{> loading }} {{/if}} |
||||||
|
</div> |
||||||
|
<input id="user-status-filter" type="text" class="rc-input__element" placeholder="{{_ " Search "}}" autofocus dir="auto"> |
||||||
|
</div> |
||||||
|
</form> |
||||||
|
<div class="results"> |
||||||
|
{{{_ "Showing_results" customUserStatus.length}}} |
||||||
|
</div> |
||||||
|
{{#table fixed='true' onItemClick=onTableItemClick onScroll=onTableScroll onResize=onTableResize}} |
||||||
|
<thead> |
||||||
|
<tr class="admin-table-row"> |
||||||
|
<th class="content-background-color border-component-color" width="1%"> |
||||||
|
<div class="table-fake-th"> </div> |
||||||
|
</th> |
||||||
|
<th class="content-background-color border-component-color" width="49%"> |
||||||
|
<div class="table-fake-th">{{_ "Name"}}</div> |
||||||
|
</th> |
||||||
|
<th class="content-background-color border-component-color" width="50%"> |
||||||
|
<div class="table-fake-th">{{_ "Presence"}}</div> |
||||||
|
</th> |
||||||
|
</tr> |
||||||
|
</thead> |
||||||
|
<tbody> |
||||||
|
{{#each customUserStatus}} |
||||||
|
<tr class="user-status-info row-link admin-table-row"> |
||||||
|
<td class="border-component-color"> |
||||||
|
<div class="rc-table-wrapper userStatusAdminPreview"> |
||||||
|
<div class="rc-table-info"> |
||||||
|
<span class="rc-table-title"> |
||||||
|
{{> userStatusPreview name=name}} |
||||||
|
</span> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</td> |
||||||
|
<td class="border-component-color"> |
||||||
|
<div class="rc-table-wrapper"> |
||||||
|
<div class="rc-table-info"> |
||||||
|
<span class="rc-table-title">{{name}}</span></div> |
||||||
|
</div> |
||||||
|
</td> |
||||||
|
<td class="border-component-color"> |
||||||
|
<div class="rc-table-wrapper"> |
||||||
|
<div class="rc-table-info"> |
||||||
|
<span class="rc-table-title">{{localizedStatusType}}</span></div> |
||||||
|
</div> |
||||||
|
</td> |
||||||
|
</tr> |
||||||
|
{{/each}} |
||||||
|
</tbody> |
||||||
|
{{/table}} |
||||||
|
{{#if hasMore}} |
||||||
|
<button class="button secondary load-more {{isLoading}}">{{_ "Load_more"}}</button> |
||||||
|
{{/if}} |
||||||
|
{{/unless}} |
||||||
|
</div> |
||||||
|
</section> |
||||||
|
{{#with flexData}} |
||||||
|
{{> flexTabBar}} |
||||||
|
{{/with}} |
||||||
|
</div> |
||||||
|
</template> |
||||||
@ -0,0 +1,137 @@ |
|||||||
|
import s from 'underscore.string'; |
||||||
|
import { Template } from 'meteor/templating'; |
||||||
|
import { ReactiveVar } from 'meteor/reactive-var'; |
||||||
|
import { FlowRouter } from 'meteor/kadira:flow-router'; |
||||||
|
import { Tracker } from 'meteor/tracker'; |
||||||
|
|
||||||
|
import { CustomUserStatus } from '../../../models'; |
||||||
|
import { TabBar, SideNav, RocketChatTabBar } from '../../../ui-utils'; |
||||||
|
import { t } from '../../../utils'; |
||||||
|
|
||||||
|
Template.adminUserStatus.helpers({ |
||||||
|
isReady() { |
||||||
|
if (Template.instance().ready != null) { |
||||||
|
return Template.instance().ready.get(); |
||||||
|
} |
||||||
|
return undefined; |
||||||
|
}, |
||||||
|
customUserStatus() { |
||||||
|
return Template.instance().customUserStatus().map((userStatus) => { |
||||||
|
const { _id, name, statusType } = userStatus; |
||||||
|
const localizedStatusType = statusType ? t(statusType) : ''; |
||||||
|
|
||||||
|
return { |
||||||
|
_id, |
||||||
|
name, |
||||||
|
statusType, |
||||||
|
localizedStatusType, |
||||||
|
}; |
||||||
|
}); |
||||||
|
}, |
||||||
|
isLoading() { |
||||||
|
if (Template.instance().ready != null) { |
||||||
|
if (!Template.instance().ready.get()) { |
||||||
|
return 'btn-loading'; |
||||||
|
} |
||||||
|
} |
||||||
|
}, |
||||||
|
hasMore() { |
||||||
|
if (Template.instance().limit != null) { |
||||||
|
if (typeof Template.instance().customUserStatus === 'function') { |
||||||
|
return Template.instance().limit.get() === Template.instance().customUserStatus().length; |
||||||
|
} |
||||||
|
} |
||||||
|
return false; |
||||||
|
}, |
||||||
|
flexData() { |
||||||
|
return { |
||||||
|
tabBar: Template.instance().tabBar, |
||||||
|
data: Template.instance().tabBarData.get(), |
||||||
|
}; |
||||||
|
}, |
||||||
|
}); |
||||||
|
|
||||||
|
Template.adminUserStatus.onCreated(function() { |
||||||
|
const instance = this; |
||||||
|
this.limit = new ReactiveVar(50); |
||||||
|
this.filter = new ReactiveVar(''); |
||||||
|
this.ready = new ReactiveVar(false); |
||||||
|
|
||||||
|
this.tabBar = new RocketChatTabBar(); |
||||||
|
this.tabBar.showGroup(FlowRouter.current().route.name); |
||||||
|
this.tabBarData = new ReactiveVar(); |
||||||
|
|
||||||
|
TabBar.addButton({ |
||||||
|
groups: ['user-status-custom'], |
||||||
|
id: 'add-user-status', |
||||||
|
i18nTitle: 'Custom_User_Status_Add', |
||||||
|
icon: 'plus', |
||||||
|
template: 'adminUserStatusEdit', |
||||||
|
order: 1, |
||||||
|
}); |
||||||
|
|
||||||
|
TabBar.addButton({ |
||||||
|
groups: ['user-status-custom'], |
||||||
|
id: 'admin-user-status-info', |
||||||
|
i18nTitle: 'Custom_User_Status_Info', |
||||||
|
icon: 'customize', |
||||||
|
template: 'adminUserStatusInfo', |
||||||
|
order: 2, |
||||||
|
}); |
||||||
|
|
||||||
|
this.autorun(function() { |
||||||
|
const limit = instance.limit !== null ? instance.limit.get() : 0; |
||||||
|
const subscription = instance.subscribe('fullUserStatusData', '', limit); |
||||||
|
instance.ready.set(subscription.ready()); |
||||||
|
}); |
||||||
|
|
||||||
|
this.customUserStatus = function() { |
||||||
|
const filter = instance.filter != null ? s.trim(instance.filter.get()) : ''; |
||||||
|
|
||||||
|
let query = {}; |
||||||
|
|
||||||
|
if (filter) { |
||||||
|
const filterReg = new RegExp(s.escapeRegExp(filter), 'i'); |
||||||
|
query = { $or: [{ name: filterReg }] }; |
||||||
|
} |
||||||
|
|
||||||
|
const limit = instance.limit != null ? instance.limit.get() : 0; |
||||||
|
|
||||||
|
return CustomUserStatus.find(query, { limit, sort: { name: 1 } }).fetch(); |
||||||
|
}; |
||||||
|
}); |
||||||
|
|
||||||
|
Template.adminUserStatus.onRendered(() => |
||||||
|
Tracker.afterFlush(function() { |
||||||
|
SideNav.setFlex('adminFlex'); |
||||||
|
SideNav.openFlex(); |
||||||
|
}) |
||||||
|
); |
||||||
|
|
||||||
|
Template.adminUserStatus.events({ |
||||||
|
'keydown #user-status-filter'(e) { |
||||||
|
// stop enter key
|
||||||
|
if (e.which === 13) { |
||||||
|
e.stopPropagation(); |
||||||
|
e.preventDefault(); |
||||||
|
} |
||||||
|
}, |
||||||
|
|
||||||
|
'keyup #user-status-filter'(e, t) { |
||||||
|
e.stopPropagation(); |
||||||
|
e.preventDefault(); |
||||||
|
t.filter.set(e.currentTarget.value); |
||||||
|
}, |
||||||
|
|
||||||
|
'click .user-status-info'(e, instance) { |
||||||
|
e.preventDefault(); |
||||||
|
instance.tabBarData.set(CustomUserStatus.findOne({ _id: this._id })); |
||||||
|
instance.tabBar.open('admin-user-status-info'); |
||||||
|
}, |
||||||
|
|
||||||
|
'click .load-more'(e, t) { |
||||||
|
e.preventDefault(); |
||||||
|
e.stopPropagation(); |
||||||
|
t.limit.set(t.limit.get() + 50); |
||||||
|
}, |
||||||
|
}); |
||||||
@ -0,0 +1,7 @@ |
|||||||
|
<template name="adminUserStatusEdit"> |
||||||
|
<div class="content"> |
||||||
|
<div class="user-status-view"> |
||||||
|
{{> userStatusEdit .}} |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</template> |
||||||
@ -0,0 +1,7 @@ |
|||||||
|
<template name="adminUserStatusInfo"> |
||||||
|
<div class="content"> |
||||||
|
<div class="user-status-view"> |
||||||
|
{{> userStatusInfo .}} |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</template> |
||||||
@ -0,0 +1,9 @@ |
|||||||
|
import { FlowRouter } from 'meteor/kadira:flow-router'; |
||||||
|
import { BlazeLayout } from 'meteor/kadira:blaze-layout'; |
||||||
|
|
||||||
|
FlowRouter.route('/admin/user-status-custom', { |
||||||
|
name: 'user-status-custom', |
||||||
|
action(/* params */) { |
||||||
|
BlazeLayout.render('main', { center: 'adminUserStatus' }); |
||||||
|
}, |
||||||
|
}); |
||||||
@ -0,0 +1,11 @@ |
|||||||
|
import { AdminBox } from '../../../ui-utils'; |
||||||
|
import { hasAtLeastOnePermission } from '../../../authorization'; |
||||||
|
|
||||||
|
AdminBox.addOption({ |
||||||
|
href: 'user-status-custom', |
||||||
|
i18nLabel: 'Custom_User_Status', |
||||||
|
icon: 'user', |
||||||
|
permissionGranted() { |
||||||
|
return hasAtLeastOnePermission(['manage-user-status']); |
||||||
|
}, |
||||||
|
}); |
||||||
@ -0,0 +1,42 @@ |
|||||||
|
<template name="userStatusEdit"> |
||||||
|
{{#unless hasPermission 'manage-user-status'}} |
||||||
|
<p>{{_ "You_are_not_authorized_to_view_this_page"}}</p> |
||||||
|
{{else}} |
||||||
|
<div class="about"> |
||||||
|
<form class="edit-form" autocomplete="off"> |
||||||
|
{{#if userStatus}} |
||||||
|
<h3>{{userStatus.name}}</h3> |
||||||
|
{{else}} |
||||||
|
<h3>{{_ "Custom_User_Status_Add"}}</h3> |
||||||
|
{{/if}} |
||||||
|
<div class="rc-input"> |
||||||
|
<label class="rc-input__label"> |
||||||
|
<div class="rc-input__title">{{_ "Name"}}</div> |
||||||
|
<div class="rc-input__wrapper"> |
||||||
|
<input name="name" type="text" value="{{userStatus.name}}" class="rc-input__element" placeholder="{{_ " Name "}}" autocomplete="off"> |
||||||
|
</div> |
||||||
|
</label> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="rc-input"> |
||||||
|
<label class="rc-input__label"> |
||||||
|
<div class="rc-input__title">{{_ "Presence"}}</div> |
||||||
|
<div class="rc-select"> |
||||||
|
<select class="rc-select__element" name="statusType"> |
||||||
|
<option value="">{{_ "None"}}</option> |
||||||
|
{{#each options}} |
||||||
|
<option value="{{value}}" {{selected}}>{{name}}</option> |
||||||
|
{{/each}} |
||||||
|
</select> |
||||||
|
{{> icon block="rc-select__arrow" icon="arrow-down" }} |
||||||
|
</div> |
||||||
|
</label> |
||||||
|
</div> |
||||||
|
<nav class="rc-button__group rc-button__group--stretch"> |
||||||
|
<button class='rc-button rc-button--secondary cancel' type="button"><span>{{_ "Cancel"}}</span></button> |
||||||
|
<button class='rc-button rc-button--primary save'><span>{{_ "Save"}}</span></button> |
||||||
|
</nav> |
||||||
|
</form> |
||||||
|
</div> |
||||||
|
{{/unless}} |
||||||
|
</template> |
||||||
@ -0,0 +1,115 @@ |
|||||||
|
import toastr from 'toastr'; |
||||||
|
import s from 'underscore.string'; |
||||||
|
import { Meteor } from 'meteor/meteor'; |
||||||
|
import { Template } from 'meteor/templating'; |
||||||
|
import { TAPi18n } from 'meteor/tap:i18n'; |
||||||
|
|
||||||
|
import { t, handleError } from '../../../utils'; |
||||||
|
|
||||||
|
Template.userStatusEdit.helpers({ |
||||||
|
userStatus() { |
||||||
|
return Template.instance().userStatus; |
||||||
|
}, |
||||||
|
|
||||||
|
options() { |
||||||
|
const userStatusType = this.userStatus ? this.userStatus.statusType : ''; |
||||||
|
|
||||||
|
return [{ |
||||||
|
value: 'online', |
||||||
|
name: t('Online'), |
||||||
|
selected: userStatusType === 'online' ? 'selected' : '', |
||||||
|
}, { |
||||||
|
value: 'away', |
||||||
|
name: t('Away'), |
||||||
|
selected: userStatusType === 'away' ? 'selected' : '', |
||||||
|
}, { |
||||||
|
value: 'busy', |
||||||
|
name: t('Busy'), |
||||||
|
selected: userStatusType === 'busy' ? 'selected' : '', |
||||||
|
}, { |
||||||
|
value: 'offline', |
||||||
|
name: t('Invisible'), |
||||||
|
selected: userStatusType === 'offline' ? 'selected' : '', |
||||||
|
}]; |
||||||
|
}, |
||||||
|
}); |
||||||
|
|
||||||
|
Template.userStatusEdit.events({ |
||||||
|
'click .cancel'(e, t) { |
||||||
|
e.stopPropagation(); |
||||||
|
e.preventDefault(); |
||||||
|
t.cancel(t.find('form')); |
||||||
|
}, |
||||||
|
|
||||||
|
'submit form'(e, t) { |
||||||
|
e.stopPropagation(); |
||||||
|
e.preventDefault(); |
||||||
|
t.save(e.currentTarget); |
||||||
|
}, |
||||||
|
}); |
||||||
|
|
||||||
|
Template.userStatusEdit.onCreated(function() { |
||||||
|
if (this.data != null) { |
||||||
|
this.userStatus = this.data.userStatus; |
||||||
|
} else { |
||||||
|
this.userStatus = undefined; |
||||||
|
} |
||||||
|
|
||||||
|
this.tabBar = Template.currentData().tabBar; |
||||||
|
|
||||||
|
this.cancel = (form, name) => { |
||||||
|
form.reset(); |
||||||
|
this.tabBar.close(); |
||||||
|
if (this.userStatus) { |
||||||
|
this.data.back(name); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
this.getUserStatusData = () => { |
||||||
|
const userStatusData = {}; |
||||||
|
if (this.userStatus != null) { |
||||||
|
userStatusData._id = this.userStatus._id; |
||||||
|
userStatusData.previousName = this.userStatus.name; |
||||||
|
} |
||||||
|
userStatusData.name = s.trim(this.$('#name').val()); |
||||||
|
userStatusData.statusType = s.trim(this.$('#statusType').val()); |
||||||
|
return userStatusData; |
||||||
|
}; |
||||||
|
|
||||||
|
this.validate = () => { |
||||||
|
const userStatusData = this.getUserStatusData(); |
||||||
|
|
||||||
|
const errors = []; |
||||||
|
if (!userStatusData.name) { |
||||||
|
errors.push('Name'); |
||||||
|
} |
||||||
|
|
||||||
|
for (const error of errors) { |
||||||
|
toastr.error(TAPi18n.__('error-the-field-is-required', { field: TAPi18n.__(error) })); |
||||||
|
} |
||||||
|
|
||||||
|
return errors.length === 0; |
||||||
|
}; |
||||||
|
|
||||||
|
this.save = (form) => { |
||||||
|
if (this.validate()) { |
||||||
|
const userStatusData = this.getUserStatusData(); |
||||||
|
|
||||||
|
Meteor.call('insertOrUpdateUserStatus', userStatusData, (error, result) => { |
||||||
|
if (result) { |
||||||
|
if (userStatusData._id) { |
||||||
|
toastr.success(t('Custom_User_Status_Updated_Successfully')); |
||||||
|
} else { |
||||||
|
toastr.success(t('Custom_User_Status_Added_Successfully')); |
||||||
|
} |
||||||
|
|
||||||
|
this.cancel(form, userStatusData.name); |
||||||
|
} |
||||||
|
|
||||||
|
if (error) { |
||||||
|
handleError(error); |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
}; |
||||||
|
}); |
||||||
@ -0,0 +1,22 @@ |
|||||||
|
<template name="userStatusInfo"> |
||||||
|
{{#if editingUserStatus}} |
||||||
|
{{> userStatusEdit (userStatusToEdit)}} |
||||||
|
{{else}} |
||||||
|
{{#with userStatus}} |
||||||
|
<div class="edit-form"> |
||||||
|
<div class="thumb"> |
||||||
|
{{> userStatusPreview name=name}} |
||||||
|
</div> |
||||||
|
<div class="info"> |
||||||
|
<h3 title="{{name}}">{{name}}</h3> |
||||||
|
</div> |
||||||
|
<nav class="rc-button__group rc-button__group--stretch"> |
||||||
|
{{#if hasPermission 'manage-user-status'}} |
||||||
|
<button class='rc-button rc-button--cancel delete'><span><i class='icon-trash'></i> {{_ "Delete"}}</span></button> |
||||||
|
<button class='rc-button rc-button--primary edit-user-satus'><span><i class='icon-edit'></i> {{_ "Edit"}}</span></button> |
||||||
|
{{/if}} |
||||||
|
</nav> |
||||||
|
</div> |
||||||
|
{{/with}} |
||||||
|
{{/if}} |
||||||
|
</template> |
||||||
@ -0,0 +1,117 @@ |
|||||||
|
import { Meteor } from 'meteor/meteor'; |
||||||
|
import { Template } from 'meteor/templating'; |
||||||
|
import { ReactiveVar } from 'meteor/reactive-var'; |
||||||
|
|
||||||
|
import { t, handleError } from '../../../utils'; |
||||||
|
import { modal } from '../../../ui-utils'; |
||||||
|
|
||||||
|
Template.userStatusInfo.helpers({ |
||||||
|
name() { |
||||||
|
const userStatus = Template.instance().userStatus.get(); |
||||||
|
return userStatus.name; |
||||||
|
}, |
||||||
|
|
||||||
|
userStatus() { |
||||||
|
return Template.instance().userStatus.get(); |
||||||
|
}, |
||||||
|
|
||||||
|
editingUserStatus() { |
||||||
|
return Template.instance().editingUserStatus.get(); |
||||||
|
}, |
||||||
|
|
||||||
|
userStatusToEdit() { |
||||||
|
const instance = Template.instance(); |
||||||
|
return { |
||||||
|
tabBar: this.tabBar, |
||||||
|
userStatus: instance.userStatus.get(), |
||||||
|
back(name) { |
||||||
|
instance.editingUserStatus.set(); |
||||||
|
|
||||||
|
if (name != null) { |
||||||
|
const userStatus = instance.userStatus.get(); |
||||||
|
if (userStatus != null && userStatus.name != null && userStatus.name !== name) { |
||||||
|
return instance.loadedName.set(name); |
||||||
|
} |
||||||
|
} |
||||||
|
}, |
||||||
|
}; |
||||||
|
}, |
||||||
|
}); |
||||||
|
|
||||||
|
Template.userStatusInfo.events({ |
||||||
|
'click .thumb'(e) { |
||||||
|
$(e.currentTarget).toggleClass('bigger'); |
||||||
|
}, |
||||||
|
|
||||||
|
'click .delete'(e, instance) { |
||||||
|
e.stopPropagation(); |
||||||
|
e.preventDefault(); |
||||||
|
const userStatus = instance.userStatus.get(); |
||||||
|
if (userStatus != null) { |
||||||
|
const { _id } = userStatus; |
||||||
|
modal.open({ |
||||||
|
title: t('Are_you_sure'), |
||||||
|
text: t('Custom_User_Status_Delete_Warning'), |
||||||
|
type: 'warning', |
||||||
|
showCancelButton: true, |
||||||
|
confirmButtonColor: '#DD6B55', |
||||||
|
confirmButtonText: t('Yes_delete_it'), |
||||||
|
cancelButtonText: t('Cancel'), |
||||||
|
closeOnConfirm: false, |
||||||
|
html: false, |
||||||
|
}, function() { |
||||||
|
Meteor.call('deleteCustomUserStatus', _id, (error/* , result */) => { |
||||||
|
if (error) { |
||||||
|
return handleError(error); |
||||||
|
} |
||||||
|
|
||||||
|
modal.open({ |
||||||
|
title: t('Deleted'), |
||||||
|
text: t('Custom_User_Status_Has_Been_Deleted'), |
||||||
|
type: 'success', |
||||||
|
timer: 2000, |
||||||
|
showConfirmButton: false, |
||||||
|
}); |
||||||
|
|
||||||
|
instance.tabBar.close(); |
||||||
|
}); |
||||||
|
}); |
||||||
|
} |
||||||
|
}, |
||||||
|
|
||||||
|
'click .edit-user-satus'(e, instance) { |
||||||
|
e.stopPropagation(); |
||||||
|
e.preventDefault(); |
||||||
|
|
||||||
|
instance.editingUserStatus.set(instance.userStatus.get()._id); |
||||||
|
}, |
||||||
|
}); |
||||||
|
|
||||||
|
Template.userStatusInfo.onCreated(function() { |
||||||
|
this.userStatus = new ReactiveVar(); |
||||||
|
this.editingUserStatus = new ReactiveVar(); |
||||||
|
this.loadedName = new ReactiveVar(); |
||||||
|
this.tabBar = Template.currentData().tabBar; |
||||||
|
|
||||||
|
this.autorun(() => { |
||||||
|
const data = Template.currentData(); |
||||||
|
if (data != null && data.clear != null) { |
||||||
|
this.clear = data.clear; |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
this.autorun(() => { |
||||||
|
const data = Template.currentData(); |
||||||
|
const userStatus = this.userStatus.get(); |
||||||
|
if (userStatus != null && userStatus.name != null) { |
||||||
|
this.loadedName.set(userStatus.name); |
||||||
|
} else if (data != null && data.name != null) { |
||||||
|
this.loadedName.set(data.name); |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
this.autorun(() => { |
||||||
|
const data = Template.currentData(); |
||||||
|
this.userStatus.set(data); |
||||||
|
}); |
||||||
|
}); |
||||||
@ -0,0 +1,5 @@ |
|||||||
|
<template name="userStatusPreview"> |
||||||
|
<div class="userStatusAdminPreview"> |
||||||
|
<div class="userStatusAdminPreview" data-user-status="{{this.name}}"></div> |
||||||
|
</div> |
||||||
|
</template> |
||||||
@ -0,0 +1,17 @@ |
|||||||
|
import './admin/adminUserStatus.html'; |
||||||
|
import './admin/adminUserStatus'; |
||||||
|
import './admin/adminUserStatusEdit.html'; |
||||||
|
import './admin/adminUserStatusInfo.html'; |
||||||
|
import './admin/userStatusEdit.html'; |
||||||
|
import './admin/userStatusEdit'; |
||||||
|
import './admin/userStatusInfo.html'; |
||||||
|
import './admin/userStatusInfo'; |
||||||
|
import './admin/userStatusPreview.html'; |
||||||
|
import './admin/route'; |
||||||
|
import './admin/startup'; |
||||||
|
|
||||||
|
import './notifications/deleteCustomUserStatus'; |
||||||
|
import './notifications/updateCustomUserStatus'; |
||||||
|
|
||||||
|
export { userStatus } from './lib/userStatus'; |
||||||
|
export { deleteCustomUserStatus, updateCustomUserStatus } from './lib/customUserStatus'; |
||||||
@ -0,0 +1,54 @@ |
|||||||
|
import { Meteor } from 'meteor/meteor'; |
||||||
|
|
||||||
|
import { userStatus } from './userStatus'; |
||||||
|
|
||||||
|
userStatus.packages.customUserStatus = { |
||||||
|
list: [], |
||||||
|
}; |
||||||
|
|
||||||
|
export const deleteCustomUserStatus = function(customUserStatusData) { |
||||||
|
delete userStatus.list[customUserStatusData._id]; |
||||||
|
|
||||||
|
const arrayIndex = userStatus.packages.customUserStatus.list.indexOf(customUserStatusData._id); |
||||||
|
if (arrayIndex !== -1) { |
||||||
|
userStatus.packages.customUserStatusData.list.splice(arrayIndex, 1); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
export const updateCustomUserStatus = function(customUserStatusData) { |
||||||
|
const newUserStatus = { |
||||||
|
name: customUserStatusData.name, |
||||||
|
id: customUserStatusData._id, |
||||||
|
statusType: customUserStatusData.statusType, |
||||||
|
localizeName: false, |
||||||
|
}; |
||||||
|
|
||||||
|
const arrayIndex = userStatus.packages.customUserStatus.list.indexOf(newUserStatus.id); |
||||||
|
if (arrayIndex === -1) { |
||||||
|
userStatus.packages.customUserStatus.list.push(newUserStatus); |
||||||
|
} else { |
||||||
|
userStatus.packages.customUserStatus.list[arrayIndex] = newUserStatus; |
||||||
|
} |
||||||
|
|
||||||
|
userStatus.list[newUserStatus.id] = newUserStatus; |
||||||
|
}; |
||||||
|
|
||||||
|
Meteor.startup(() => |
||||||
|
Meteor.call('listCustomUserStatus', (error, result) => { |
||||||
|
if (!result) { |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
for (const customStatus of result) { |
||||||
|
const newUserStatus = { |
||||||
|
name: customStatus.name, |
||||||
|
id: customStatus._id, |
||||||
|
statusType: customStatus.statusType, |
||||||
|
localizeName: false, |
||||||
|
}; |
||||||
|
|
||||||
|
userStatus.packages.customUserStatus.list.push(newUserStatus); |
||||||
|
userStatus.list[newUserStatus.id] = newUserStatus; |
||||||
|
} |
||||||
|
}) |
||||||
|
); |
||||||
@ -0,0 +1,36 @@ |
|||||||
|
export const userStatus = { |
||||||
|
packages: { |
||||||
|
base: { |
||||||
|
render(html) { |
||||||
|
return html; |
||||||
|
}, |
||||||
|
}, |
||||||
|
}, |
||||||
|
|
||||||
|
list: { |
||||||
|
online: { |
||||||
|
name: 'online', |
||||||
|
localizeName: true, |
||||||
|
id: 'online', |
||||||
|
statusType: 'online', |
||||||
|
}, |
||||||
|
away: { |
||||||
|
name: 'away', |
||||||
|
localizeName: true, |
||||||
|
id: 'away', |
||||||
|
statusType: 'away', |
||||||
|
}, |
||||||
|
busy: { |
||||||
|
name: 'busy', |
||||||
|
localizeName: true, |
||||||
|
id: 'busy', |
||||||
|
statusType: 'busy', |
||||||
|
}, |
||||||
|
invisible: { |
||||||
|
name: 'invisible', |
||||||
|
localizeName: true, |
||||||
|
id: 'offline', |
||||||
|
statusType: 'offline', |
||||||
|
}, |
||||||
|
}, |
||||||
|
}; |
||||||
@ -0,0 +1,8 @@ |
|||||||
|
import { Meteor } from 'meteor/meteor'; |
||||||
|
|
||||||
|
import { deleteCustomUserStatus } from '../lib/customUserStatus'; |
||||||
|
import { Notifications } from '../../../notifications'; |
||||||
|
|
||||||
|
Meteor.startup(() => |
||||||
|
Notifications.onLogged('deleteCustomUserStatus', (data) => deleteCustomUserStatus(data.userStatusData)) |
||||||
|
); |
||||||
@ -0,0 +1,8 @@ |
|||||||
|
import { Meteor } from 'meteor/meteor'; |
||||||
|
|
||||||
|
import { updateCustomUserStatus } from '../lib/customUserStatus'; |
||||||
|
import { Notifications } from '../../../notifications'; |
||||||
|
|
||||||
|
Meteor.startup(() => |
||||||
|
Notifications.onLogged('updateCustomUserStatus', (data) => updateCustomUserStatus(data.userStatusData)) |
||||||
|
); |
||||||
@ -0,0 +1,8 @@ |
|||||||
|
import { Meteor } from 'meteor/meteor'; |
||||||
|
|
||||||
|
if (Meteor.isClient) { |
||||||
|
module.exports = require('./client/index.js'); |
||||||
|
} |
||||||
|
if (Meteor.isServer) { |
||||||
|
module.exports = require('./server/index.js'); |
||||||
|
} |
||||||
@ -0,0 +1,6 @@ |
|||||||
|
import './methods/deleteCustomUserStatus'; |
||||||
|
import './methods/insertOrUpdateUserStatus'; |
||||||
|
import './methods/listCustomUserStatus'; |
||||||
|
import './methods/setUserStatus'; |
||||||
|
|
||||||
|
import './publications/fullUserStatusData'; |
||||||
@ -0,0 +1,26 @@ |
|||||||
|
import { Meteor } from 'meteor/meteor'; |
||||||
|
|
||||||
|
import { hasPermission } from '../../../authorization'; |
||||||
|
import { Notifications } from '../../../notifications'; |
||||||
|
import { CustomUserStatus } from '../../../models'; |
||||||
|
|
||||||
|
Meteor.methods({ |
||||||
|
deleteCustomUserStatus(userStatusID) { |
||||||
|
let userStatus = null; |
||||||
|
|
||||||
|
if (hasPermission(this.userId, 'manage-user-status')) { |
||||||
|
userStatus = CustomUserStatus.findOneById(userStatusID); |
||||||
|
} else { |
||||||
|
throw new Meteor.Error('not_authorized'); |
||||||
|
} |
||||||
|
|
||||||
|
if (userStatus == null) { |
||||||
|
throw new Meteor.Error('Custom_User_Status_Error_Invalid_User_Status', 'Invalid user status', { method: 'deleteCustomUserStatus' }); |
||||||
|
} |
||||||
|
|
||||||
|
CustomUserStatus.removeById(userStatusID); |
||||||
|
Notifications.notifyLogged('deleteCustomUserStatus', { userStatusData: userStatus }); |
||||||
|
|
||||||
|
return true; |
||||||
|
}, |
||||||
|
}); |
||||||
@ -0,0 +1,70 @@ |
|||||||
|
import { Meteor } from 'meteor/meteor'; |
||||||
|
import s from 'underscore.string'; |
||||||
|
|
||||||
|
import { hasPermission } from '../../../authorization'; |
||||||
|
import { Notifications } from '../../../notifications'; |
||||||
|
import { CustomUserStatus } from '../../../models'; |
||||||
|
|
||||||
|
Meteor.methods({ |
||||||
|
insertOrUpdateUserStatus(userStatusData) { |
||||||
|
if (!hasPermission(this.userId, 'manage-user-status')) { |
||||||
|
throw new Meteor.Error('not_authorized'); |
||||||
|
} |
||||||
|
|
||||||
|
if (!s.trim(userStatusData.name)) { |
||||||
|
throw new Meteor.Error('error-the-field-is-required', 'The field Name is required', { method: 'insertOrUpdateUserStatus', field: 'Name' }); |
||||||
|
} |
||||||
|
|
||||||
|
// allow all characters except >, <, &, ", '
|
||||||
|
// more practical than allowing specific sets of characters; also allows foreign languages
|
||||||
|
const nameValidation = /[><&"']/; |
||||||
|
|
||||||
|
if (nameValidation.test(userStatusData.name)) { |
||||||
|
throw new Meteor.Error('error-input-is-not-a-valid-field', `${ userStatusData.name } is not a valid name`, { method: 'insertOrUpdateUserStatus', input: userStatusData.name, field: 'Name' }); |
||||||
|
} |
||||||
|
|
||||||
|
let matchingResults = []; |
||||||
|
|
||||||
|
if (userStatusData._id) { |
||||||
|
matchingResults = CustomUserStatus.findByNameExceptId(userStatusData.name, userStatusData._id).fetch(); |
||||||
|
} else { |
||||||
|
matchingResults = CustomUserStatus.findByName(userStatusData.name).fetch(); |
||||||
|
} |
||||||
|
|
||||||
|
if (matchingResults.length > 0) { |
||||||
|
throw new Meteor.Error('Custom_User_Status_Error_Name_Already_In_Use', 'The custom user status name is already in use', { method: 'insertOrUpdateUserStatus' }); |
||||||
|
} |
||||||
|
|
||||||
|
const validStatusTypes = ['online', 'away', 'busy', 'offline']; |
||||||
|
if (userStatusData.statusType && validStatusTypes.indexOf(userStatusData.statusType) < 0) { |
||||||
|
throw new Meteor.Error('error-input-is-not-a-valid-field', `${ userStatusData.statusType } is not a valid status type`, { method: 'insertOrUpdateUserStatus', input: userStatusData.statusType, field: 'StatusType' }); |
||||||
|
} |
||||||
|
|
||||||
|
if (!userStatusData._id) { |
||||||
|
// insert user status
|
||||||
|
const createUserStatus = { |
||||||
|
name: userStatusData.name, |
||||||
|
statusType: userStatusData.statusType || null, |
||||||
|
}; |
||||||
|
|
||||||
|
const _id = CustomUserStatus.create(createUserStatus); |
||||||
|
|
||||||
|
Notifications.notifyLogged('updateCustomUserStatus', { userStatusData: createUserStatus }); |
||||||
|
|
||||||
|
return _id; |
||||||
|
} |
||||||
|
|
||||||
|
// update User status
|
||||||
|
if (userStatusData.name !== userStatusData.previousName) { |
||||||
|
CustomUserStatus.setName(userStatusData._id, userStatusData.name); |
||||||
|
} |
||||||
|
|
||||||
|
if (userStatusData.statusType !== userStatusData.previousStatusType) { |
||||||
|
CustomUserStatus.setStatusType(userStatusData._id, userStatusData.statusType); |
||||||
|
} |
||||||
|
|
||||||
|
Notifications.notifyLogged('updateCustomUserStatus', { userStatusData }); |
||||||
|
|
||||||
|
return true; |
||||||
|
}, |
||||||
|
}); |
||||||
@ -0,0 +1,9 @@ |
|||||||
|
import { Meteor } from 'meteor/meteor'; |
||||||
|
|
||||||
|
import { CustomUserStatus } from '../../../models'; |
||||||
|
|
||||||
|
Meteor.methods({ |
||||||
|
listCustomUserStatus() { |
||||||
|
return CustomUserStatus.find({}).fetch(); |
||||||
|
}, |
||||||
|
}); |
||||||
@ -0,0 +1,30 @@ |
|||||||
|
import { Meteor } from 'meteor/meteor'; |
||||||
|
import { check } from 'meteor/check'; |
||||||
|
|
||||||
|
import { settings } from '../../../settings'; |
||||||
|
import { RateLimiter, setStatusMessage } from '../../../lib'; |
||||||
|
|
||||||
|
Meteor.methods({ |
||||||
|
setUserStatus(statusType, statusText) { |
||||||
|
if (statusType) { |
||||||
|
Meteor.call('UserPresence:setDefaultStatus', statusType); |
||||||
|
} |
||||||
|
|
||||||
|
if (statusText || statusText === '') { |
||||||
|
check(statusText, String); |
||||||
|
|
||||||
|
if (!settings.get('Accounts_AllowUserStatusMessageChange')) { |
||||||
|
throw new Meteor.Error('error-not-allowed', 'Not allowed', { |
||||||
|
method: 'setUserStatus', |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
const userId = Meteor.userId(); |
||||||
|
setStatusMessage(userId, statusText); |
||||||
|
} |
||||||
|
}, |
||||||
|
}); |
||||||
|
|
||||||
|
RateLimiter.limitMethod('setUserStatus', 1, 1000, { |
||||||
|
userId: () => true, |
||||||
|
}); |
||||||
@ -0,0 +1,30 @@ |
|||||||
|
import s from 'underscore.string'; |
||||||
|
import { Meteor } from 'meteor/meteor'; |
||||||
|
|
||||||
|
import { CustomUserStatus } from '../../../models'; |
||||||
|
|
||||||
|
Meteor.publish('fullUserStatusData', function(filter, limit) { |
||||||
|
if (!this.userId) { |
||||||
|
return this.ready(); |
||||||
|
} |
||||||
|
|
||||||
|
const fields = { |
||||||
|
name: 1, |
||||||
|
statusType: 1, |
||||||
|
}; |
||||||
|
|
||||||
|
filter = s.trim(filter); |
||||||
|
|
||||||
|
const options = { |
||||||
|
fields, |
||||||
|
limit, |
||||||
|
sort: { name: 1 }, |
||||||
|
}; |
||||||
|
|
||||||
|
if (filter) { |
||||||
|
const filterReg = new RegExp(s.escapeRegExp(filter), 'i'); |
||||||
|
return CustomUserStatus.findByName(filterReg, options); |
||||||
|
} |
||||||
|
|
||||||
|
return CustomUserStatus.find({}, options); |
||||||
|
}); |
||||||
Loading…
Reference in new issue