a80db339f8
Requires turning IrcUtils into an Angular service, because the global variable trick won't work with use strict. Reuse is still easily possible by removing the angular wrapping around it.
317 lines
13 KiB
JavaScript
317 lines
13 KiB
JavaScript
(function() {
|
|
'use strict';
|
|
|
|
var weechat = angular.module('weechat');
|
|
|
|
weechat.directive('inputBar', function() {
|
|
|
|
return {
|
|
|
|
templateUrl: 'directives/input.html',
|
|
|
|
scope: {
|
|
inputId: '@inputId',
|
|
command: '=command'
|
|
},
|
|
|
|
controller: ['$rootScope', '$scope', '$element', '$log', 'connection', 'models', 'IrcUtils', function($rootScope,
|
|
$scope,
|
|
$element, //XXX do we need this? don't seem to be using it
|
|
$log,
|
|
connection, //XXX we should eliminate this dependency and use signals instead
|
|
models,
|
|
IrcUtils) {
|
|
|
|
/*
|
|
* Returns the input element
|
|
*/
|
|
$scope.getInputNode = function() {
|
|
return document.querySelector('textarea#' + $scope.inputId);
|
|
};
|
|
|
|
$scope.hideSidebar = function() {
|
|
$rootScope.hideSidebar();
|
|
};
|
|
|
|
$scope.completeNick = function() {
|
|
// input DOM node
|
|
var inputNode = $scope.getInputNode();
|
|
|
|
// get current caret position
|
|
var caretPos = inputNode.selectionStart;
|
|
|
|
// get current active buffer
|
|
var activeBuffer = models.getActiveBuffer();
|
|
|
|
// Empty input makes $scope.command undefined -- use empty string instead
|
|
var input = $scope.command || '';
|
|
|
|
// complete nick
|
|
var nickComp = IrcUtils.completeNick(input, caretPos, $scope.iterCandidate,
|
|
activeBuffer.getNicklistByTime(), ':');
|
|
|
|
// remember iteration candidate
|
|
$scope.iterCandidate = nickComp.iterCandidate;
|
|
|
|
// update current input
|
|
$scope.command = nickComp.text;
|
|
|
|
// update current caret position
|
|
setTimeout(function() {
|
|
inputNode.focus();
|
|
inputNode.setSelectionRange(nickComp.caretPos, nickComp.caretPos);
|
|
}, 0);
|
|
};
|
|
|
|
|
|
// Send the message to the websocket
|
|
$scope.sendMessage = function() {
|
|
//XXX Use a signal here
|
|
var ab = models.getActiveBuffer();
|
|
|
|
// It's undefined early in the lifecycle of the program.
|
|
// Don't send empty commands
|
|
if($scope.command !== undefined && $scope.command !== '') {
|
|
|
|
// log to buffer history
|
|
ab.addToHistory($scope.command);
|
|
|
|
// Split the command into multiple commands based on line breaks
|
|
_.each($scope.command.split(/\r?\n/), function(line) {
|
|
connection.sendMessage(line);
|
|
});
|
|
|
|
// Check for /clear command
|
|
if ($scope.command === '/buffer clear' || $scope.command === '/c') {
|
|
$log.debug('Clearing lines');
|
|
ab.clear();
|
|
}
|
|
|
|
// Empty the input after it's sent
|
|
$scope.command = '';
|
|
}
|
|
|
|
$scope.getInputNode().focus();
|
|
};
|
|
|
|
//XXX THIS DOES NOT BELONG HERE!
|
|
$rootScope.addMention = function(prefix) {
|
|
// Extract nick from bufferline prefix
|
|
var nick = prefix[prefix.length - 1].text;
|
|
|
|
var newValue = $scope.command || ''; // can be undefined, in that case, use the empty string
|
|
var addColon = newValue.length === 0;
|
|
if (newValue.length > 0) {
|
|
// Try to determine if it's a sequence of nicks
|
|
var trimmedValue = newValue.trim();
|
|
if (trimmedValue.charAt(trimmedValue.length - 1) === ':') {
|
|
// get last word
|
|
var lastSpace = trimmedValue.lastIndexOf(' ') + 1;
|
|
var lastWord = trimmedValue.slice(lastSpace, trimmedValue.length - 1);
|
|
var nicklist = models.getActiveBuffer().getNicklistByTime();
|
|
// check against nicklist to see if it's a list of highlights
|
|
for (var index in nicklist) {
|
|
if (nicklist[index].name === lastWord) {
|
|
// It's another highlight!
|
|
newValue = newValue.slice(0, newValue.lastIndexOf(':')) + ' ';
|
|
addColon = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add a space before the nick if there isn't one already
|
|
// Last char might have changed above, so re-check
|
|
if (newValue.charAt(newValue.length - 1) !== ' ') {
|
|
newValue += ' ';
|
|
}
|
|
}
|
|
// Add highlight to nicklist
|
|
newValue += nick;
|
|
if (addColon) {
|
|
newValue += ': ';
|
|
}
|
|
$scope.command = newValue;
|
|
$scope.getInputNode().focus();
|
|
};
|
|
|
|
|
|
// Handle key presses in the input bar
|
|
$rootScope.handleKeyPress = function($event) {
|
|
// don't do anything if not connected
|
|
if (!$rootScope.connected) {
|
|
return true;
|
|
}
|
|
|
|
var inputNode = $scope.getInputNode();
|
|
|
|
// Support different browser quirks
|
|
var code = $event.keyCode ? $event.keyCode : $event.charCode;
|
|
|
|
// any other key than Tab resets nick completion iteration
|
|
var tmpIterCandidate = $scope.iterCandidate;
|
|
$scope.iterCandidate = null;
|
|
|
|
// Left Alt+[0-9] -> jump to buffer
|
|
if ($event.altKey && !$event.ctrlKey && (code > 47 && code < 58)) {
|
|
if (code === 48) {
|
|
code = 58;
|
|
}
|
|
|
|
var bufferNumber = code - 48 - 1 ;
|
|
// Map the buffers to only their numbers and IDs so we don't have to
|
|
// copy the entire (possibly very large) buffer object, and then sort
|
|
// the buffers according to their WeeChat number
|
|
var sortedBuffers = _.map(models.getBuffers(), function(buffer) {
|
|
return [buffer.number, buffer.id];
|
|
}).sort(function(left, right) {
|
|
// By default, Array.prototype.sort() sorts alphabetically.
|
|
// Pass an ordering function to sort by first element.
|
|
return left[0] - right[0];
|
|
});
|
|
var activeBufferId = sortedBuffers[bufferNumber];
|
|
if (activeBufferId) {
|
|
$scope.$parent.setActiveBuffer(activeBufferId[1]);
|
|
$event.preventDefault();
|
|
}
|
|
}
|
|
|
|
// Tab -> nick completion
|
|
if (code === 9 && !$event.altKey && !$event.ctrlKey) {
|
|
$event.preventDefault();
|
|
$scope.iterCandidate = tmpIterCandidate;
|
|
$scope.completeNick();
|
|
return true;
|
|
}
|
|
|
|
// Left Alt+n -> toggle nicklist
|
|
if ($event.altKey && !$event.ctrlKey && code === 78) {
|
|
$event.preventDefault();
|
|
$rootScope.toggleNicklist();
|
|
return true;
|
|
}
|
|
|
|
// Alt+A -> switch to buffer with activity
|
|
if ($event.altKey && (code === 97 || code === 65)) {
|
|
$event.preventDefault();
|
|
$rootScope.switchToActivityBuffer();
|
|
return true;
|
|
}
|
|
|
|
// Alt+L -> focus on input bar
|
|
if ($event.altKey && (code === 76 || code === 108)) {
|
|
$event.preventDefault();
|
|
inputNode.focus();
|
|
inputNode.setSelectionRange($scope.command.length, $scope.command.length);
|
|
return true;
|
|
}
|
|
|
|
// Alt+< -> switch to previous buffer
|
|
if ($event.altKey && (code === 60 || code === 226)) {
|
|
var previousBuffer = models.getPreviousBuffer();
|
|
if (previousBuffer) {
|
|
models.setActiveBuffer(previousBuffer.id);
|
|
$event.preventDefault();
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// Double-tap Escape -> disconnect
|
|
if (code === 27) {
|
|
$event.preventDefault();
|
|
|
|
// Check if a modal is visible. If so, close it instead of disconnecting
|
|
var modals = document.querySelectorAll('.gb-modal');
|
|
for (var modalId = 0; modalId < modals.length; modalId++) {
|
|
if (modals[modalId].getAttribute('data-state') === 'visible') {
|
|
modals[modalId].setAttribute('data-state', 'hidden');
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (typeof $scope.lastEscape !== "undefined" && (Date.now() - $scope.lastEscape) <= 500) {
|
|
// Double-tap
|
|
connection.disconnect();
|
|
}
|
|
$scope.lastEscape = Date.now();
|
|
return true;
|
|
}
|
|
|
|
// Alt+G -> focus on buffer filter input
|
|
if ($event.altKey && (code === 103 || code === 71)) {
|
|
$event.preventDefault();
|
|
if (!$scope.$parent.isSidebarVisible()) {
|
|
$scope.$parent.showSidebar();
|
|
}
|
|
document.getElementById('bufferFilter').focus();
|
|
return true;
|
|
}
|
|
|
|
// Arrow up -> go up in history
|
|
if ($event.type === "keydown" && code === 38) {
|
|
$scope.command = models.getActiveBuffer().getHistoryUp($scope.command);
|
|
// Set cursor to last position. Need 0ms timeout because browser sets cursor
|
|
// position to the beginning after this key handler returns.
|
|
setTimeout(function() {
|
|
if ($scope.command) {
|
|
inputNode.setSelectionRange($scope.command.length, $scope.command.length);
|
|
}
|
|
}, 0);
|
|
return true;
|
|
}
|
|
|
|
// Arrow down -> go down in history
|
|
if ($event.type === "keydown" && code === 40) {
|
|
$scope.command = models.getActiveBuffer().getHistoryDown($scope.command);
|
|
// We don't need to set the cursor to the rightmost position here, the browser does that for us
|
|
return true;
|
|
}
|
|
|
|
// Enter to submit, shift-enter for newline
|
|
if (code == 13 && !$event.shiftKey && document.activeElement === inputNode) {
|
|
$event.preventDefault();
|
|
$scope.sendMessage();
|
|
return true;
|
|
}
|
|
// Some readline keybindings
|
|
if ($rootScope.readlineBindings && $event.ctrlKey && !$event.altKey && !$event.shiftKey && document.activeElement === inputNode) {
|
|
// get current caret position
|
|
var caretPos = inputNode.selectionStart;
|
|
// Ctrl-a
|
|
if (code == 65) {
|
|
inputNode.setSelectionRange(0, 0);
|
|
// Ctrl-e
|
|
} else if (code == 69) {
|
|
inputNode.setSelectionRange($scope.command.length, $scope.command.length);
|
|
// Ctrl-u
|
|
} else if (code == 85) {
|
|
$scope.command = $scope.command.slice(caretPos);
|
|
setTimeout(function() {
|
|
inputNode.setSelectionRange(0, 0);
|
|
});
|
|
// Ctrl-k
|
|
} else if (code == 75) {
|
|
$scope.command = $scope.command.slice(0, caretPos);
|
|
setTimeout(function() {
|
|
inputNode.setSelectionRange($scope.command.length, $scope.command.length);
|
|
});
|
|
// Ctrl-w
|
|
} else if (code == 87) {
|
|
var trimmedValue = $scope.command.slice(0, caretPos);
|
|
var lastSpace = trimmedValue.lastIndexOf(' ') + 1;
|
|
$scope.command = $scope.command.slice(0, lastSpace) + $scope.command.slice(caretPos, $scope.command.length);
|
|
setTimeout(function() {
|
|
inputNode.setSelectionRange(lastSpace, lastSpace);
|
|
});
|
|
} else {
|
|
return false;
|
|
}
|
|
$event.preventDefault();
|
|
return true;
|
|
}
|
|
};
|
|
}]
|
|
};
|
|
});
|
|
})();
|