ec877ab224
Save outgoing /query nicks to a list and then check that list when we get a buffer title rename and then switch to the buffer if the name matches any nick in the outgoing list. Reason to use title event instead of buffer opened event is that buffer open event doesn't contain the short name. That would require parsing and guessing full names and in general be more complicated (but maybe more correct) This patch can be further improved in the future to check for existing buffers and switch to them if they already exist.
428 lines
19 KiB
JavaScript
428 lines
19 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', 'settings', 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,
|
|
settings) {
|
|
|
|
// E.g. Turn :smile: into the unicode equivalent
|
|
$scope.inputChanged = function() {
|
|
$scope.command = emojione.shortnameToUnicode($scope.command);
|
|
};
|
|
|
|
/*
|
|
* 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) {
|
|
// Ask before a /quit
|
|
if (line === '/quit' || line.indexOf('/quit ') === 0) {
|
|
if (!window.confirm("Are you sure you want to quit WeeChat? This will prevent you from connecting with Glowing Bear until you restart WeeChat on the command line!")) {
|
|
// skip this line
|
|
return;
|
|
}
|
|
}
|
|
connection.sendMessage(line);
|
|
});
|
|
|
|
// Check for /clear command
|
|
if ($scope.command === '/buffer clear' || $scope.command === '/c') {
|
|
$log.debug('Clearing lines');
|
|
ab.clear();
|
|
}
|
|
|
|
// Check against a list of commands that opens a new
|
|
// buffer and save the name of the buffer so we can
|
|
// also automatically switch to the new buffer in gb
|
|
var opencommands = ['/query', '/join', '/j', '/q'];
|
|
var spacepos = $scope.command.indexOf(' ');
|
|
var firstword = $scope.command.substr(0, spacepos);
|
|
var index = opencommands.indexOf(firstword);
|
|
if (index >= 0) {
|
|
var queryName = $scope.command.substring(spacepos + 1);
|
|
// Cache our queries so when a buffer gets opened we can open in UI
|
|
models.outgoingQueries.push(queryName);
|
|
}
|
|
|
|
// Empty the input after it's sent
|
|
$scope.command = '';
|
|
}
|
|
|
|
// New style clearing requires this, old does not
|
|
if (parseInt(models.version.charAt(0)) >= 1) {
|
|
connection.sendHotlistClear();
|
|
}
|
|
|
|
$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;
|
|
|
|
// reset quick keys display
|
|
$rootScope.showQuickKeys = false;
|
|
|
|
// 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 ;
|
|
|
|
var activeBufferId;
|
|
// quick select filtered entries
|
|
if (($scope.$parent.search.length || $scope.$parent.onlyUnread) && $scope.$parent.filteredBuffers.length) {
|
|
var filteredBufferNum = $scope.$parent.filteredBuffers[bufferNumber];
|
|
if (filteredBufferNum !== undefined) {
|
|
activeBufferId = [filteredBufferNum.number, filteredBufferNum.id];
|
|
}
|
|
} else {
|
|
// 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];
|
|
});
|
|
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();
|
|
}
|
|
setTimeout(function() {
|
|
document.getElementById('bufferFilter').focus();
|
|
});
|
|
return true;
|
|
}
|
|
|
|
var caretPos;
|
|
|
|
// Arrow up -> go up in history
|
|
if ($event.type === "keydown" && code === 38 && document.activeElement === inputNode) {
|
|
caretPos = inputNode.selectionStart;
|
|
if ($scope.command.slice(0, caretPos).indexOf("\n") !== -1) {
|
|
return false;
|
|
}
|
|
$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 && document.activeElement === inputNode) {
|
|
caretPos = inputNode.selectionStart;
|
|
if ($scope.command.slice(caretPos).indexOf("\n") !== -1) {
|
|
return false;
|
|
}
|
|
$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;
|
|
}
|
|
|
|
var bufferlines = document.getElementById("bufferlines");
|
|
var lines;
|
|
var i;
|
|
|
|
// Page up -> scroll up
|
|
if ($event.type === "keydown" && code === 33 && document.activeElement === inputNode && !$event.ctrlKey && !$event.altKey && !$event.shiftKey) {
|
|
if (bufferlines.scrollTop === 0) {
|
|
if (!$rootScope.loadingLines) {
|
|
$scope.$parent.fetchMoreLines();
|
|
}
|
|
return true;
|
|
}
|
|
lines = bufferlines.querySelectorAll("tr");
|
|
for (i = lines.length - 1; i >= 0; i--) {
|
|
if ((lines[i].offsetTop-bufferlines.scrollTop)<bufferlines.clientHeight/2) {
|
|
lines[i].scrollIntoView(false);
|
|
break;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Page down -> scroll down
|
|
if ($event.type === "keydown" && code === 34 && document.activeElement === inputNode && !$event.ctrlKey && !$event.altKey && !$event.shiftKey) {
|
|
lines = bufferlines.querySelectorAll("tr");
|
|
for (i = 0; i < lines.length; i++) {
|
|
if ((lines[i].offsetTop-bufferlines.scrollTop)>bufferlines.clientHeight/2) {
|
|
lines[i].scrollIntoView(true);
|
|
break;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Some readline keybindings
|
|
if (settings.readlineBindings && $event.ctrlKey && !$event.altKey && !$event.shiftKey && document.activeElement === inputNode) {
|
|
// get current caret position
|
|
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;
|
|
}
|
|
|
|
// Alt key down -> display quick key legend
|
|
if ($event.type === "keydown" && code === 18 && !$event.ctrlKey && !$event.shiftKey) {
|
|
$rootScope.showQuickKeys = true;
|
|
}
|
|
};
|
|
|
|
$rootScope.handleKeyRelease = function($event) {
|
|
// Alt key up -> remove quick key legend
|
|
if ($event.keyCode === 18) {
|
|
if ($rootScope.quickKeysTimer !== undefined) {
|
|
clearTimeout($rootScope.quickKeysTimer);
|
|
}
|
|
$rootScope.quickKeysTimer = setTimeout(function() {
|
|
if ($rootScope.showQuickKeys) {
|
|
$rootScope.showQuickKeys = false;
|
|
$rootScope.$apply();
|
|
}
|
|
delete $rootScope.quickKeysTimer;
|
|
}, 1000);
|
|
return true;
|
|
}
|
|
};
|
|
}]
|
|
};
|
|
});
|
|
})();
|