Skip to content

Commit

Permalink
Add the command preview rest api and make the previews selectable via…
Browse files Browse the repository at this point in the history
… the keyboard
  • Loading branch information
graywolf336 committed May 19, 2018
1 parent 6f73bfb commit fafc6da
Show file tree
Hide file tree
Showing 3 changed files with 206 additions and 11 deletions.
80 changes: 79 additions & 1 deletion packages/rocketchat-api/server/v1/commands.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ RocketChat.API.v1.addRoute('commands.run', { authRequired: true }, {
}

if (typeof body.roomId !== 'string') {
return RocketChat.API.v1.failure('The room\'s id where to execute this command must provided and be a string.');
return RocketChat.API.v1.failure('The room\'s id where to execute this command must be provided and be a string.');
}

const cmd = body.command.toLowerCase();
Expand All @@ -84,3 +84,81 @@ RocketChat.API.v1.addRoute('commands.run', { authRequired: true }, {
return RocketChat.API.v1.success({ result });
}
});

RocketChat.API.v1.addRoute('commands.preview', { authRequired: true }, {
// Expects these query params: command: 'giphy', params: 'mine', roomId: 'value'
get() {
const query = this.queryParams;
const user = this.getLoggedInUser();

if (typeof query.command !== 'string') {
return RocketChat.API.v1.failure('You must provide a command to get the previews from.');
}

if (query.params && typeof query.params !== 'string') {
return RocketChat.API.v1.failure('The parameters for the command must be a single string.');
}

if (typeof query.roomId !== 'string') {
return RocketChat.API.v1.failure('The room\'s id where the previews are being displayed must be provided and be a string.');
}

const cmd = query.command.toLowerCase();
if (!RocketChat.slashCommands.commands[query.command.toLowerCase()]) {
return RocketChat.API.v1.failure('The command provided does not exist (or is disabled).');
}

// This will throw an error if they can't or the room is invalid
Meteor.call('canAccessRoom', query.roomId, user._id);

const params = query.params ? query.params : '';

let preview;
Meteor.runAsUser(user._id, () => {
preview = Meteor.call('getSlashCommandPreviews', { cmd, params, msg: { rid: query.roomId } });
});

return RocketChat.API.v1.success({ preview });
},
// Expects a body format of: { command: 'giphy', params: 'mine', roomId: 'value', previewItem: { id: 'sadf8' type: 'image', value: 'https://dev.null/gif } }
post() {
const body = this.bodyParams;
const user = this.getLoggedInUser();

if (typeof body.command !== 'string') {
return RocketChat.API.v1.failure('You must provide a command to run the preview item on.');
}

if (body.params && typeof body.params !== 'string') {
return RocketChat.API.v1.failure('The parameters for the command must be a single string.');
}

if (typeof body.roomId !== 'string') {
return RocketChat.API.v1.failure('The room\'s id where the preview is being executed in must be provided and be a string.');
}

if (typeof body.previewItem === 'undefined') {
return RocketChat.API.v1.failure('The preview item being executed must be provided.');
}

if (!body.previewItem.id || !body.previewItem.type || typeof body.previewItem.value === 'undefined') {
return RocketChat.API.v1.failure('The preview item being executed is in the wrong format.');
}

const cmd = body.command.toLowerCase();
if (!RocketChat.slashCommands.commands[body.command.toLowerCase()]) {
return RocketChat.API.v1.failure('The command provided does not exist (or is disabled).');
}

// This will throw an error if they can't or the room is invalid
Meteor.call('canAccessRoom', body.roomId, user._id);

const params = body.params ? body.params : '';

Meteor.runAsUser(user._id, () => {
Meteor.call('executeSlashCommandPreview', { cmd, params, msg: { rid: body.roomId } }, body.previewItem);
});

return RocketChat.API.v1.success();
}
});
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
</div>
<div class="message-popup-items">
{{#each items}}
<div class="popup-item" data-id="{id}}">
<div class="popup-item" data-id="{{id}}">
{{#if isType 'image' type}}
<img src="{{value}}" alt="{{value}}">
{{/if}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ Template.messagePopupSlashCommandPreview.onCreated(function() {
this.isLoading = new ReactiveVar(true);
this.preview = new ReactiveVar();
this.selectedItem = new ReactiveVar();
this.commandName = new ReactiveVar('');
this.commandArgs = new ReactiveVar('');

// regex ensures a command is entered into the input
Expand All @@ -41,27 +42,96 @@ Template.messagePopupSlashCommandPreview.onCreated(function() {

const template = this;
template.fetchPreviews = _.debounce(function _previewFetcher(cmd, args) {
const command = cmd;
const params = args;
Meteor.call('getSlashCommandPreviews', { cmd, params, msg: { rid: Session.get('openedRoom') } }, function(err, preview) {
if (err) {
return;
}

if (!preview || !Array.isArray(preview.items) || preview.items.length === 0) {
// TODO: Display no results found
template.open.set(false);
return;
}

template.preview.set(preview);
template.commandName.set(command);
template.commandArgs.set(params);
template.isLoading.set(false);

Meteor.defer(function() {
template.verifySelection();
});
});
}, 500);

template.enterKeyAction = () => {
const current = template.find('.popup-item.selected');

if (!current) {
return;
}

const selectedId = current.getAttribute('data-id');

if (!selectedId) {
return;
}

const cmd = template.commandName.curValue;
const params = template.commandArgs.curValue;

if (!cmd || !params) {
return;
}

const item = template.preview.curValue.items.find((i) => i.id === selectedId);

if (!item) {
return;
}

Meteor.call('executeSlashCommandPreview', { cmd, params, msg: { rid: Session.get('openedRoom') } }, item, function(err) {
if (err) {
console.warn(err);
}
});

template.open.set(false);
template.inputBox.value = '';
template.preview.set();
template.commandName.set('');
template.commandArgs.set('');
};

template.verifySelection = () => {
const current = template.find('.popup-item.selected');

if (!current) {
const first = template.find('.popup-item');

if (first) {
first.className += ' selected sidebar-item__popup-active';
}
}
};

// Typing data
template.onInputKeyup = (event) => {
if (template.open.curValue === true && event.which === keys.ESC) {
template.open.set(false);
$('.toolbar').css('display', 'none'); // TODO will it be a different class?
$('.toolbar').css('display', 'none');
event.preventDefault();
event.stopPropagation();
return;
}

if (event.which === keys.ARROW_UP || event.which === keys.ARROW_DOWN) {
// Arrow up and down are for navigating the choices
return;
}

const inputValueAtCursor = template.inputBox.value.substr(0, getCursorPosition(template.inputBox));

if (!template.matchSelectorRegex.test(inputValueAtCursor)) {
Expand Down Expand Up @@ -90,21 +160,66 @@ Template.messagePopupSlashCommandPreview.onCreated(function() {
return;
}

// If they haven't changed a thing, show what we already got
if (template.commandName.curValue === cmd && template.commandArgs.curValue === args && template.preview.curValue) {
template.isLoading.set(false);
template.open.set(true);
return;
}

template.isLoading.set(true);
template.open.set(true);

// Fetch and display them
template.fetchPreviews(cmd, args);
};

// Using the keyboard to navigate the options
template.onInputKeydown = (event) => {
if (!template.open.curValue || template.isLoading.curValue) {
return;
}

if (event.which === keys.ENTER) { // || event.which === keys.TAB) { <-- does using tab to select make sense?
template.enterKeyAction();
event.preventDefault();
event.stopPropagation();
return;
}

// TODO: Evaluate this
// if (event.which !== keys.ARROW_UP && event.which !== keys.ARROW_DOWN) {
// return Meteor.defer(function() {
// template.verifySelection();
// });
// }
if (event.which === keys.ARROW_UP) {
template.up();
event.preventDefault();
event.stopPropagation();
return;
}

if (event.which === keys.ARROW_DOWN) {
template.down();
event.preventDefault();
event.stopPropagation();
}
};

console.log('hello and here be the template', template);
template.up = () => {
const current = template.find('.popup-item.selected');
const previous = $(current).prev('.popup-item')[0] || template.find('.popup-item:last-child');

if (previous != null) {
current.className = current.className.replace(/\sselected/, '').replace('sidebar-item__popup-active', '');
previous.className += ' selected sidebar-item__popup-active';
}
};

template.down = () => {
const current = template.find('.popup-item.selected');
const next = $(current).next('.popup-item')[0] || template.find('.popup-item');

if (next && next.classList.contains('popup-item')) {
current.className = current.className.replace(/\sselected/, '').replace('sidebar-item__popup-active', '');
next.className += ' selected sidebar-item__popup-active';
}
};
});

Template.messagePopupSlashCommandPreview.onRendered(function _messagePopupSlashCommandPreviewRendered() {
Expand All @@ -114,10 +229,12 @@ Template.messagePopupSlashCommandPreview.onRendered(function _messagePopupSlashC

this.inputBox = this.data.getInput();
$(this.inputBox).on('keyup', this.onInputKeyup.bind(this));
$(this.inputBox).on('keydown', this.onInputKeydown.bind(this));
});

Template.messagePopupSlashCommandPreview.onDestroyed(function() {
$(this.input).off('keyup', this.onInputKeyup);
$(this.inputBox).off('keyup', this.onInputKeyup);
$(this.inputBox).off('keydown', this.onInputKeydown);
});

Template.messagePopupSlashCommandPreview.helpers({
Expand Down

0 comments on commit fafc6da

Please sign in to comment.