2014-08-24 18:53:55 +02:00
( function ( ) {
'use strict' ;
2014-08-28 18:25:40 +02:00
var weechat = angular . module ( 'weechat' ) ;
weechat . directive ( 'inputBar' , function ( ) {
return {
templateUrl : 'directives/input.html' ,
scope : {
2014-08-31 16:29:13 +02:00
inputId : '@inputId' ,
command : '=command'
2014-08-28 18:25:40 +02:00
} ,
2017-03-17 13:51:49 +01:00
controller : [ '$rootScope' , '$scope' , '$element' , '$log' , 'connection' , 'imgur' , 'models' , 'IrcUtils' , 'settings' , 'utils' , function ( $rootScope ,
2014-08-28 18:25:40 +02:00
$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
2015-10-09 14:01:45 +02:00
imgur ,
2014-08-24 18:53:55 +02:00
models ,
2014-12-22 22:17:01 +01:00
IrcUtils ,
2017-03-17 13:51:49 +01:00
settings ,
utils ) {
2014-08-28 18:25:40 +02:00
2017-03-17 15:29:59 +01:00
// Expose utils to be able to check if we're on a mobile UI
$scope . utils = utils ;
2015-03-21 17:23:05 +01:00
// E.g. Turn :smile: into the unicode equivalent
$scope . inputChanged = function ( ) {
$scope . command = emojione . shortnameToUnicode ( $scope . command ) ;
} ;
2014-08-28 18:25:40 +02:00
/ *
* 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 ) ;
} ;
2015-11-10 19:40:30 +01:00
$rootScope . insertAtCaret = function ( toInsert ) {
// caret position in the input bar
var inputNode = $scope . getInputNode ( ) ,
caretPos = inputNode . selectionStart ;
var prefix = $scope . command . substring ( 0 , caretPos ) ,
suffix = $scope . command . substring ( caretPos , $scope . command . length ) ;
// Add spaces if missing
if ( prefix . length > 0 && prefix [ prefix . length - 1 ] !== ' ' ) {
prefix += ' ' ;
}
if ( suffix . length > 0 && suffix [ 0 ] !== ' ' ) {
suffix = ' ' . concat ( suffix ) ;
}
$scope . command = prefix + toInsert + suffix ;
setTimeout ( function ( ) {
inputNode . focus ( ) ;
var pos = $scope . command . length - suffix . length ;
inputNode . setSelectionRange ( pos , pos ) ;
// force refresh?
$scope . $apply ( ) ;
} , 0 ) ;
} ;
2015-10-12 21:09:13 +02:00
$scope . uploadImage = function ( $event , files ) {
// Send image url after upload
var sendImageUrl = function ( imageUrl ) {
2015-11-07 12:32:25 +01:00
// Send image
if ( imageUrl !== undefined && imageUrl !== '' ) {
2015-11-10 19:40:30 +01:00
$rootScope . insertAtCaret ( String ( imageUrl ) ) ;
2015-11-07 12:32:25 +01:00
}
2015-10-12 21:09:13 +02:00
} ;
if ( typeof files !== "undefined" && files . length > 0 ) {
// Loop through files
for ( var i = 0 ; i < files . length ; i ++ ) {
// Process image
imgur . process ( files [ i ] , sendImageUrl ) ;
}
}
2015-10-09 14:01:45 +02:00
} ;
2014-08-28 18:25:40 +02:00
// 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 ) {
2014-11-06 14:21:40 +01:00
// 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 ;
}
}
2014-08-28 18:25:40 +02:00
connection . sendMessage ( line ) ;
} ) ;
// Check for /clear command
if ( $scope . command === '/buffer clear' || $scope . command === '/c' ) {
$log . debug ( 'Clearing lines' ) ;
ab . clear ( ) ;
}
2015-04-22 15:31:12 +02:00
// 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 ) ;
}
2014-08-28 18:25:40 +02:00
// Empty the input after it's sent
$scope . command = '' ;
}
2015-04-18 12:06:02 +02:00
// New style clearing requires this, old does not
2015-09-17 08:59:38 +02:00
if ( models . version [ 0 ] >= 1 ) {
2015-04-18 12:06:02 +02:00
connection . sendHotlistClear ( ) ;
}
2015-03-23 13:32:02 +01:00
2014-08-28 18:25:40 +02:00
$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 ;
2016-04-20 12:13:47 +02:00
// A KeyboardEvent property representing the physical key that was pressed, ignoring the keyboard layout and ignoring whether any modifier keys were active.
// Not supported in Edge or Safari at the time of writing this, but supported in Firefox and Chrome.
var key = $event . code ;
2015-06-14 01:54:59 +02:00
// Safari doesn't implement DOM 3 input events yet as of 8.0.6
var altg = $event . getModifierState ? $event . getModifierState ( 'AltGraph' ) : false ;
2015-06-12 11:10:48 +02:00
// Mac OSX behaves differntly for altgr, so we check for that
if ( altg ) {
// We don't handle any anything with altgr
return false ;
}
2014-09-03 14:21:11 +02:00
// reset quick keys display
$rootScope . showQuickKeys = false ;
2014-08-28 18:25:40 +02:00
// any other key than Tab resets nick completion iteration
var tmpIterCandidate = $scope . iterCandidate ;
$scope . iterCandidate = null ;
2017-03-14 23:00:01 +01:00
var bufferNumber ;
var sortedBuffers ;
var filteredBufferNum ;
var activeBufferId ;
2017-03-15 13:21:29 +01:00
// if Alt+J was pressed last...
if ( $rootScope . showJumpKeys ) {
// ... we expect two digits now
if ( ! $event . altKey && ( code > 47 && code < 58 ) ) {
// first digit
if ( $scope . jumpDecimal === undefined ) {
$scope . jumpDecimal = code - 48 ;
$event . preventDefault ( ) ;
// second digit
} else {
bufferNumber = ( $scope . jumpDecimal * 10 ) + ( code - 48 ) - 1 ;
// quick select filtered entries
if ( ( $scope . $parent . search . length || $scope . $parent . onlyUnread ) && $scope . $parent . filteredBuffers . length ) {
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
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 ( ) ;
$rootScope . showJumpKeys = false ;
$scope . jumpDecimal = undefined ;
2017-03-14 23:00:01 +01:00
}
} else {
2017-03-15 13:21:29 +01:00
$rootScope . showJumpKeys = false ;
$scope . jumpDecimal = undefined ;
2017-03-14 23:00:01 +01:00
}
}
2014-08-28 18:25:40 +02:00
// Left Alt+[0-9] -> jump to buffer
if ( $event . altKey && ! $event . ctrlKey && ( code > 47 && code < 58 ) ) {
if ( code === 48 ) {
code = 58 ;
}
2017-03-14 23:00:01 +01:00
bufferNumber = code - 48 - 1 ;
2014-09-03 14:21:11 +02:00
// quick select filtered entries
if ( ( $scope . $parent . search . length || $scope . $parent . onlyUnread ) && $scope . $parent . filteredBuffers . length ) {
2017-03-14 23:00:01 +01:00
filteredBufferNum = $scope . $parent . filteredBuffers [ bufferNumber ] ;
2014-09-03 14:21:11 +02:00
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
2017-03-14 23:00:01 +01:00
sortedBuffers = _ . map ( models . getBuffers ( ) , function ( buffer ) {
2014-09-03 14:21:11 +02:00
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 ] ;
}
2014-08-28 18:25:40 +02:00
if ( activeBufferId ) {
2014-09-03 14:39:38 +02:00
$scope . $parent . setActiveBuffer ( activeBufferId [ 1 ] ) ;
2014-08-28 18:25:40 +02:00
$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 ;
}
2015-12-02 00:02:32 +01:00
// Alt+Arrow up/down -> switch to prev/next adjacent buffer
if ( $event . altKey && ! $event . ctrlKey && ( code === 38 || code === 40 ) ) {
$event . preventDefault ( ) ;
var direction = code - 39 ;
$rootScope . switchToAdjacentBuffer ( direction ) ;
return true ;
}
2014-08-28 18:25:40 +02:00
// 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
2016-04-20 12:13:47 +02:00
// https://w3c.github.io/uievents-code/#code-IntlBackslash
if ( $event . altKey && ( code === 60 || code === 226 || key === "IntlBackslash" ) ) {
2014-08-28 18:25:40 +02:00
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 ( ) ;
2014-09-03 14:39:38 +02:00
if ( ! $scope . $parent . isSidebarVisible ( ) ) {
$scope . $parent . showSidebar ( ) ;
}
2014-09-20 06:02:12 +02:00
setTimeout ( function ( ) {
document . getElementById ( 'bufferFilter' ) . focus ( ) ;
} ) ;
2014-08-28 18:25:40 +02:00
return true ;
}
2016-10-13 14:16:24 +02:00
// Alt-h -> Toggle all as read
2016-11-09 13:02:21 +01:00
if ( $event . altKey && ! $event . ctrlKey && code === 72 ) {
2016-10-13 14:16:24 +02:00
var buffers = models . getBuffers ( ) ;
_ . each ( buffers , function ( buffer ) {
buffer . unread = 0 ;
buffer . notification = 0 ;
} ) ;
2016-11-09 11:32:12 +01:00
connection . sendHotlistClearAll ( ) ;
2016-10-13 14:16:24 +02:00
}
2017-03-14 23:00:01 +01:00
// Alt+J -> Jump to buffer
if ( $event . altKey && ( code === 106 || code === 74 ) ) {
$event . preventDefault ( ) ;
$rootScope . showJumpKeys = true ;
return true ;
}
2014-09-05 09:12:07 +02:00
var caretPos ;
2014-08-28 18:25:40 +02:00
// Arrow up -> go up in history
2014-09-05 09:12:07 +02:00
if ( $event . type === "keydown" && code === 38 && document . activeElement === inputNode ) {
caretPos = inputNode . selectionStart ;
if ( $scope . command . slice ( 0 , caretPos ) . indexOf ( "\n" ) !== - 1 ) {
return false ;
}
2014-08-28 18:25:40 +02:00
$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
2014-09-05 09:12:07 +02:00
if ( $event . type === "keydown" && code === 40 && document . activeElement === inputNode ) {
caretPos = inputNode . selectionStart ;
if ( $scope . command . slice ( caretPos ) . indexOf ( "\n" ) !== - 1 ) {
return false ;
}
2014-08-28 18:25:40 +02:00
$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 ;
}
2014-09-05 09:12:07 +02:00
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 ) {
2014-09-22 14:54:27 +02:00
if ( bufferlines . scrollTop === 0 ) {
if ( ! $rootScope . loadingLines ) {
$scope . $parent . fetchMoreLines ( ) ;
}
return true ;
}
2014-09-05 09:12:07 +02:00
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 ;
}
2014-08-28 18:25:40 +02:00
// Some readline keybindings
2014-12-22 22:17:01 +01:00
if ( settings . readlineBindings && $event . ctrlKey && ! $event . altKey && ! $event . shiftKey && document . activeElement === inputNode ) {
2014-08-28 18:25:40 +02:00
// get current caret position
2014-09-05 09:12:07 +02:00
caretPos = inputNode . selectionStart ;
2014-08-28 18:25:40 +02:00
// 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 ;
}
2014-10-20 11:40:30 +02:00
2014-09-03 14:21:11 +02:00
// Alt key down -> display quick key legend
2014-10-20 11:40:30 +02:00
if ( $event . type === "keydown" && code === 18 && ! $event . ctrlKey && ! $event . shiftKey ) {
2014-09-03 14:21:11 +02:00
$rootScope . showQuickKeys = true ;
2014-10-20 11:40:30 +02:00
}
} ;
$rootScope . handleKeyRelease = function ( $event ) {
// Alt key up -> remove quick key legend
if ( $event . keyCode === 18 ) {
2014-09-03 14:21:11 +02:00
if ( $rootScope . quickKeysTimer !== undefined ) {
clearTimeout ( $rootScope . quickKeysTimer ) ;
}
$rootScope . quickKeysTimer = setTimeout ( function ( ) {
if ( $rootScope . showQuickKeys ) {
$rootScope . showQuickKeys = false ;
$rootScope . $apply ( ) ;
}
delete $rootScope . quickKeysTimer ;
2014-10-20 11:40:30 +02:00
} , 1000 ) ;
2014-09-03 14:21:11 +02:00
return true ;
}
2014-08-28 18:25:40 +02:00
} ;
2017-03-17 06:37:59 +01:00
$scope . handleCompleteNickButton = function ( $event ) {
$event . preventDefault ( ) ;
$scope . completeNick ( ) ;
2017-03-17 15:31:18 +01:00
setTimeout ( function ( ) {
$scope . getInputNode ( ) . focus ( ) ;
} , 0 ) ;
2017-03-17 06:37:59 +01:00
return true ;
} ;
2014-08-28 18:25:40 +02:00
} ]
} ;
} ) ;
2014-08-24 18:53:55 +02:00
} ) ( ) ;