Merge branch 'development'
[KiwiIRC.git] / client / src / views / controlbox.js
CommitLineData
50ac472f
D
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 });
9076c054
D
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 });
50ac472f
D
39 },
40
f4d69a63
D
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
50ac472f 48 showNickChange: function (ev) {
4caae868
D
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 });
50ac472f
D
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 }
4caae868 79
50ac472f
D
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
c71ae076 153 case (ev.keyCode === 9 //Check if ONLY tab is pressed
4caae868 154 && !ev.shiftKey //(user could be using some browser
c71ae076 155 && !ev.altKey //keyboard shortcut)
4caae868
D
156 && !ev.metaKey
157 && !ev.ctrlKey):
50ac472f
D
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) {
5a719670 175 return nick.toLowerCase();
50ac472f
D
176 });
177 this.tabcomplete.data = ac_data;
178 }
179
180 if (inp_val[inp[0].selectionStart - 1] === ' ') {
181 return false;
182 }
4caae868 183
50ac472f
D
184 (function () {
185 var tokens, // Words before the cursor position
186 val, // New value being built up
4caae868 187 p1, // Position in the value just before the nick
50ac472f
D
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
9a0720d3
D
197 // Only add the trailing text if not at the beginning of the line
198 if (tokens.length > 1)
199 trailing = '';
200
50ac472f
D
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) {
8773ce62
D
247 var that = this,
248 command, params, events_data,
50ac472f 249 pre_processed;
4caae868 250
039146ea
D
251 // If sending a message when not in a channel or query window, automatically
252 // convert it into a command
253 if (command_raw[0] !== '/' && !_kiwi.app.panels().active.isChannel() && !_kiwi.app.panels().active.isQuery()) {
254 command_raw = '/' + command_raw;
255 }
256
50ac472f
D
257 // The default command
258 if (command_raw[0] !== '/' || command_raw.substr(0, 2) === '//') {
19318643
VC
259 // Remove any slash escaping at the start (ie. //)
260 command_raw = command_raw.replace(/^\/\//, '/');
50ac472f
D
261
262 // Prepend the default command
263 command_raw = '/msg ' + _kiwi.app.panels().active.get('name') + ' ' + command_raw;
264 }
265
266 // Process the raw command for any aliases
267 this.preprocessor.vars.server = _kiwi.app.connections.active_connection.get('name');
268 this.preprocessor.vars.channel = _kiwi.app.panels().active.get('name');
269 this.preprocessor.vars.destination = this.preprocessor.vars.channel;
270 command_raw = this.preprocessor.process(command_raw);
271
272 // Extract the command and parameters
19318643 273 params = command_raw.split(/\s/);
50ac472f
D
274 if (params[0][0] === '/') {
275 command = params[0].substr(1).toLowerCase();
276 params = params.splice(1, params.length - 1);
277 } else {
278 // Default command
279 command = 'msg';
280 params.unshift(_kiwi.app.panels().active.get('name'));
281 }
282
8773ce62
D
283 // Emit a plugin event for any modifications
284 events_data = {command: command, params: params};
50ac472f 285
8773ce62 286 _kiwi.global.events.emit('command', events_data)
060391b1 287 .then(function() {
8773ce62
D
288 // Trigger the command events
289 that.trigger('command', {command: events_data.command, params: events_data.params});
290 that.trigger('command:' + events_data.command, {command: events_data.command, params: events_data.params});
291
292 // If we didn't have any listeners for this event, fire a special case
293 // TODO: This feels dirty. Should this really be done..?
294 if (!that._events['command:' + events_data.command]) {
295 that.trigger('unknown_command', {command: events_data.command, params: events_data.params});
296 }
297 });
50ac472f
D
298 },
299
300
301 addPluginIcon: function ($icon) {
302 var $tool = $('<div class="tool"></div>').append($icon);
303 this.$el.find('.input_tools').append($tool);
304 _kiwi.app.view.doLayout();
305 }
c71ae076 306});