2014-07-07 19:54:56 +02:00
var weechat = angular . module ( 'weechat' , [ 'ngRoute' , 'localStorage' , 'weechatModels' , 'plugins' , 'ngSanitize' , 'ngWebsockets' , 'pasvaz.bindonce' , 'ngTouch' ] ) ;
2013-02-18 00:49:42 +01:00
2013-10-06 13:42:45 +02:00
weechat . filter ( 'toArray' , function ( ) {
'use strict' ;
return function ( obj ) {
if ( ! ( obj instanceof Object ) ) {
return obj ;
}
return Object . keys ( obj ) . map ( function ( key ) {
2013-12-17 14:40:49 +01:00
return Object . defineProperty ( obj [ key ] , '$key' , { value : key } ) ;
2013-10-06 13:42:45 +02:00
} ) ;
2013-12-16 14:09:01 +01:00
} ;
2013-10-06 13:42:45 +02:00
} ) ;
2013-02-18 00:49:42 +01:00
2014-07-07 16:42:23 +02:00
// Helper to change style of a class
var changeClassStyle = function ( classSelector , attr , value ) {
_ . each ( document . getElementsByClassName ( classSelector ) , function ( e ) {
e . style [ attr ] = value ;
} ) ;
} ;
// Helper to get style from a class
var getClassStyle = function ( classSelector , attr ) {
_ . each ( document . getElementsByClassName ( classSelector ) , function ( e ) {
return e . style [ attr ] ;
} ) ;
} ;
2014-02-16 15:10:56 +01:00
weechat . filter ( 'irclinky' , [ '$filter' , function ( $filter ) {
'use strict' ;
return function ( text , target ) {
if ( ! text ) {
return text ;
}
var linkiedText = $filter ( 'linky' ) ( text , target ) ;
2014-02-19 16:38:07 +01:00
// This regex in no way matches all IRC channel names (they could begin with a +, an &, or an exclamation
// mark followed by 5 alphanumeric characters, and are bounded in length by 50).
// However, it matches all *common* IRC channels while trying to minimise false positives. "#1" is much
// more likely to be "number 1" than "IRC channel #1".
// Thus, we only match channels beginning with a # and having at least one letter in them.
2014-07-02 09:41:50 +02:00
var channelRegex = /(^|[\s,.:;?!"'()+@-])(#+[a-z0-9-_]*[a-z][a-z0-9-_]*)/gmi ;
2014-02-16 15:10:56 +01:00
// This is SUPER nasty, but ng-click does not work inside a filter, as the markup has to be $compiled first, which is not possible in filter afaik.
// Therefore, get the scope, fire the method, and $apply. Yuck. I sincerely hope someone finds a better way of doing this.
linkiedText = linkiedText . replace ( channelRegex , '$1<a href="#" onclick="var $scope = angular.element(event.target).scope(); $scope.openBuffer(\'$2\'); $scope.$apply();">$2</a>' ) ;
return linkiedText ;
} ;
} ] ) ;
2014-05-08 13:24:57 +02:00
weechat . factory ( 'handlers' , [ '$rootScope' , '$log' , 'models' , 'plugins' , function ( $rootScope , $log , models , plugins ) {
2013-08-05 03:39:23 +02:00
2013-10-02 02:32:18 +02:00
var handleBufferClosing = function ( message ) {
2013-12-16 14:00:59 +01:00
var bufferMessage = message . objects [ 0 ] . content [ 0 ] ;
2014-05-08 12:05:08 +02:00
var bufferId = bufferMessage . pointers [ 0 ] ;
models . closeBuffer ( bufferId ) ;
2013-12-16 14:09:01 +01:00
} ;
2013-10-02 02:32:18 +02:00
2014-02-21 17:33:10 +01:00
var handleLine = function ( line , manually ) {
2013-10-09 17:53:25 +02:00
var message = new models . BufferLine ( line ) ;
2014-02-10 19:43:52 +01:00
var buffer = models . getBuffer ( message . buffer ) ;
buffer . requestedLines ++ ;
2013-10-06 12:34:41 +02:00
// Only react to line if its displayed
2014-02-08 13:49:21 +01:00
if ( message . displayed ) {
2013-10-28 13:21:51 +01:00
message = plugins . PluginManager . contentForMessage ( message , $rootScope . visible ) ;
2013-10-09 17:53:25 +02:00
buffer . addLine ( message ) ;
2013-10-06 02:06:28 +02:00
2014-02-21 17:33:10 +01:00
if ( manually ) {
2013-12-14 00:59:51 +01:00
buffer . lastSeen ++ ;
}
2014-02-21 17:33:10 +01:00
if ( buffer . active && ! manually ) {
2013-12-23 22:34:21 +01:00
$rootScope . scrollWithBuffer ( ) ;
2013-10-06 12:34:41 +02:00
}
2013-10-08 15:55:07 +02:00
2014-03-05 12:46:17 +01:00
if ( ! manually && ( ! buffer . active || ! $rootScope . isWindowFocused ( ) ) ) {
2014-02-08 13:49:21 +01:00
if ( buffer . notify > 1 && _ . contains ( message . tags , 'notify_message' ) && ! _ . contains ( message . tags , 'notify_none' ) ) {
2013-10-15 14:59:06 +02:00
buffer . unread ++ ;
2013-10-15 15:21:13 +02:00
$rootScope . $emit ( 'notificationChanged' ) ;
2013-10-09 17:53:25 +02:00
}
2013-08-06 22:39:10 +02:00
2014-02-08 13:49:21 +01:00
if ( ( buffer . notify !== 0 && message . highlight ) || _ . contains ( message . tags , 'notify_private' ) ) {
2013-10-15 14:59:06 +02:00
buffer . notification ++ ;
2013-10-09 17:53:25 +02:00
$rootScope . createHighlight ( buffer , message ) ;
2013-10-15 15:21:13 +02:00
$rootScope . $emit ( 'notificationChanged' ) ;
2013-10-09 17:53:25 +02:00
}
2013-10-06 12:34:41 +02:00
}
2013-10-08 16:03:44 +02:00
}
2013-12-16 14:09:01 +01:00
} ;
2013-08-05 03:39:23 +02:00
2013-10-06 20:20:34 +02:00
var handleBufferLineAdded = function ( message ) {
2013-12-16 14:00:59 +01:00
message . objects [ 0 ] . content . forEach ( function ( l ) {
2013-10-19 20:11:01 +02:00
handleLine ( l , false ) ;
} ) ;
2013-12-16 14:09:01 +01:00
} ;
2013-10-06 20:20:34 +02:00
2013-08-05 03:39:23 +02:00
var handleBufferOpened = function ( message ) {
2013-12-16 14:00:59 +01:00
var bufferMessage = message . objects [ 0 ] . content [ 0 ] ;
2013-10-08 03:15:25 +02:00
var buffer = new models . Buffer ( bufferMessage ) ;
2013-10-08 16:05:46 +02:00
models . addBuffer ( buffer ) ;
2014-06-10 16:59:45 +02:00
/ * U n t i l w e c a n d e c i d e i f u s e r a s k e d f o r t h i s b u f f e r t o b e o p e n e d
2014-07-01 20:41:27 +02:00
* or not we will let user click opened buffers .
2013-12-14 18:53:35 +01:00
models . setActiveBuffer ( buffer . id ) ;
2014-06-10 16:59:45 +02:00
* /
2013-12-16 14:09:01 +01:00
} ;
2013-10-06 02:06:28 +02:00
2013-10-07 00:24:18 +02:00
var handleBufferTitleChanged = function ( message ) {
2013-12-16 14:00:59 +01:00
var obj = message . objects [ 0 ] . content [ 0 ] ;
var buffer = obj . pointers [ 0 ] ;
2013-10-10 12:37:25 +02:00
var old = models . getBuffer ( buffer ) ;
2013-12-16 14:00:59 +01:00
old . fullName = obj . full _name ;
old . title = obj . title ;
old . number = obj . number ;
2013-12-16 14:09:01 +01:00
} ;
2013-10-17 18:36:53 +02:00
2013-10-07 11:45:18 +02:00
var handleBufferRenamed = function ( message ) {
2013-12-16 14:00:59 +01:00
var obj = message . objects [ 0 ] . content [ 0 ] ;
var buffer = obj . pointers [ 0 ] ;
2013-10-10 12:37:25 +02:00
var old = models . getBuffer ( buffer ) ;
2013-12-16 14:00:59 +01:00
old . fullName = obj . full _name ;
old . shortName = obj . short _name ;
2013-12-16 14:09:01 +01:00
} ;
2013-08-05 03:39:23 +02:00
2014-05-09 18:36:35 +02:00
var handleBufferLocalvarChanged = function ( message ) {
var obj = message . objects [ 0 ] . content [ 0 ] ;
var buffer = obj . pointers [ 0 ] ;
var old = models . getBuffer ( buffer ) ;
var localvars = obj . local _variables ;
if ( old !== undefined && localvars !== undefined ) {
// Update indendation status
old . indent = ( [ 'channel' , 'private' ] . indexOf ( localvars . type ) >= 0 ) ;
}
} ;
2013-10-06 20:20:34 +02:00
/ *
* Handle answers to ( lineinfo ) messages
*
* ( lineinfo ) messages are specified by this client . It is request after bufinfo completes
* /
2014-02-21 17:33:10 +01:00
var handleLineInfo = function ( message , manually ) {
2013-12-16 14:00:59 +01:00
var lines = message . objects [ 0 ] . content . reverse ( ) ;
2014-02-21 17:33:10 +01:00
if ( manually === undefined ) {
manually = true ;
2014-02-18 19:13:23 +01:00
}
2013-10-19 20:11:01 +02:00
lines . forEach ( function ( l ) {
2014-02-21 17:33:10 +01:00
handleLine ( l , manually ) ;
2013-10-19 20:11:01 +02:00
} ) ;
2013-12-16 14:09:01 +01:00
} ;
2013-10-06 02:06:28 +02:00
2013-10-16 01:09:31 +02:00
/ *
* Handle answers to hotlist request
* /
var handleHotlistInfo = function ( message ) {
2013-12-16 14:18:17 +01:00
if ( message . objects . length === 0 ) {
2013-10-19 20:11:01 +02:00
return ;
}
2013-12-16 14:00:59 +01:00
var hotlist = message . objects [ 0 ] . content ;
2013-10-19 20:11:01 +02:00
hotlist . forEach ( function ( l ) {
var buffer = models . getBuffer ( l . buffer ) ;
// 1 is message
buffer . unread += l . count [ 1 ] ;
2014-05-07 19:57:13 +02:00
// 2 is private
buffer . notification += l . count [ 2 ] ;
2013-10-19 20:11:01 +02:00
// 3 is highlight
buffer . notification += l . count [ 3 ] ;
/ * S i n c e t h e r e i s u n r e a d m e s s a g e s , w e c a n g u e s s
* what the last read line is and update it accordingly
* /
2014-02-08 13:49:21 +01:00
var unreadSum = _ . reduce ( l . count , function ( memo , num ) { return memo + num ; } , 0 ) ;
2013-10-19 20:11:01 +02:00
buffer . lastSeen = buffer . lines . length - 1 - unreadSum ;
} ) ;
2013-12-16 14:09:01 +01:00
} ;
2013-10-16 01:09:31 +02:00
2013-10-26 22:19:13 +02:00
/ *
* Handle nicklist event
* /
var handleNicklist = function ( message ) {
2013-12-16 14:00:59 +01:00
var nicklist = message . objects [ 0 ] . content ;
2013-10-26 22:19:13 +02:00
var group = 'root' ;
nicklist . forEach ( function ( n ) {
var buffer = models . getBuffer ( n . pointers [ 0 ] ) ;
2014-02-08 14:20:33 +01:00
if ( n . group === 1 ) {
2013-10-26 22:19:13 +02:00
var g = new models . NickGroup ( n ) ;
group = g . name ;
buffer . nicklist [ group ] = g ;
2014-02-08 13:49:21 +01:00
} else {
2013-10-26 22:19:13 +02:00
var nick = new models . Nick ( n ) ;
2013-10-27 10:48:20 +01:00
buffer . addNick ( group , nick ) ;
2013-10-26 22:19:13 +02:00
}
} ) ;
2013-12-16 14:09:01 +01:00
} ;
2013-10-26 22:19:13 +02:00
/ *
* Handle nicklist diff event
* /
var handleNicklistDiff = function ( message ) {
2013-12-16 14:00:59 +01:00
var nicklist = message . objects [ 0 ] . content ;
2013-10-26 22:19:13 +02:00
var group ;
nicklist . forEach ( function ( n ) {
var buffer = models . getBuffer ( n . pointers [ 0 ] ) ;
2013-12-16 14:00:59 +01:00
var d = n . _diff ;
2014-02-08 14:20:33 +01:00
if ( n . group === 1 ) {
2013-10-27 00:26:17 +02:00
group = n . name ;
2014-02-08 13:49:21 +01:00
if ( group === undefined ) {
2013-10-26 23:40:00 +02:00
var g = new models . NickGroup ( n ) ;
buffer . nicklist [ group ] = g ;
2013-10-27 00:26:17 +02:00
group = g . name ;
2013-10-26 23:40:00 +02:00
}
2014-02-08 13:49:21 +01:00
} else {
2013-10-26 22:19:13 +02:00
var nick = new models . Nick ( n ) ;
2014-02-08 14:20:33 +01:00
if ( d === 43 ) { // +
2013-10-27 00:26:17 +02:00
buffer . addNick ( group , nick ) ;
2014-02-08 14:20:33 +01:00
} else if ( d === 45 ) { // -
2013-10-27 00:26:17 +02:00
buffer . delNick ( group , nick ) ;
2014-02-08 14:20:33 +01:00
} else if ( d === 42 ) { // *
2013-10-27 00:26:17 +02:00
buffer . updateNick ( group , nick ) ;
2013-10-26 23:40:00 +02:00
}
2013-10-26 22:19:13 +02:00
}
} ) ;
2013-12-16 14:09:01 +01:00
} ;
2013-10-26 22:19:13 +02:00
2013-08-05 04:31:04 +02:00
var eventHandlers = {
2013-10-06 02:06:28 +02:00
_buffer _closing : handleBufferClosing ,
2013-08-05 03:39:23 +02:00
_buffer _line _added : handleBufferLineAdded ,
2014-05-09 18:36:35 +02:00
_buffer _localvar _added : handleBufferLocalvarChanged ,
_buffer _localvar _removed : handleBufferLocalvarChanged ,
2013-10-07 00:24:18 +02:00
_buffer _opened : handleBufferOpened ,
2013-10-07 11:45:18 +02:00
_buffer _title _changed : handleBufferTitleChanged ,
2013-10-26 22:19:13 +02:00
_buffer _renamed : handleBufferRenamed ,
_nicklist : handleNicklist ,
_nicklist _diff : handleNicklistDiff
2013-12-16 14:09:01 +01:00
} ;
2013-08-05 03:39:23 +02:00
2014-02-08 16:46:58 +01:00
$rootScope . $on ( 'onMessage' , function ( event , message ) {
if ( _ . has ( eventHandlers , message . id ) ) {
eventHandlers [ message . id ] ( message ) ;
2014-05-08 13:24:57 +02:00
} else {
$log . debug ( 'Unhandled event received: ' + message . id ) ;
2014-02-08 16:46:58 +01:00
}
} ) ;
2014-02-08 14:20:33 +01:00
var handleEvent = function ( event ) {
if ( _ . has ( eventHandlers , event . id ) ) {
eventHandlers [ event . id ] ( event ) ;
}
} ;
2013-08-05 03:39:23 +02:00
return {
2013-10-12 18:29:10 +02:00
handleEvent : handleEvent ,
2013-10-16 01:09:31 +02:00
handleLineInfo : handleLineInfo ,
2013-10-26 22:19:13 +02:00
handleHotlistInfo : handleHotlistInfo ,
handleNicklist : handleNicklist
2013-12-16 14:09:01 +01:00
} ;
2013-08-05 03:39:23 +02:00
} ] ) ;
2014-02-09 18:01:42 +01:00
weechat . factory ( 'connection' ,
[ '$rootScope' ,
'$log' ,
'handlers' ,
'models' ,
2014-02-09 18:06:04 +01:00
'ngWebsockets' ,
2014-02-10 01:06:30 +01:00
function ( $rootScope ,
$log ,
handlers ,
models ,
ngWebsockets ) {
2014-02-18 19:13:23 +01:00
2014-02-09 18:01:42 +01:00
protocol = new weeChat . Protocol ( ) ;
2013-10-27 16:28:34 +01:00
2013-08-05 04:25:59 +02:00
// Takes care of the connection and websocket hooks
2014-02-10 01:06:30 +01:00
2013-12-20 17:10:58 +01:00
var connect = function ( host , port , passwd , ssl , noCompression ) {
2014-02-08 13:49:21 +01:00
var proto = ssl ? 'wss' : 'ws' ;
2014-06-21 18:41:56 +02:00
// If host is an IPv6 literal wrap it in brackets
if ( host . indexOf ( ":" ) !== - 1 ) {
host = "[" + host + "]" ;
}
2014-02-08 16:46:58 +01:00
var url = proto + "://" + host + ":" + port + "/weechat" ;
2014-05-06 21:48:40 +02:00
$log . debug ( 'Connecting to URL: ' , url ) ;
2013-02-18 00:49:42 +01:00
2014-02-08 16:46:58 +01:00
var onopen = function ( ) {
2013-10-17 18:36:53 +02:00
2014-02-18 03:18:33 +01:00
// Helper methods for initialization commands
2014-02-18 03:15:10 +01:00
var _initializeConnection = function ( passwd ) {
2014-02-18 03:46:00 +01:00
// This is not the proper way to do this.
// WeeChat does not send a confirmation for the init.
// Until it does, We need to "assume" that formatInit
// will be received before formatInfo
ngWebsockets . send (
2014-02-18 03:15:10 +01:00
weeChat . Protocol . formatInit ( {
password : passwd ,
compression : noCompression ? 'off' : 'zlib'
2014-02-18 03:46:00 +01:00
} )
) ;
2014-02-18 03:15:10 +01:00
2014-02-18 03:46:00 +01:00
return ngWebsockets . send (
2014-02-18 03:15:10 +01:00
weeChat . Protocol . formatInfo ( {
name : 'version'
} )
2014-02-18 03:46:00 +01:00
) ;
2014-02-18 03:15:10 +01:00
} ;
var _requestHotlist = function ( ) {
return ngWebsockets . send (
weeChat . Protocol . formatHdata ( {
path : "hotlist:gui_hotlist(*)" ,
keys : [ ]
} )
) ;
} ;
var _requestBufferInfos = function ( ) {
return ngWebsockets . send (
weeChat . Protocol . formatHdata ( {
path : 'buffer:gui_buffers(*)' ,
keys : [ 'local_variables,notify,number,full_name,short_name,title' ]
} )
) ;
} ;
var _requestSync = function ( ) {
return ngWebsockets . send (
weeChat . Protocol . formatSync ( { } )
) ;
} ;
2013-10-17 18:36:53 +02:00
2013-11-09 22:52:26 +01:00
// First command asks for the password and issues
// a version command. If it fails, it means the we
// did not provide the proper password.
2014-02-18 03:15:10 +01:00
_initializeConnection ( passwd ) . then (
2014-02-18 03:18:33 +01:00
function ( ) {
// Connection is successful
// Send all the other commands required for initialization
_requestBufferInfos ( ) . then ( function ( bufinfo ) {
var bufferInfos = bufinfo . objects [ 0 ] . content ;
// buffers objects
for ( var i = 0 ; i < bufferInfos . length ; i ++ ) {
var buffer = new models . Buffer ( bufferInfos [ i ] ) ;
models . addBuffer ( buffer ) ;
// Switch to first buffer on startup
if ( i === 0 ) {
models . setActiveBuffer ( buffer . id ) ;
}
}
} ) ;
_requestHotlist ( ) . then ( function ( hotlist ) {
handlers . handleHotlistInfo ( hotlist ) ;
} ) ;
_requestSync ( ) ;
$log . info ( "Connected to relay" ) ;
$rootScope . connected = true ;
} ,
2013-12-17 14:40:49 +01:00
function ( ) {
2014-02-14 15:14:55 +01:00
// Connection got closed, lets check if we ever was connected successfully
2014-02-18 19:13:23 +01:00
if ( ! $rootScope . waseverconnected ) {
2014-02-14 15:14:55 +01:00
$rootScope . passwordError = true ;
2014-02-18 19:13:23 +01:00
}
2013-11-09 22:52:26 +01:00
}
) ;
2013-10-27 16:47:25 +01:00
2013-12-16 14:09:01 +01:00
} ;
2013-10-27 15:49:54 +01:00
2014-02-24 23:16:49 +01:00
var onmessage = function ( ) {
2014-02-14 15:14:55 +01:00
// If we recieve a message from WeeChat it means that
// password was OK. Store that result and check for it
// in the failure handler.
$rootScope . waseverconnected = true ;
2014-02-14 15:32:30 +01:00
} ;
2014-02-14 15:14:55 +01:00
2014-04-22 19:03:12 +02:00
var onclose = function ( evt ) {
2014-02-09 18:11:26 +01:00
/ *
* Handles websocket disconnection
* /
2013-08-05 04:25:59 +02:00
$log . info ( "Disconnected from relay" ) ;
2013-10-27 15:49:54 +01:00
failCallbacks ( 'disconnection' ) ;
2014-02-09 18:11:26 +01:00
$rootScope . connected = false ;
2014-05-06 21:59:10 +02:00
$rootScope . $emit ( 'relayDisconnect' ) ;
if ( ssl && evt . code === 1006 ) {
2014-04-22 19:03:12 +02:00
// A password error doesn't trigger onerror, but certificate issues do. Check time of last error.
if ( typeof $rootScope . lastError !== "undefined" && ( Date . now ( ) - $rootScope . lastError ) < 1000 ) {
// abnormal disconnect by client, most likely ssl error
$rootScope . sslError = true ;
}
}
2013-08-05 04:25:59 +02:00
$rootScope . $apply ( ) ;
2013-12-16 14:09:01 +01:00
} ;
2013-07-21 17:48:32 +02:00
2014-02-08 16:46:58 +01:00
var onerror = function ( evt ) {
2014-02-09 18:11:26 +01:00
/ *
* Handles cases when connection issues come from
* the relay .
* /
2014-04-22 19:03:12 +02:00
$log . error ( "Relay error" , evt ) ;
$rootScope . lastError = Date . now ( ) ;
2013-10-17 18:36:53 +02:00
2014-02-08 16:46:58 +01:00
if ( evt . type === "error" && this . readyState !== 1 ) {
2013-10-27 15:49:54 +01:00
failCallbacks ( 'error' ) ;
2013-10-03 01:55:30 +02:00
$rootScope . errorMessage = true ;
}
2013-12-16 14:09:01 +01:00
} ;
2013-02-18 00:49:42 +01:00
2014-02-08 16:46:58 +01:00
protocol . setId = function ( id , message ) {
return '(' + id + ') ' + message ;
2014-02-10 01:33:35 +01:00
} ;
2014-02-08 16:46:58 +01:00
2014-02-14 15:09:09 +01:00
2014-05-06 21:48:40 +02:00
try {
ngWebsockets . connect ( url ,
2014-02-08 16:46:58 +01:00
protocol ,
{
'binaryType' : "arraybuffer" ,
'onopen' : onopen ,
'onclose' : onclose ,
2014-02-14 15:14:55 +01:00
'onmessage' : onmessage ,
2014-03-19 19:02:20 +01:00
'onerror' : onerror
2014-02-10 01:33:35 +01:00
} ) ;
2014-05-07 19:15:47 +02:00
} catch ( e ) {
2014-05-06 21:48:40 +02:00
$log . debug ( "Websocket caught DOMException:" , e ) ;
$rootScope . lastError = Date . now ( ) ;
$rootScope . errorMessage = true ;
$rootScope . securityError = true ;
2014-05-07 19:15:47 +02:00
$rootScope . $emit ( 'relayDisconnect' ) ;
2014-05-06 21:48:40 +02:00
}
2014-02-08 16:46:58 +01:00
2013-12-16 14:09:01 +01:00
} ;
2013-07-31 14:40:43 +02:00
2013-10-16 14:25:07 +02:00
var disconnect = function ( ) {
2014-02-14 17:40:44 +01:00
ngWebsockets . send ( weeChat . Protocol . formatQuit ( ) ) ;
2013-12-16 14:09:01 +01:00
} ;
2013-10-16 14:25:07 +02:00
2013-12-14 16:32:14 +01:00
/ *
* Format and send a weechat message
*
* @ returns the angular promise
* /
2013-08-02 03:54:12 +02:00
var sendMessage = function ( message ) {
2014-02-09 18:06:04 +01:00
ngWebsockets . send ( weeChat . Protocol . formatInput ( {
2013-12-16 14:00:59 +01:00
buffer : models . getActiveBuffer ( ) . fullName ,
2013-10-06 01:54:07 +02:00
data : message
} ) ) ;
2013-12-16 14:09:01 +01:00
} ;
2013-07-21 17:48:32 +02:00
2013-10-19 12:14:32 +02:00
var sendCoreCommand = function ( command ) {
2014-02-09 18:06:04 +01:00
ngWebsockets . send ( weeChat . Protocol . formatInput ( {
2013-10-19 12:14:32 +02:00
buffer : 'core.weechat' ,
data : command
} ) ) ;
2013-12-16 14:09:01 +01:00
} ;
2013-10-19 12:14:32 +02:00
2014-03-07 18:56:48 +01:00
var requestNicklist = function ( bufferId , callback ) {
bufferId = bufferId || null ;
ngWebsockets . send (
weeChat . Protocol . formatNicklist ( {
buffer : bufferId
} )
) . then ( function ( nicklist ) {
handlers . handleNicklist ( nicklist ) ;
if ( callback !== undefined ) {
callback ( ) ;
}
} ) ;
} ;
2014-02-10 19:44:15 +01:00
var fetchMoreLines = function ( numLines ) {
2014-05-06 18:38:20 +02:00
$log . debug ( 'Fetching ' , numLines , ' lines' ) ;
2014-02-10 19:43:52 +01:00
var buffer = models . getActiveBuffer ( ) ;
2014-04-20 20:44:01 +02:00
if ( numLines === undefined ) {
// Math.max(undefined, *) = NaN -> need a number here
numLines = 0 ;
}
2014-02-10 20:32:00 +01:00
// Calculate number of lines to fetch, at least as many as the parameter
2014-02-10 19:44:15 +01:00
numLines = Math . max ( numLines , buffer . requestedLines * 2 ) ;
2014-02-10 19:43:52 +01:00
2014-02-10 20:32:00 +01:00
// Indicator that we are loading lines, hides "load more lines" link
2014-02-10 19:43:52 +01:00
$rootScope . loadingLines = true ;
2014-02-10 20:32:00 +01:00
// Send hdata request to fetch lines for this particular buffer
2014-06-18 23:10:33 +02:00
return ngWebsockets . send (
2014-02-10 19:43:52 +01:00
weeChat . Protocol . formatHdata ( {
2014-02-10 20:32:00 +01:00
// "0x" is important, otherwise it won't work
2014-02-10 19:43:52 +01:00
path : "buffer:0x" + buffer . id + "/own_lines/last_line(-" + numLines + ")/data" ,
keys : [ ]
} )
) . then ( function ( lineinfo ) {
// delete old lines and add new ones
var oldLength = buffer . lines . length ;
2014-03-10 18:10:56 +01:00
// Whether to set the readmarker to the middle position
// Don't do that if we didn't get any more lines than we already had
var setReadmarker = ( buffer . lastSeen >= 0 ) && ( oldLength !== buffer . lines . length ) ;
2014-02-10 19:43:52 +01:00
buffer . lines . length = 0 ;
2014-07-06 12:30:38 +02:00
// We need to set the number of requested lines to 0 here, because parsing a line
// increments it. This is needed to also count newly arriving lines while we're
// already connected.
2014-02-10 19:43:52 +01:00
buffer . requestedLines = 0 ;
2014-02-28 00:55:34 +01:00
// Count number of lines recieved
2014-02-28 01:08:11 +01:00
var linesReceivedCount = lineinfo . objects [ 0 ] . content . length ;
2014-02-28 00:55:34 +01:00
// Parse the lines
2014-02-21 17:33:10 +01:00
handlers . handleLineInfo ( lineinfo , true ) ;
2014-02-10 20:32:00 +01:00
2014-02-21 17:33:10 +01:00
if ( setReadmarker ) {
// Read marker was somewhere in the old lines - we don't need it any more,
// set it to the boundary between old and new. This way, we stay at the exact
// same position in the text through the scrollWithBuffer below
buffer . lastSeen = buffer . lines . length - oldLength - 1 ;
} else {
// We are currently fetching at least some unread lines, so we need to keep
// the read marker position correct
buffer . lastSeen -= oldLength ;
}
2014-02-28 00:55:34 +01:00
// We requested more lines than we got, no more lines.
2014-02-28 01:08:11 +01:00
if ( linesReceivedCount < numLines ) {
2014-02-28 03:50:50 +01:00
buffer . allLinesFetched = true ;
2014-02-28 00:55:34 +01:00
}
2014-02-10 19:43:52 +01:00
$rootScope . loadingLines = false ;
2014-02-10 20:32:00 +01:00
// Scroll read marker to the center of the screen
2014-02-10 19:43:59 +01:00
$rootScope . scrollWithBuffer ( true ) ;
2014-02-10 19:43:52 +01:00
} ) ;
2014-02-10 20:32:00 +01:00
} ;
2014-02-10 19:43:52 +01:00
2013-10-06 20:20:34 +02:00
2013-08-02 03:54:12 +02:00
return {
connect : connect ,
2013-10-16 14:25:07 +02:00
disconnect : disconnect ,
2013-10-19 12:14:32 +02:00
sendMessage : sendMessage ,
2014-02-10 19:43:52 +01:00
sendCoreCommand : sendCoreCommand ,
2014-03-07 18:56:48 +01:00
fetchMoreLines : fetchMoreLines ,
requestNicklist : requestNicklist
2013-12-16 14:09:01 +01:00
} ;
2013-02-18 00:49:42 +01:00
} ] ) ;
2013-12-17 14:40:49 +01:00
weechat . controller ( 'WeechatCtrl' , [ '$rootScope' , '$scope' , '$store' , '$timeout' , '$log' , 'models' , 'connection' , function ( $rootScope , $scope , $store , $timeout , $log , models , connection ) {
2013-12-16 17:09:17 +01:00
2014-04-28 14:40:27 +02:00
// From: http://stackoverflow.com/a/18539624 by StackOverflow user "plantian"
2013-12-17 21:30:22 +01:00
$rootScope . countWatchers = function ( ) {
2014-04-28 14:40:27 +02:00
var q = [ $rootScope ] , watchers = 0 , scope ;
while ( q . length > 0 ) {
scope = q . pop ( ) ;
if ( scope . $$watchers ) {
watchers += scope . $$watchers . length ;
2013-12-16 17:09:17 +01:00
}
2014-04-28 14:40:27 +02:00
if ( scope . $$childHead ) {
q . push ( scope . $$childHead ) ;
}
if ( scope . $$nextSibling ) {
q . push ( scope . $$nextSibling ) ;
}
}
2014-05-06 18:38:20 +02:00
$log . debug ( watchers ) ;
2013-12-16 17:09:17 +01:00
} ;
2014-02-25 21:41:41 +01:00
2014-03-31 02:12:01 +02:00
$rootScope . isMobileUi = function ( ) {
2014-02-25 22:17:29 +01:00
// TODO don't base detection solely on screen width
2014-03-31 02:12:01 +02:00
// You are right. In the meantime I am renaming isMobileDevice to isMobileUi
2014-02-25 22:17:29 +01:00
var mobile _cutoff = 968 ;
return ( document . body . clientWidth < mobile _cutoff ) ;
} ;
2014-02-25 21:41:41 +01:00
// Ask for permission to display desktop notifications
$scope . requestNotificationPermission = function ( ) {
// Firefox
if ( window . Notification ) {
Notification . requestPermission ( function ( status ) {
$log . info ( 'Notification permission status: ' , status ) ;
if ( Notification . permission !== status ) {
Notification . permission = status ;
}
} ) ;
}
// Webkit
2014-02-21 00:26:50 +01:00
if ( window . webkitNotifications !== undefined ) {
var havePermission = window . webkitNotifications . checkPermission ( ) ;
if ( havePermission !== 0 ) { // 0 is PERMISSION_ALLOWED
2014-02-25 21:41:41 +01:00
$log . info ( 'Notification permission status: ' , havePermission === 0 ) ;
2014-02-21 00:26:50 +01:00
window . webkitNotifications . requestPermission ( ) ;
}
2013-10-06 12:34:41 +02:00
}
2014-02-21 00:26:50 +01:00
} ;
2014-02-25 21:41:41 +01:00
$scope . isinstalled = ( function ( ) {
// Check for firefox & app installed
if ( navigator . mozApps !== undefined ) {
navigator . mozApps . getSelf ( ) . onsuccess = function _onAppReady ( evt ) {
var app = evt . target . result ;
if ( app ) {
return true ;
} else {
return false ;
}
} ;
} else {
return false ;
}
} ( ) ) ;
2013-10-06 12:34:41 +02:00
2014-03-05 12:46:17 +01:00
// Detect page visibility attributes
( function ( ) {
// Sadly, the page visibility API still has a lot of vendor prefixes
if ( typeof document . hidden !== "undefined" ) { // Chrome >= 33, Firefox >= 18, Opera >= 12.10, Safari >= 7
$scope . documentHidden = "hidden" ;
$scope . documentVisibilityChange = "visibilitychange" ;
} else if ( typeof document . webkitHidden !== "undefined" ) { // 13 <= Chrome < 33
$scope . documentHidden = "webkitHidden" ;
$scope . documentVisibilityChange = "webkitvisibilitychange" ;
} else if ( typeof document . mozHidden !== "undefined" ) { // 10 <= Firefox < 18
$scope . documentHidden = "mozHidden" ;
$scope . documentVisibilityChange = "mozvisibilitychange" ;
} else if ( typeof document . msHidden !== "undefined" ) { // IE >= 10
$scope . documentHidden = "msHidden" ;
$scope . documentVisibilityChange = "msvisibilitychange" ;
}
} ) ( ) ;
2014-07-18 13:13:54 +02:00
// Enable debug mode if "?debug=1" or "?debug=true" is set
( function ( ) {
window . location . search . substring ( 1 ) . split ( '&' ) . forEach ( function ( f ) {
var segs = f . split ( '=' ) ;
if ( segs [ 0 ] === "debug" && [ "true" , "1" ] . indexOf ( segs [ 1 ] ) != - 1 ) {
$rootScope . debugMode = true ;
return ;
}
} ) ;
} ) ( ) ;
2014-03-05 12:46:17 +01:00
$rootScope . isWindowFocused = function ( ) {
if ( typeof $scope . documentHidden === "undefined" ) {
// Page Visibility API not supported, assume yes
return true ;
} else {
var isHidden = document [ $scope . documentHidden ] ;
return ! isHidden ;
}
} ;
if ( typeof $scope . documentVisibilityChange !== "undefined" ) {
document . addEventListener ( $scope . documentVisibilityChange , function ( ) {
if ( ! document [ $scope . documentHidden ] ) {
// We just switched back to the glowing-bear window and unread messages may have
// accumulated in the active buffer while the window was in the background
var buffer = models . getActiveBuffer ( ) ;
2014-04-15 22:01:13 +02:00
// This can also be triggered before connecting to the relay, check for null (not undefined!)
if ( buffer !== null ) {
buffer . unread = 0 ;
buffer . notification = 0 ;
2014-03-05 12:46:17 +01:00
2014-04-15 22:01:13 +02:00
// Trigger title and favico update
$rootScope . $emit ( 'notificationChanged' ) ;
}
2014-03-05 12:46:17 +01:00
// the unread badge in the bufferlist doesn't update if we don't do this
$rootScope . $apply ( ) ;
}
} , false ) ;
}
2014-02-20 23:35:34 +01:00
// Reduce buffers with "+" operation over a key. Mostly useful for unread/notification counts.
$rootScope . unreadCount = function ( type ) {
if ( ! type ) {
type = "unread" ;
}
// Do this the old-fashioned way with iterating over the keys, as underscore proved to be error-prone
var keys = Object . keys ( models . model . buffers ) ;
var count = 0 ;
for ( var key in keys ) {
count += models . model . buffers [ keys [ key ] ] [ type ] ;
}
return count ;
} ;
2014-02-20 23:37:06 +01:00
$rootScope . updateTitle = function ( ) {
var notifications = $rootScope . unreadCount ( 'notification' ) ;
if ( notifications > 0 ) {
// New notifications deserve an exclamation mark
2014-02-21 11:22:27 +01:00
$rootScope . notificationStatus = '(' + notifications + ') ' ;
2014-02-20 23:37:06 +01:00
} else {
2014-02-21 11:22:27 +01:00
$rootScope . notificationStatus = '' ;
2014-02-20 23:37:06 +01:00
}
var activeBuffer = models . getActiveBuffer ( ) ;
2014-05-06 21:59:10 +02:00
if ( activeBuffer ) {
$rootScope . pageTitle = activeBuffer . shortName + ' | ' + activeBuffer . title ;
}
2014-02-20 23:37:06 +01:00
} ;
2014-02-20 23:35:34 +01:00
$scope . updateFavico = function ( ) {
var notifications = $rootScope . unreadCount ( 'notification' ) ;
if ( notifications > 0 ) {
$scope . favico . badge ( notifications , {
bgColor : '#d00' ,
textColor : '#fff'
} ) ;
} else {
var unread = $rootScope . unreadCount ( 'unread' ) ;
if ( unread === 0 ) {
$scope . favico . reset ( ) ;
} else {
$scope . favico . badge ( unread , {
bgColor : '#5CB85C' ,
textColor : '#ff0'
} ) ;
}
}
} ;
2014-03-07 18:07:05 +01:00
$rootScope . $on ( 'activeBufferChanged' , function ( event , unreadSum ) {
2013-10-12 22:09:02 +02:00
var ab = models . getActiveBuffer ( ) ;
2014-06-26 22:38:04 +02:00
2014-07-01 20:41:27 +02:00
// Discard surplus lines. This is done *before* lines are fetched because that saves us the effort of special handling for the
// case where a buffer is opened for the first time ;)
var minRetainUnread = ab . lines . length - unreadSum + 5 ; // do not discard unread lines and keep 5 additional lines for context
var surplusLines = ab . lines . length - ( 2 * $scope . lines _per _screen + 10 ) ; // retain up to 2*(screenful + 10) + 10 lines because magic numbers
var linesToRemove = Math . max ( 0 , Math . min ( minRetainUnread , surplusLines ) ) ;
ab . lines . splice ( 0 , linesToRemove ) ; // remove the lines from the buffer
2014-07-06 12:30:38 +02:00
ab . requestedLines -= linesToRemove ; // to ensure that the correct amount of lines is fetched should more be requested
2014-07-01 20:41:27 +02:00
ab . lastSeen -= linesToRemove ; // adjust readmarker
2014-06-26 22:38:04 +02:00
2014-04-20 17:44:50 +02:00
$scope . bufferlines = ab . lines ;
$scope . nicklist = ab . nicklist ;
2014-03-07 18:56:48 +01:00
2014-04-25 15:36:31 +02:00
// Send a request for the nicklist if it hasn't been loaded yet
if ( ! ab . nicklistRequested ( ) ) {
connection . requestNicklist ( ab . fullName , function ( ) {
$scope . showNicklist = $scope . updateShowNicklist ( ) ;
// Scroll after nicklist has been loaded, as it may break long lines
$rootScope . scrollWithBuffer ( true ) ;
} ) ;
} else {
// Check if we should show nicklist or not
$scope . showNicklist = $scope . updateShowNicklist ( ) ;
}
2014-06-27 04:36:30 +02:00
if ( ab . requestedLines < $scope . lines _per _screen ) {
2014-02-10 20:32:00 +01:00
// buffer has not been loaded, but some lines may already be present if they arrived after we connected
2014-03-07 18:07:05 +01:00
// try to determine how many lines to fetch
2014-07-06 12:30:38 +02:00
var numLines = $scope . lines _per _screen + 10 ; // that's (a screenful plus 10 lines) plus 10 lines, just to be safe
2014-03-07 18:07:05 +01:00
if ( unreadSum > numLines ) {
// request up to 4*(screenful + 10 lines)
numLines = Math . min ( 4 * numLines , unreadSum ) ;
}
2014-06-18 23:10:33 +02:00
$scope . fetchMoreLines ( numLines ) . then (
// Update initial scroll position
// Most relevant when first connecting to properly initalise
function ( ) {
$timeout ( function ( ) {
var bufferlines = document . getElementById ( "bufferlines" ) ;
$rootScope . originalBufferlinesPosition = bufferlines . scrollTop + bufferlines . scrollHeight ;
} ) ;
}
) ;
2014-02-10 19:44:15 +01:00
}
2014-02-20 23:37:06 +01:00
$rootScope . updateTitle ( ab ) ;
2013-10-19 12:14:32 +02:00
2014-03-07 18:56:48 +01:00
$rootScope . scrollWithBuffer ( true ) ;
2013-10-19 12:14:32 +02:00
// If user wants to sync hotlist with weechat
// we will send a /buffer bufferName command every time
// the user switches a buffer. This will ensure that notifications
// are cleared in the buffer the user switches to
2014-02-08 13:49:21 +01:00
if ( $scope . hotlistsync && ab . fullName ) {
2013-10-19 12:14:32 +02:00
connection . sendCoreCommand ( '/buffer ' + ab . fullName ) ;
}
2013-10-22 14:24:09 +02:00
// Clear search term on buffer change
$scope . search = '' ;
2013-12-17 21:30:22 +01:00
2014-03-31 02:12:01 +02:00
if ( ! $rootScope . isMobileUi ( ) ) {
2014-05-04 12:21:35 +02:00
// This needs to happen asynchronously to prevent the enter key handler
// of the input bar to be triggered on buffer switch via the search.
// Otherwise its current contents would be sent to the new buffer
setTimeout ( function ( ) {
document . getElementById ( 'sendMessage' ) . focus ( ) ;
} , 0 ) ;
2014-02-25 22:17:29 +01:00
}
2013-10-12 22:09:02 +02:00
} ) ;
2014-02-18 19:13:23 +01:00
2014-02-02 14:00:17 +01:00
$scope . favico = new Favico ( { animation : 'none' } ) ;
2014-02-18 19:13:23 +01:00
2013-10-15 15:21:13 +02:00
$rootScope . $on ( 'notificationChanged' , function ( ) {
2014-02-20 23:37:06 +01:00
$rootScope . updateTitle ( ) ;
2014-02-20 23:35:34 +01:00
if ( $scope . useFavico && $scope . favico ) {
$scope . updateFavico ( ) ;
2013-10-15 15:21:13 +02:00
}
} ) ;
2013-10-12 22:09:02 +02:00
2014-02-21 14:54:17 +01:00
$rootScope . $on ( 'relayDisconnect' , function ( ) {
2014-07-02 00:07:34 +02:00
models . reinitialize ( ) ;
2014-03-05 14:20:48 +01:00
$rootScope . $emit ( 'notificationChanged' ) ;
2014-05-06 21:59:10 +02:00
$scope . connectbutton = 'Connect' ;
2014-02-21 14:54:17 +01:00
} ) ;
2014-05-06 21:59:10 +02:00
$scope . connectbutton = 'Connect' ;
2014-02-21 14:54:17 +01:00
2014-07-02 00:07:34 +02:00
$scope . getBuffers = models . getBuffers . bind ( models ) ;
2014-02-25 21:42:59 +01:00
2014-04-20 17:44:50 +02:00
$scope . bufferlines = { } ;
$scope . nicklist = { } ;
2013-12-16 14:09:01 +01:00
$scope . activeBuffer = models . getActiveBuffer ;
2013-10-08 15:55:07 +02:00
2014-02-14 15:14:55 +01:00
$rootScope . waseverconnected = false ;
2013-02-16 19:18:14 +01:00
2013-10-08 15:55:07 +02:00
$rootScope . models = models ;
2013-10-27 07:48:30 +01:00
$rootScope . iterCandidate = null ;
2013-10-11 23:15:08 +02:00
$store . bind ( $scope , "host" , "localhost" ) ;
$store . bind ( $scope , "port" , "9001" ) ;
2013-10-05 16:05:16 +02:00
$store . bind ( $scope , "proto" , "weechat" ) ;
2013-10-09 17:53:25 +02:00
$store . bind ( $scope , "ssl" , false ) ;
2013-10-25 13:43:03 +02:00
$store . bind ( $scope , "savepassword" , false ) ;
2014-02-08 13:49:21 +01:00
if ( $scope . savepassword ) {
2013-10-25 13:43:03 +02:00
$store . bind ( $scope , "password" , "" ) ;
}
2013-10-16 15:45:38 +02:00
2014-02-27 14:28:01 +01:00
// If we are on mobile change some defaults
// We use 968 px as the cutoff, which should match the value in glowingbear.css
var nonicklist = false ;
var noembed = false ;
2014-05-28 21:03:17 +02:00
var showtimestamp = true ;
2014-02-27 14:28:01 +01:00
2014-03-31 02:12:01 +02:00
$rootScope . wasMobileUi = false ;
2014-03-30 23:53:29 +02:00
2014-03-31 02:12:01 +02:00
if ( $rootScope . isMobileUi ( ) ) {
2014-02-27 14:28:01 +01:00
nonicklist = true ;
noembed = true ;
2014-03-31 02:12:01 +02:00
$rootScope . wasMobileUi = true ;
2014-02-27 14:28:01 +01:00
}
2013-10-17 10:50:29 +02:00
// Save setting for displaying only buffers with unread messages
2013-10-16 15:45:38 +02:00
$store . bind ( $scope , "onlyUnread" , false ) ;
2014-02-27 14:28:01 +01:00
2013-10-19 12:14:32 +02:00
// Save setting for syncing hotlist
$store . bind ( $scope , "hotlistsync" , true ) ;
2013-10-26 22:56:52 +02:00
// Save setting for displaying nicklist
2014-02-27 14:28:01 +01:00
$store . bind ( $scope , "nonicklist" , nonicklist ) ;
2013-10-28 13:21:51 +01:00
// Save setting for displaying embeds
2014-02-27 14:28:01 +01:00
$store . bind ( $scope , "noembed" , noembed ) ;
2013-12-08 22:29:48 +01:00
// Save setting for channel ordering
2014-07-18 13:35:13 +02:00
$store . bind ( $scope , "orderbyserver" , true ) ;
2014-02-20 23:35:34 +01:00
// Save setting for updating favicon
$store . bind ( $scope , "useFavico" , true ) ;
2014-05-28 21:03:17 +02:00
// Save setting for showtimestamp
$store . bind ( $scope , "showtimestamp" , showtimestamp ) ;
2014-06-23 02:35:04 +02:00
// Save setting for showing seconds on timestamps
$store . bind ( $scope , "showtimestampSeconds" , false ) ;
2014-03-01 19:55:22 +01:00
// Save setting for playing sound on notification
$store . bind ( $scope , "soundnotification" , false ) ;
2014-07-07 01:48:19 +02:00
// Save setting for font family
2014-07-07 20:11:14 +02:00
$store . bind ( $scope , "fontfamily" , getClassStyle ( 'monospace' , 'fontFamily' ) ) ;
2014-07-07 16:09:25 +02:00
// Save setting for font size
2014-07-07 20:11:14 +02:00
$store . bind ( $scope , "fontsize" , getClassStyle ( 'monospace' , 'fontSize' ) ) ;
2014-02-27 14:28:01 +01:00
2013-12-26 18:33:43 +01:00
// Save setting for displaying embeds in rootScope so it can be used from service
2013-12-16 14:18:17 +01:00
$rootScope . visible = $scope . noembed === false ;
2013-12-18 23:09:53 +01:00
2014-07-08 10:51:17 +02:00
$scope . isSidebarVisible = function ( ) {
return document . getElementById ( 'sidebar' ) . getAttribute ( 'sidebar-state' ) === 'visible' ;
} ;
$scope . showSidebar = function ( ) {
document . getElementById ( 'sidebar' ) . setAttribute ( 'data-state' , 'visible' ) ;
document . getElementById ( 'content' ) . setAttribute ( 'sidebar-state' , 'visible' ) ;
} ;
$scope . hideSidebar = function ( ) {
document . getElementById ( 'sidebar' ) . setAttribute ( 'data-state' , 'hidden' ) ;
document . getElementById ( 'content' ) . setAttribute ( 'sidebar-state' , 'hidden' ) ;
} ;
// toggle sidebar (if on mobile)
$scope . toggleSidebar = function ( ) {
2014-03-31 02:12:01 +02:00
if ( $rootScope . isMobileUi ( ) ) {
2014-07-08 10:51:17 +02:00
if ( $scope . isSidebarVisible ( ) ) {
$scope . hideSidebar ( ) ;
} else {
$scope . showSidebar ( ) ;
}
2014-02-23 15:17:59 +01:00
}
2014-02-19 16:15:23 +01:00
} ;
2014-02-24 11:28:43 +01:00
2014-07-08 10:51:17 +02:00
// Open and close panels while on mobile devices through swiping
2014-02-19 16:15:23 +01:00
$scope . openNick = function ( ) {
2014-03-31 02:12:01 +02:00
if ( $rootScope . isMobileUi ( ) ) {
2014-02-25 22:17:29 +01:00
if ( $scope . nonicklist ) {
2014-02-19 16:15:23 +01:00
$scope . nonicklist = false ;
2014-02-24 11:28:43 +01:00
}
2014-02-19 16:15:23 +01:00
}
} ;
$scope . closeNick = function ( ) {
2014-03-31 02:12:01 +02:00
if ( $rootScope . isMobileUi ( ) ) {
2014-02-25 22:17:29 +01:00
if ( ! $scope . nonicklist ) {
2014-02-19 16:15:23 +01:00
$scope . nonicklist = true ;
2014-02-24 11:28:43 +01:00
}
2014-02-19 16:15:23 +01:00
}
} ;
2013-12-18 23:09:53 +01:00
2013-10-28 13:21:51 +01:00
// Watch model and update show setting when it changes
$scope . $watch ( 'noembed' , function ( ) {
2013-12-16 14:18:17 +01:00
$rootScope . visible = $scope . noembed === false ;
2013-10-28 13:21:51 +01:00
} ) ;
2013-12-08 22:29:48 +01:00
// Watch model and update channel sorting when it changes
$scope . $watch ( 'orderbyserver' , function ( ) {
$rootScope . predicate = $scope . orderbyserver ? 'serverSortKey' : 'number' ;
} ) ;
2014-02-20 23:35:34 +01:00
$scope . $watch ( 'useFavico' , function ( ) {
// this check is necessary as this is called on page load, too
if ( ! $rootScope . connected ) {
return ;
}
if ( $scope . useFavico ) {
$scope . updateFavico ( ) ;
} else {
$scope . favico . reset ( ) ;
}
} ) ;
2014-07-07 01:48:19 +02:00
// Update font family when changed
$scope . $watch ( 'fontfamily' , function ( ) {
2014-07-07 20:11:14 +02:00
changeClassStyle ( 'monospace' , 'fontFamily' , $scope . fontfamily ) ;
2014-07-07 01:48:19 +02:00
} ) ;
2014-07-07 16:09:25 +02:00
// Update font size when changed
$scope . $watch ( 'fontsize' , function ( ) {
2014-07-07 20:11:14 +02:00
changeClassStyle ( 'monospace' , 'fontSize' , $scope . fontsize ) ;
2014-07-07 16:09:25 +02:00
} ) ;
2013-10-02 02:32:18 +02:00
2013-12-14 15:40:11 +01:00
$scope . setActiveBuffer = function ( bufferId , key ) {
2014-02-19 15:53:03 +01:00
// If we are on mobile we need to collapse the menu on sidebar clicks
// We use 968 px as the cutoff, which should match the value in glowingbear.css
2014-03-31 02:12:01 +02:00
if ( $rootScope . isMobileUi ( ) ) {
2014-07-08 10:51:17 +02:00
$scope . hideSidebar ( ) ;
2014-02-19 15:53:03 +01:00
}
2013-12-14 15:40:11 +01:00
return models . setActiveBuffer ( bufferId , key ) ;
2013-07-30 15:22:37 +02:00
} ;
2014-02-16 15:10:56 +01:00
$scope . openBuffer = function ( bufferName ) {
var fullName = models . getActiveBuffer ( ) . fullName ;
fullName = fullName . substring ( 0 , fullName . lastIndexOf ( '.' ) + 1 ) + bufferName ; // substitute the last part
2013-12-14 15:40:11 +01:00
2014-02-16 15:10:56 +01:00
if ( ! $scope . setActiveBuffer ( fullName , 'fullName' ) ) {
var command = 'join' ;
if ( [ '#' , '&' , '+' , '!' ] . indexOf ( bufferName . charAt ( 0 ) ) < 0 ) { // these are the characters a channel name can start with (RFC 2813-2813)
command = 'query' ;
}
connection . sendMessage ( '/' + command + ' ' + bufferName ) ;
2013-12-14 15:40:11 +01:00
}
2013-12-16 14:09:01 +01:00
} ;
2013-12-14 15:40:11 +01:00
2014-02-10 21:17:51 +01:00
// Calculate number of lines to fetch
2014-02-28 11:23:10 +01:00
$scope . calculateNumLines = function ( ) {
Fix that bug where only one line was loaded
Let me tell the story backwards:
For some reason, only one line would be fetched in `fetchMoreLines`, but
neither `numLines` nor `buffer.requestedLines` were `undefined`. The
paramater `numLines` must have had some strange value, though.
`connection.fetchMoreLines` is invoked through the main controllers
`$scope.fetchMoreLines`, which sets the parameter to `$scope.lines` if it was
undefined before. That value is computed in `$scope.calculateNumLines`,
which takes the height of the lines area and divides it by the height of the first
bufferline. This computation is retriggered on every `resize` event.
The first bufferline is the 'fetch more lines' link at the top of the window. If the
currently active buffer doesn't have more lines, it is hidden with `ngHide`,
causing its `clientHeight` property to be `0`, and the number of lines to be
fetched `$scope.lines = Infinity` (due to a division by zero).
In `connection.fetchMoreLines`, the following request is then made:
`"buffer:0x" + buffer.id + "/own_lines/last_line(-" + numLines + ")/data"`
to which WeeChat responds with one line. Voilà, there's your mess.
2014-05-03 16:07:12 +02:00
var bufferlineElements = document . querySelectorAll ( ".bufferline" ) ;
var lineHeight = 0 , idx = 0 ;
while ( lineHeight === 0 && idx < bufferlineElements . length ) {
lineHeight = bufferlineElements [ idx ++ ] . clientHeight ;
}
2014-02-28 11:23:10 +01:00
var areaHeight = document . querySelector ( "#bufferlines" ) . clientHeight ;
// Fetch 10 lines more than theoretically needed so that scrolling up will correctly trigger the loading of more lines
// Also, some lines might be hidden, so it's probably better to have a bit of buffer there
var numLines = Math . ceil ( areaHeight / lineHeight + 10 ) ;
2014-06-27 04:36:30 +02:00
$scope . lines _per _screen = numLines ;
2014-02-28 11:23:10 +01:00
} ;
$scope . calculateNumLines ( ) ;
// Recalculate number of lines on resize
window . addEventListener ( "resize" , _ . debounce ( function ( ) {
// Recalculation fails when not connected
if ( $rootScope . connected ) {
2014-03-13 19:49:29 +01:00
// Show the sidebar if switching away from mobile view, hide it when switching to mobile
// Wrap in a condition so we save ourselves the $apply if nothing changes (50ms or more)
2014-07-08 10:51:17 +02:00
if ( $scope . wasMobileUi && ! $scope . isMobileUi ( ) ) {
$scope . showSidebar ( ) ;
2014-03-13 19:49:29 +01:00
}
2014-03-31 02:12:01 +02:00
$scope . wasMobileUi = $scope . isMobileUi ( ) ;
2014-02-28 11:23:10 +01:00
$scope . calculateNumLines ( ) ;
2014-06-18 23:10:33 +02:00
// if we're scrolled to the bottom, scroll down to the same position after the resize
// most common use case: opening the keyboard on a mobile device
var bufferlines = document . getElementById ( "bufferlines" ) ;
if ( $rootScope . originalBufferlinesPosition === bufferlines . scrollHeight + bufferlines . scrollTop ) {
$timeout ( function ( ) {
bufferlines . scrollTop = bufferlines . scrollHeight ;
} , 100 ) ;
}
$rootScope . originalBufferlinesPosition = bufferlines . scrollTop + bufferlines . scrollHeight ;
2014-02-28 11:23:10 +01:00
}
} , 100 ) ) ;
2014-02-10 21:17:51 +01:00
2014-02-10 19:43:52 +01:00
$rootScope . loadingLines = false ;
2014-03-07 18:07:05 +01:00
$scope . fetchMoreLines = function ( numLines ) {
if ( ! numLines ) {
2014-06-27 04:36:30 +02:00
numLines = $scope . lines _per _screen ;
2014-03-07 18:07:05 +01:00
}
2014-06-18 23:10:33 +02:00
return connection . fetchMoreLines ( numLines ) ;
2014-02-10 20:32:00 +01:00
} ;
2014-02-10 19:43:52 +01:00
2013-12-28 16:50:31 +01:00
$rootScope . scrollWithBuffer = function ( nonIncremental ) {
// First, get scrolling status *before* modification
// This is required to determine where we were in the buffer pre-change
var bl = document . getElementById ( 'bufferlines' ) ;
var sVal = bl . scrollHeight - bl . clientHeight ;
2013-10-16 23:59:27 +02:00
var scroll = function ( ) {
2013-12-09 11:10:11 +01:00
var sTop = bl . scrollTop ;
2013-12-28 16:50:31 +01:00
// Determine if we want to scroll at all
2014-02-05 11:19:54 +01:00
// Give the check 3 pixels of slack so you don't have to hit
// the exact spot. This fixes a bug in some browsers
2014-02-11 22:45:57 +01:00
if ( ( nonIncremental && sTop < sVal ) || ( Math . abs ( sTop - sVal ) < 3 ) ) {
2014-02-11 21:40:25 +01:00
var readmarker = document . querySelector ( ".readmarker" ) ;
2014-02-08 13:49:21 +01:00
if ( nonIncremental && readmarker ) {
2013-12-28 16:50:31 +01:00
// Switching channels, scroll to read marker
2014-02-11 21:40:25 +01:00
bl . scrollTop = readmarker . offsetTop - readmarker . parentElement . scrollHeight + readmarker . scrollHeight ;
2013-12-28 16:50:31 +01:00
} else {
// New message, scroll with buffer (i.e. to bottom)
bl . scrollTop = bl . scrollHeight - bl . clientHeight ;
2013-10-22 19:58:12 +02:00
}
2013-10-11 18:45:01 +02:00
}
2013-12-16 14:09:01 +01:00
} ;
2013-10-22 19:58:12 +02:00
// Here be scrolling dragons
$timeout ( scroll ) ;
$timeout ( scroll , 100 ) ;
2013-10-16 23:59:27 +02:00
$timeout ( scroll , 300 ) ;
2013-10-22 19:58:12 +02:00
$timeout ( scroll , 500 ) ;
2013-12-16 14:09:01 +01:00
} ;
2013-10-07 16:58:41 +02:00
2013-02-16 19:18:14 +01:00
2013-02-18 00:49:42 +01:00
$scope . connect = function ( ) {
2014-02-25 21:41:41 +01:00
$scope . requestNotificationPermission ( ) ;
2014-04-22 19:03:12 +02:00
$rootScope . sslError = false ;
2014-05-06 21:48:40 +02:00
$rootScope . securityError = false ;
2014-04-22 19:03:12 +02:00
$rootScope . errorMessage = false ;
2014-05-06 21:59:10 +02:00
$scope . connectbutton = 'Connecting ...' ;
2013-12-20 17:10:58 +01:00
connection . connect ( $scope . host , $scope . port , $scope . password , $scope . ssl ) ;
2013-12-16 14:09:01 +01:00
} ;
2013-10-16 14:44:44 +02:00
$scope . disconnect = function ( ) {
2014-05-06 21:59:10 +02:00
$scope . connectbutton = 'Connect' ;
2013-10-16 14:25:07 +02:00
connection . disconnect ( ) ;
2013-12-16 14:09:01 +01:00
} ;
2013-10-21 15:51:05 +02:00
$scope . install = function ( ) {
2014-02-08 13:49:21 +01:00
if ( navigator . mozApps !== undefined ) {
2014-02-22 13:45:36 +01:00
// Find absolute url with trailing '/' or '/index.html' removed
var base _url = location . protocol + '//' + location . host +
location . pathname . replace ( /\/(index\.html)?$/ , '' ) ;
var request = navigator . mozApps . install ( base _url + '/manifest.webapp' ) ;
2013-10-21 15:51:05 +02:00
request . onsuccess = function ( ) {
$scope . isinstalled = true ;
// Save the App object that is returned
var appRecord = this . result ;
// Start the app.
appRecord . launch ( ) ;
alert ( 'Installation successful!' ) ;
} ;
request . onerror = function ( ) {
// Display the error information from the DOMError object
alert ( 'Install failed, error: ' + this . error . name ) ;
} ;
2014-02-08 13:49:21 +01:00
} else {
2013-10-21 15:51:05 +02:00
alert ( 'Sorry. Only supported in Firefox v26+' ) ;
}
2013-12-16 14:09:01 +01:00
} ;
2013-10-21 15:51:05 +02:00
2013-10-06 12:34:41 +02:00
/* Function gets called from bufferLineAdded code if user should be notified */
2013-10-09 17:53:25 +02:00
$rootScope . createHighlight = function ( buffer , message ) {
2014-02-21 00:26:50 +01:00
var title = '' ;
2014-02-24 15:31:10 +01:00
var body = '' ;
2014-03-13 21:33:46 +01:00
var numNotifications = buffer . notification ;
2014-02-21 10:49:03 +01:00
if ( [ '#' , '&' , '+' , '!' ] . indexOf ( buffer . shortName . charAt ( 0 ) ) < 0 ) {
2014-03-13 21:33:46 +01:00
if ( numNotifications > 1 ) {
title = numNotifications . toString ( ) + ' private messages from ' ;
} else {
title = 'Private message from ' ;
}
2014-02-24 15:31:10 +01:00
body = message . text ;
2014-02-21 10:49:03 +01:00
} else {
2014-03-13 21:33:46 +01:00
if ( numNotifications > 1 ) {
title = numNotifications . toString ( ) + ' highlights in ' ;
} else {
title = 'Highlight in ' ;
}
2014-03-05 14:01:22 +01:00
var prefix = '' ;
for ( var i = 0 ; i < message . prefix . length ; i ++ ) {
prefix += message . prefix [ i ] . text ;
}
body = '<' + prefix + '> ' + message . text ;
2014-02-21 00:26:50 +01:00
}
title += buffer . shortName ;
title += buffer . fullName . replace ( /irc.([^\.]+)\..+/ , " ($1)" ) ;
2013-10-06 12:34:41 +02:00
2014-02-21 00:26:50 +01:00
var notification = new Notification ( title , {
2014-02-24 15:31:10 +01:00
body : body ,
2014-03-18 10:53:23 +01:00
icon : 'assets/img/favicon.png'
2014-02-21 00:26:50 +01:00
} ) ;
2013-10-06 12:34:41 +02:00
// Cancel notification automatically
2014-02-21 00:26:50 +01:00
var timeout = 15 * 1000 ;
2013-10-06 12:34:41 +02:00
notification . onshow = function ( ) {
2013-12-17 21:30:22 +01:00
setTimeout ( function ( ) {
2013-12-16 14:09:01 +01:00
notification . close ( ) ;
} , timeout ) ;
} ;
2014-02-21 00:26:50 +01:00
// Click takes the user to the buffer
notification . onclick = function ( ) {
models . setActiveBuffer ( buffer . id ) ;
window . focus ( ) ;
notification . close ( ) ;
} ;
2014-03-01 19:55:22 +01:00
if ( $scope . soundnotification ) {
// TODO fill in a sound file
2014-03-04 14:26:32 +01:00
var audioFile = "assets/audio/sonar" ;
2014-03-01 19:55:22 +01:00
var soundHTML = '<audio autoplay="autoplay"><source src="' + audioFile + '.ogg" type="audio/ogg" /><source src="' + audioFile + '.mp3" type="audio/mpeg" /></audio>' ;
document . getElementById ( "soundNotification" ) . innerHTML = soundHTML ;
}
2013-10-06 12:34:41 +02:00
} ;
2013-10-11 14:44:05 +02:00
$scope . hasUnread = function ( buffer ) {
2013-12-17 21:30:22 +01:00
// if search is set, return every buffer
2014-02-08 13:49:21 +01:00
if ( $scope . search && $scope . search !== "" ) {
2013-10-11 23:47:47 +02:00
return true ;
}
2014-02-08 13:49:21 +01:00
if ( $scope . onlyUnread ) {
2013-10-19 20:11:01 +02:00
// Always show current buffer in list
2014-02-08 14:20:33 +01:00
if ( models . getActiveBuffer ( ) === buffer ) {
2013-10-19 20:11:01 +02:00
return true ;
}
return buffer . unread > 0 || buffer . notification > 0 ;
}
return true ;
2013-10-11 14:44:05 +02:00
} ;
2013-10-11 15:59:55 +02:00
2013-10-27 10:48:20 +01:00
// Watch model and update show setting when it changes
$scope . $watch ( 'nonicklist' , function ( ) {
$scope . showNicklist = $scope . updateShowNicklist ( ) ;
} ) ;
$scope . showNicklist = false ;
2013-12-17 21:30:22 +01:00
// Utility function that template can use to check if nicklist should
2013-10-27 10:48:20 +01:00
// be displayed for current buffer or not
// is called on buffer switch
$scope . updateShowNicklist = function ( ) {
var ab = models . getActiveBuffer ( ) ;
2014-02-08 13:49:21 +01:00
if ( ! ab ) {
2013-10-27 10:48:20 +01:00
return false ;
}
// Check if option no nicklist is set
2014-02-08 13:49:21 +01:00
if ( $scope . nonicklist ) {
2013-10-27 10:48:20 +01:00
return false ;
}
2014-03-07 18:52:32 +01:00
// Check if nicklist is empty
if ( ab . isNicklistEmpty ( ) ) {
2013-10-27 10:48:20 +01:00
return false ;
}
return true ;
2013-12-16 14:09:01 +01:00
} ;
2013-10-27 10:48:20 +01:00
2013-10-11 15:59:55 +02:00
$rootScope . switchToActivityBuffer = function ( ) {
// Find next buffer with activity and switch to it
2014-07-02 00:07:34 +02:00
var sortedBuffers = _ . sortBy ( $scope . getBuffers ( ) , 'number' ) ;
2014-02-17 13:37:14 +01:00
var i , buffer ;
2014-02-18 19:13:23 +01:00
// Try to find buffer with notification
2014-02-17 13:37:14 +01:00
for ( i in sortedBuffers ) {
buffer = sortedBuffers [ i ] ;
2014-02-08 13:49:21 +01:00
if ( buffer . notification > 0 ) {
2013-10-11 15:59:55 +02:00
$scope . setActiveBuffer ( buffer . id ) ;
2014-02-21 17:03:35 +01:00
return ; // return instead of break so that the second for loop isn't executed
2014-02-17 13:31:23 +01:00
}
}
2014-02-18 19:13:23 +01:00
// No notifications, find first buffer with unread lines instead
2014-02-17 13:37:14 +01:00
for ( i in sortedBuffers ) {
buffer = sortedBuffers [ i ] ;
2014-02-18 19:13:23 +01:00
if ( buffer . unread > 0 ) {
2013-10-11 15:59:55 +02:00
$scope . setActiveBuffer ( buffer . id ) ;
2014-02-21 17:03:35 +01:00
return ;
2013-10-11 15:59:55 +02:00
}
2013-10-11 22:43:01 +02:00
}
2013-12-16 14:09:01 +01:00
} ;
2014-02-28 00:31:32 +01:00
// Helper function since the keypress handler is in a different scope
$rootScope . toggleNicklist = function ( ) {
$scope . nonicklist = ! $scope . nonicklist ;
2014-02-28 03:25:59 +01:00
} ;
2013-10-11 15:59:55 +02:00
2013-10-27 07:48:30 +01:00
2013-10-17 13:13:01 +02:00
$scope . handleSearchBoxKey = function ( $event ) {
// Support different browser quirks
var code = $event . keyCode ? $event . keyCode : $event . charCode ;
// Handle escape
2014-02-08 14:20:33 +01:00
if ( code === 27 ) {
2013-10-19 20:11:01 +02:00
$event . preventDefault ( ) ;
$scope . search = '' ;
2013-10-17 13:13:01 +02:00
} // Handle enter
2014-02-08 14:20:33 +01:00
else if ( code === 13 ) {
2013-10-19 20:11:01 +02:00
$event . preventDefault ( ) ;
2014-02-21 09:37:55 +01:00
if ( $scope . filteredBuffers . length > 0 ) {
models . setActiveBuffer ( $scope . filteredBuffers [ 0 ] . id ) ;
}
2013-10-19 20:11:01 +02:00
$scope . search = '' ;
2013-10-17 13:13:01 +02:00
}
2013-12-16 14:09:01 +01:00
} ;
2013-10-15 15:21:13 +02:00
2013-10-28 13:55:46 +01:00
// Prevent user from accidentally leaving the page
window . onbeforeunload = function ( event ) {
2014-02-21 00:52:45 +01:00
if ( $rootScope . connected ) {
event . preventDefault ( ) ;
// Chrome requires us to set this or it will not show the dialog
2014-04-19 14:13:32 +02:00
event . returnValue = "You have an active connection to your WeeChat relay. Please disconnect using the button in the top-right corner or by double-tapping the Escape key." ;
2014-02-21 00:52:45 +01:00
}
2014-02-10 20:26:30 +01:00
$scope . favico . reset ( ) ;
2013-10-28 13:55:46 +01:00
} ;
2013-08-02 03:54:12 +02:00
} ]
2013-10-26 10:30:35 +02:00
) ;
2013-10-27 20:49:51 +01:00
2013-12-17 21:30:22 +01:00
weechat . config ( [ '$routeProvider' ,
2013-10-27 20:49:51 +01:00
function ( $routeProvider ) {
$routeProvider . when ( '/' , {
templateUrl : 'index.html' ,
2013-12-17 21:39:22 +01:00
controller : 'WeechatCtrl'
2013-10-27 20:49:51 +01:00
} ) ;
}
] ) ;
2013-10-27 21:41:27 +01:00
2014-02-07 02:59:50 +01:00
weechat . directive ( 'plugin' , function ( ) {
2014-02-07 03:21:49 +01:00
/ *
* Plugin directive
* Shows additional plugin content
* /
2014-02-07 02:59:50 +01:00
return {
templateUrl : 'directives/plugin.html' ,
scope : {
2014-03-19 19:02:20 +01:00
plugin : '=data'
2014-02-07 02:59:50 +01:00
} ,
2014-02-07 03:21:49 +01:00
controller : function ( $scope ) {
$scope . displayedContent = "" ;
$scope . hideContent = function ( ) {
$scope . plugin . visible = false ;
2014-02-08 13:49:21 +01:00
} ;
2014-02-07 03:21:49 +01:00
$scope . showContent = function ( ) {
/ *
* Shows the plugin content .
* displayedContent is bound to the DOM .
* Actual plugin content is only fetched when
* content is shown .
* /
$scope . displayedContent = $scope . plugin . content ;
$scope . plugin . visible = true ;
2014-02-10 21:42:19 +01:00
// Scroll embed content into view
2014-02-13 21:46:18 +01:00
var scroll = function ( ) {
2014-02-15 15:42:53 +01:00
var embed = document . querySelector ( ".embed_" + $scope . plugin . $$hashKey ) ;
if ( embed ) {
embed . scrollIntoViewIfNeeded ( ) ;
}
2014-02-13 21:46:18 +01:00
} ;
setTimeout ( scroll , 100 ) ;
2014-02-08 13:49:21 +01:00
} ;
2014-02-07 03:21:49 +01:00
}
2014-02-08 13:49:21 +01:00
} ;
2014-02-07 02:59:50 +01:00
} ) ;
2013-10-27 21:41:27 +01:00
weechat . directive ( 'inputBar' , function ( ) {
2013-10-27 22:04:21 +01:00
2013-10-27 21:41:27 +01:00
return {
templateUrl : 'directives/input.html' ,
2014-07-01 20:41:27 +02:00
2014-02-25 15:12:52 +01:00
scope : {
2014-03-19 19:02:20 +01:00
inputId : '@inputId'
2014-02-25 15:12:52 +01:00
} ,
2014-02-25 15:14:08 +01:00
2013-10-27 21:41:27 +01:00
controller : function ( $rootScope ,
$scope ,
2014-02-23 16:48:21 +01:00
$element ,
2014-05-06 18:38:20 +02:00
$log ,
2013-10-27 21:41:27 +01:00
connection ,
models ) {
2014-02-23 16:48:21 +01:00
/ *
* Returns the input element
* /
$scope . getInputNode = function ( ) {
2014-04-28 14:52:21 +02:00
return document . querySelector ( 'textarea#' + $scope . inputId ) ;
2014-02-23 16:48:21 +01:00
} ;
2014-02-23 16:50:55 +01:00
2013-10-27 22:09:38 +01:00
$scope . completeNick = function ( ) {
2013-10-27 21:41:27 +01:00
// input DOM node
2014-02-23 16:50:55 +01:00
var inputNode = $scope . getInputNode ( ) ;
2013-10-27 21:41:27 +01:00
// get current caret position
var caretPos = inputNode . selectionStart ;
2014-04-25 21:05:20 +02:00
// get current active buffer
2013-10-27 21:41:27 +01:00
var activeBuffer = models . getActiveBuffer ( ) ;
2014-05-13 13:16:05 +02:00
// Empty input makes $scope.command undefined -- use empty string instead
var input = $scope . command || '' ;
2013-10-27 21:41:27 +01:00
// complete nick
2014-05-13 13:16:05 +02:00
var nickComp = IrcUtils . completeNick ( input , caretPos , $scope . iterCandidate ,
activeBuffer . getNicklistByTime ( ) , ':' ) ;
2013-10-27 21:41:27 +01:00
// remember iteration candidate
2013-10-27 22:09:38 +01:00
$scope . iterCandidate = nickComp . iterCandidate ;
2013-10-27 21:41:27 +01:00
// update current input
2014-05-08 00:04:12 +02:00
$scope . command = nickComp . text ;
2013-10-27 21:41:27 +01:00
// update current caret position
inputNode . focus ( ) ;
inputNode . setSelectionRange ( nickComp . caretPos , nickComp . caretPos ) ;
2013-12-16 14:09:01 +01:00
} ;
2013-10-27 21:41:27 +01:00
// Send the message to the websocket
$scope . sendMessage = function ( ) {
2014-05-06 18:38:20 +02:00
var ab = models . getActiveBuffer ( ) ;
2014-05-07 18:33:47 +02:00
// It's undefined early in the lifecycle of the program.
// Don't send empty commands
if ( $scope . command !== undefined && $scope . command !== '' ) {
2014-05-06 18:38:20 +02:00
2014-05-07 18:33:47 +02:00
// log to buffer history
ab . addToHistory ( $scope . command ) ;
2014-05-06 18:38:20 +02:00
2014-05-07 18:33:47 +02:00
// Split the command into multiple commands based on line breaks
_ . each ( $scope . command . split ( /\r?\n/ ) , function ( line ) {
connection . sendMessage ( line ) ;
} ) ;
2014-05-06 18:38:20 +02:00
2014-05-07 18:33:47 +02:00
// 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 = '' ;
}
2014-07-04 15:44:40 +02:00
$scope . getInputNode ( ) . focus ( ) ;
2013-12-16 14:09:01 +01:00
} ;
2013-10-27 21:41:27 +01:00
2014-05-09 10:13:13 +02:00
$rootScope . addMention = function ( prefix ) {
2014-05-08 00:38:26 +02:00
// 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 ( ) ;
} ;
2013-10-27 21:41:27 +01:00
// Handle key presses in the input bar
2014-02-25 15:12:52 +01:00
$rootScope . handleKeyPress = function ( $event ) {
2013-10-27 21:41:27 +01:00
// don't do anything if not connected
if ( ! $rootScope . connected ) {
return true ;
}
2013-12-17 21:30:22 +01:00
2014-02-23 16:50:55 +01:00
var inputNode = $scope . getInputNode ( ) ;
2013-10-27 21:41:27 +01:00
// Support different browser quirks
var code = $event . keyCode ? $event . keyCode : $event . charCode ;
// any other key than Tab resets nick completion iteration
2013-10-27 22:09:38 +01:00
var tmpIterCandidate = $scope . iterCandidate ;
$scope . iterCandidate = null ;
2013-10-27 21:41:27 +01:00
// Left Alt+[0-9] -> jump to buffer
if ( $event . altKey && ! $event . ctrlKey && ( code > 47 && code < 58 ) ) {
2014-02-08 14:20:33 +01:00
if ( code === 48 ) {
2013-10-27 21:41:27 +01:00
code = 58 ;
}
2014-02-26 19:39:48 +01:00
var bufferNumber = code - 48 - 1 ;
var activeBufferId = Object . keys ( models . getBuffers ( ) ) [ bufferNumber ] ;
if ( activeBufferId ) {
models . setActiveBuffer ( activeBufferId ) ;
2013-10-27 21:41:27 +01:00
$event . preventDefault ( ) ;
}
}
// Tab -> nick completion
2014-02-08 14:20:33 +01:00
if ( code === 9 && ! $event . altKey && ! $event . ctrlKey ) {
2013-10-27 21:41:27 +01:00
$event . preventDefault ( ) ;
2013-10-27 22:09:38 +01:00
$scope . iterCandidate = tmpIterCandidate ;
$scope . completeNick ( ) ;
2013-10-27 21:41:27 +01:00
return true ;
}
2013-10-29 12:50:02 +01:00
// Left Alt+n -> toggle nicklist
2014-02-08 14:20:33 +01:00
if ( $event . altKey && ! $event . ctrlKey && code === 78 ) {
2013-10-29 12:50:02 +01:00
$event . preventDefault ( ) ;
2014-02-28 00:31:32 +01:00
$rootScope . toggleNicklist ( ) ;
2013-10-29 12:50:02 +01:00
return true ;
}
2013-10-27 21:41:27 +01:00
// Alt+A -> switch to buffer with activity
2014-02-08 14:20:33 +01:00
if ( $event . altKey && ( code === 97 || code === 65 ) ) {
2013-10-27 21:41:27 +01:00
$event . preventDefault ( ) ;
2014-02-25 17:33:05 +01:00
$rootScope . switchToActivityBuffer ( ) ;
2013-10-27 21:41:27 +01:00
return true ;
}
// Alt+L -> focus on input bar
2014-02-08 14:20:33 +01:00
if ( $event . altKey && ( code === 76 || code === 108 ) ) {
2013-10-27 21:41:27 +01:00
$event . preventDefault ( ) ;
inputNode . focus ( ) ;
2014-05-08 00:04:12 +02:00
inputNode . setSelectionRange ( $scope . command . length , $scope . command . length ) ;
2013-10-27 21:41:27 +01:00
return true ;
}
2014-02-19 10:56:44 +01:00
// Alt+< -> switch to previous buffer
2014-02-28 00:13:21 +01:00
if ( $event . altKey && ( code === 60 || code === 226 ) ) {
2014-02-19 10:56:44 +01:00
var previousBuffer = models . getPreviousBuffer ( ) ;
if ( previousBuffer ) {
models . setActiveBuffer ( previousBuffer . id ) ;
$event . preventDefault ( ) ;
return true ;
}
}
2013-10-27 21:41:27 +01:00
2014-04-19 14:13:32 +02:00
// Double-tap Escape -> disconnect
2014-02-08 14:20:33 +01:00
if ( code === 27 ) {
2013-10-27 21:41:27 +01:00
$event . preventDefault ( ) ;
2014-04-19 14:13:32 +02:00
if ( typeof $scope . lastEscape !== "undefined" && ( Date . now ( ) - $scope . lastEscape ) <= 500 ) {
// Double-tap
connection . disconnect ( ) ;
}
$scope . lastEscape = Date . now ( ) ;
2013-10-27 21:41:27 +01:00
return true ;
}
2014-02-21 08:17:44 +01:00
// Alt+G -> focus on buffer filter input
if ( $event . altKey && ( code === 103 || code === 71 ) ) {
2013-10-27 21:41:27 +01:00
$event . preventDefault ( ) ;
document . getElementById ( 'bufferFilter' ) . focus ( ) ;
return true ;
}
2013-12-17 20:37:45 +01:00
// Arrow up -> go up in history
2014-02-08 14:20:33 +01:00
if ( code === 38 ) {
2014-05-08 00:04:12 +02:00
$scope . command = models . getActiveBuffer ( ) . getHistoryUp ( $scope . command ) ;
2014-04-05 22:18:48 +02:00
// Set cursor to last position. Need 0ms timeout because browser sets cursor
// position to the beginning after this key handler returns.
setTimeout ( function ( ) {
2014-05-08 00:04:12 +02:00
inputNode . setSelectionRange ( $scope . command . length , $scope . command . length ) ;
2014-04-05 22:18:48 +02:00
} , 0 ) ;
2013-12-17 20:37:45 +01:00
return true ;
}
// Arrow down -> go down in history
2014-02-08 14:20:33 +01:00
if ( code === 40 ) {
2014-05-08 00:04:12 +02:00
$scope . command = models . getActiveBuffer ( ) . getHistoryDown ( $scope . command ) ;
2014-04-05 22:18:48 +02:00
// We don't need to set the cursor to the rightmost position here, the browser does that for us
2013-12-17 20:37:45 +01:00
return true ;
}
2014-04-26 18:16:46 +02:00
// Enter to submit, shift-enter for newline
2014-05-04 12:21:35 +02:00
if ( code == 13 && ! $event . shiftKey && document . activeElement === inputNode ) {
2014-04-26 18:16:46 +02:00
$event . preventDefault ( ) ;
$scope . sendMessage ( ) ;
return true ;
}
2014-07-20 12:21:46 +02:00
// Some readline keybindings
if ( $event . ctrlKey && ! $event . altKey && ! $event . shiftKey && document . activeElement === inputNode ) {
// 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 ;
}
2013-12-16 14:09:01 +01:00
} ;
2013-10-27 21:41:27 +01:00
}
2013-12-16 14:09:01 +01:00
} ;
2013-10-27 21:41:27 +01:00
} ) ;