| 1 | _kiwi.view.ControlBox = Backbone.View.extend({ |
| 2 | events: { |
| 3 | 'keydown .inp': 'process', |
| 4 | 'click .nick': 'showNickChange' |
| 5 | }, |
| 6 | |
| 7 | initialize: function () { |
| 8 | var that = this; |
| 9 | |
| 10 | this.buffer = []; // Stores previously run commands |
| 11 | this.buffer_pos = 0; // The current position in the buffer |
| 12 | |
| 13 | this.preprocessor = new InputPreProcessor(); |
| 14 | this.preprocessor.recursive_depth = 5; |
| 15 | |
| 16 | // Hold tab autocomplete data |
| 17 | this.tabcomplete = {active: false, data: [], prefix: ''}; |
| 18 | |
| 19 | // Keep the nick view updated with nick changes |
| 20 | _kiwi.app.connections.on('change:nick', function(connection) { |
| 21 | // Only update the nick view if it's the active connection |
| 22 | if (connection !== _kiwi.app.connections.active_connection) |
| 23 | return; |
| 24 | |
| 25 | $('.nick', that.$el).text(connection.get('nick')); |
| 26 | }); |
| 27 | |
| 28 | // Update our nick view as we flick between connections |
| 29 | _kiwi.app.connections.on('active', function(panel, connection) { |
| 30 | $('.nick', that.$el).text(connection.get('nick')); |
| 31 | }); |
| 32 | |
| 33 | // Keep focus on the input box as we flick between panels |
| 34 | _kiwi.app.panels.bind('active', function (active_panel) { |
| 35 | if (active_panel.isChannel() || active_panel.isServer() || active_panel.isQuery()) { |
| 36 | that.$('.inp').focus(); |
| 37 | } |
| 38 | }); |
| 39 | }, |
| 40 | |
| 41 | render: function() { |
| 42 | var send_message_text = translateText('client_views_controlbox_message'); |
| 43 | this.$('.inp').attr('placeholder', send_message_text); |
| 44 | |
| 45 | return this; |
| 46 | }, |
| 47 | |
| 48 | showNickChange: function (ev) { |
| 49 | // Nick box already open? Don't do it again |
| 50 | if (this.nick_change) |
| 51 | return; |
| 52 | |
| 53 | this.nick_change = new _kiwi.view.NickChangeBox(); |
| 54 | this.nick_change.render(); |
| 55 | |
| 56 | this.listenTo(this.nick_change, 'close', function() { |
| 57 | delete this.nick_change; |
| 58 | }); |
| 59 | }, |
| 60 | |
| 61 | process: function (ev) { |
| 62 | var that = this, |
| 63 | inp = $(ev.currentTarget), |
| 64 | inp_val = inp.val(), |
| 65 | meta; |
| 66 | |
| 67 | if (navigator.appVersion.indexOf("Mac") !== -1) { |
| 68 | meta = ev.metaKey; |
| 69 | } else { |
| 70 | meta = ev.altKey; |
| 71 | } |
| 72 | |
| 73 | // If not a tab key, reset the tabcomplete data |
| 74 | if (this.tabcomplete.active && ev.keyCode !== 9) { |
| 75 | this.tabcomplete.active = false; |
| 76 | this.tabcomplete.data = []; |
| 77 | this.tabcomplete.prefix = ''; |
| 78 | } |
| 79 | |
| 80 | switch (true) { |
| 81 | case (ev.keyCode === 13): // return |
| 82 | inp_val = inp_val.trim(); |
| 83 | |
| 84 | if (inp_val) { |
| 85 | $.each(inp_val.split('\n'), function (idx, line) { |
| 86 | that.processInput(line); |
| 87 | }); |
| 88 | |
| 89 | this.buffer.push(inp_val); |
| 90 | this.buffer_pos = this.buffer.length; |
| 91 | } |
| 92 | |
| 93 | inp.val(''); |
| 94 | return false; |
| 95 | |
| 96 | break; |
| 97 | |
| 98 | case (ev.keyCode === 38): // up |
| 99 | if (this.buffer_pos > 0) { |
| 100 | this.buffer_pos--; |
| 101 | inp.val(this.buffer[this.buffer_pos]); |
| 102 | } |
| 103 | //suppress browsers default behavior as it would set the cursor at the beginning |
| 104 | return false; |
| 105 | |
| 106 | case (ev.keyCode === 40): // down |
| 107 | if (this.buffer_pos < this.buffer.length) { |
| 108 | this.buffer_pos++; |
| 109 | inp.val(this.buffer[this.buffer_pos]); |
| 110 | } |
| 111 | break; |
| 112 | |
| 113 | case (ev.keyCode === 219 && meta): // [ + meta |
| 114 | // Find all the tab elements and get the index of the active tab |
| 115 | var $tabs = $('#kiwi .tabs').find('li[class!=connection]'); |
| 116 | var cur_tab_ind = (function() { |
| 117 | for (var idx=0; idx<$tabs.length; idx++){ |
| 118 | if ($($tabs[idx]).hasClass('active')) |
| 119 | return idx; |
| 120 | } |
| 121 | })(); |
| 122 | |
| 123 | // Work out the previous tab along. Wrap around if needed |
| 124 | if (cur_tab_ind === 0) { |
| 125 | $prev_tab = $($tabs[$tabs.length - 1]); |
| 126 | } else { |
| 127 | $prev_tab = $($tabs[cur_tab_ind - 1]); |
| 128 | } |
| 129 | |
| 130 | $prev_tab.click(); |
| 131 | return false; |
| 132 | |
| 133 | case (ev.keyCode === 221 && meta): // ] + meta |
| 134 | // Find all the tab elements and get the index of the active tab |
| 135 | var $tabs = $('#kiwi .tabs').find('li[class!=connection]'); |
| 136 | var cur_tab_ind = (function() { |
| 137 | for (var idx=0; idx<$tabs.length; idx++){ |
| 138 | if ($($tabs[idx]).hasClass('active')) |
| 139 | return idx; |
| 140 | } |
| 141 | })(); |
| 142 | |
| 143 | // Work out the next tab along. Wrap around if needed |
| 144 | if (cur_tab_ind === $tabs.length - 1) { |
| 145 | $next_tab = $($tabs[0]); |
| 146 | } else { |
| 147 | $next_tab = $($tabs[cur_tab_ind + 1]); |
| 148 | } |
| 149 | |
| 150 | $next_tab.click(); |
| 151 | return false; |
| 152 | |
| 153 | case (ev.keyCode === 9 //Check if ONLY tab is pressed |
| 154 | && !ev.shiftKey //(user could be using some browser |
| 155 | && !ev.altKey //keyboard shortcut) |
| 156 | && !ev.metaKey |
| 157 | && !ev.ctrlKey): |
| 158 | this.tabcomplete.active = true; |
| 159 | if (_.isEqual(this.tabcomplete.data, [])) { |
| 160 | // Get possible autocompletions |
| 161 | var ac_data = [], |
| 162 | members = _kiwi.app.panels().active.get('members'); |
| 163 | |
| 164 | // If we have a members list, get the models. Otherwise empty array |
| 165 | members = members ? members.models : []; |
| 166 | |
| 167 | $.each(members, function (i, member) { |
| 168 | if (!member) return; |
| 169 | ac_data.push(member.get('nick')); |
| 170 | }); |
| 171 | |
| 172 | ac_data.push(_kiwi.app.panels().active.get('name')); |
| 173 | |
| 174 | ac_data = _.sortBy(ac_data, function (nick) { |
| 175 | return nick; |
| 176 | }); |
| 177 | this.tabcomplete.data = ac_data; |
| 178 | } |
| 179 | |
| 180 | if (inp_val[inp[0].selectionStart - 1] === ' ') { |
| 181 | return false; |
| 182 | } |
| 183 | |
| 184 | (function () { |
| 185 | var tokens, // Words before the cursor position |
| 186 | val, // New value being built up |
| 187 | p1, // Position in the value just before the nick |
| 188 | newnick, // New nick to be displayed (cycles through) |
| 189 | range, // TextRange for setting new text cursor position |
| 190 | nick, // Current nick in the value |
| 191 | trailing = ': '; // Text to be inserted after a tabbed nick |
| 192 | |
| 193 | tokens = inp_val.substring(0, inp[0].selectionStart).split(' '); |
| 194 | if (tokens[tokens.length-1] == ':') |
| 195 | tokens.pop(); |
| 196 | |
| 197 | // Only add the trailing text if not at the beginning of the line |
| 198 | if (tokens.length > 1) |
| 199 | trailing = ''; |
| 200 | |
| 201 | nick = tokens[tokens.length - 1]; |
| 202 | |
| 203 | if (this.tabcomplete.prefix === '') { |
| 204 | this.tabcomplete.prefix = nick; |
| 205 | } |
| 206 | |
| 207 | this.tabcomplete.data = _.select(this.tabcomplete.data, function (n) { |
| 208 | return (n.toLowerCase().indexOf(that.tabcomplete.prefix.toLowerCase()) === 0); |
| 209 | }); |
| 210 | |
| 211 | if (this.tabcomplete.data.length > 0) { |
| 212 | // Get the current value before cursor position |
| 213 | p1 = inp[0].selectionStart - (nick.length); |
| 214 | val = inp_val.substr(0, p1); |
| 215 | |
| 216 | // Include the current selected nick |
| 217 | newnick = this.tabcomplete.data.shift(); |
| 218 | this.tabcomplete.data.push(newnick); |
| 219 | val += newnick; |
| 220 | |
| 221 | if (inp_val.substr(inp[0].selectionStart, 2) !== trailing) |
| 222 | val += trailing; |
| 223 | |
| 224 | // Now include the rest of the current value |
| 225 | val += inp_val.substr(inp[0].selectionStart); |
| 226 | |
| 227 | inp.val(val); |
| 228 | |
| 229 | // Move the cursor position to the end of the nick |
| 230 | if (inp[0].setSelectionRange) { |
| 231 | inp[0].setSelectionRange(p1 + newnick.length + trailing.length, p1 + newnick.length + trailing.length); |
| 232 | } else if (inp[0].createTextRange) { // not sure if this bit is actually needed.... |
| 233 | range = inp[0].createTextRange(); |
| 234 | range.collapse(true); |
| 235 | range.moveEnd('character', p1 + newnick.length + trailing.length); |
| 236 | range.moveStart('character', p1 + newnick.length + trailing.length); |
| 237 | range.select(); |
| 238 | } |
| 239 | } |
| 240 | }).apply(this); |
| 241 | return false; |
| 242 | } |
| 243 | }, |
| 244 | |
| 245 | |
| 246 | processInput: function (command_raw) { |
| 247 | var command, params, |
| 248 | pre_processed; |
| 249 | |
| 250 | // If sending a message when not in a channel or query window, automatically |
| 251 | // convert it into a command |
| 252 | if (command_raw[0] !== '/' && !_kiwi.app.panels().active.isChannel() && !_kiwi.app.panels().active.isQuery()) { |
| 253 | command_raw = '/' + command_raw; |
| 254 | } |
| 255 | |
| 256 | // The default command |
| 257 | if (command_raw[0] !== '/' || command_raw.substr(0, 2) === '//') { |
| 258 | // Remove any slash escaping at the start (ie. //) |
| 259 | command_raw = command_raw.replace(/^\/\//, '/'); |
| 260 | |
| 261 | // Prepend the default command |
| 262 | command_raw = '/msg ' + _kiwi.app.panels().active.get('name') + ' ' + command_raw; |
| 263 | } |
| 264 | |
| 265 | // Process the raw command for any aliases |
| 266 | this.preprocessor.vars.server = _kiwi.app.connections.active_connection.get('name'); |
| 267 | this.preprocessor.vars.channel = _kiwi.app.panels().active.get('name'); |
| 268 | this.preprocessor.vars.destination = this.preprocessor.vars.channel; |
| 269 | command_raw = this.preprocessor.process(command_raw); |
| 270 | |
| 271 | // Extract the command and parameters |
| 272 | params = command_raw.split(/\s/); |
| 273 | if (params[0][0] === '/') { |
| 274 | command = params[0].substr(1).toLowerCase(); |
| 275 | params = params.splice(1, params.length - 1); |
| 276 | } else { |
| 277 | // Default command |
| 278 | command = 'msg'; |
| 279 | params.unshift(_kiwi.app.panels().active.get('name')); |
| 280 | } |
| 281 | |
| 282 | // Trigger the command events |
| 283 | this.trigger('command', {command: command, params: params}); |
| 284 | this.trigger('command:' + command, {command: command, params: params}); |
| 285 | |
| 286 | // If we didn't have any listeners for this event, fire a special case |
| 287 | // TODO: This feels dirty. Should this really be done..? |
| 288 | if (!this._events['command:' + command]) { |
| 289 | this.trigger('unknown_command', {command: command, params: params}); |
| 290 | } |
| 291 | }, |
| 292 | |
| 293 | |
| 294 | addPluginIcon: function ($icon) { |
| 295 | var $tool = $('<div class="tool"></div>').append($icon); |
| 296 | this.$el.find('.input_tools').append($tool); |
| 297 | _kiwi.app.view.doLayout(); |
| 298 | } |
| 299 | }); |