Client: view.js split up into multiple files
[KiwiIRC.git] / client / assets / 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 });
32 },
33
34 showNickChange: function (ev) {
35 (new _kiwi.view.NickChangeBox()).render();
36 },
37
38 process: function (ev) {
39 var that = this,
40 inp = $(ev.currentTarget),
41 inp_val = inp.val(),
42 meta;
43
44 if (navigator.appVersion.indexOf("Mac") !== -1) {
45 meta = ev.metaKey;
46 } else {
47 meta = ev.altKey;
48 }
49
50 // If not a tab key, reset the tabcomplete data
51 if (this.tabcomplete.active && ev.keyCode !== 9) {
52 this.tabcomplete.active = false;
53 this.tabcomplete.data = [];
54 this.tabcomplete.prefix = '';
55 }
56
57 switch (true) {
58 case (ev.keyCode === 13): // return
59 inp_val = inp_val.trim();
60
61 if (inp_val) {
62 $.each(inp_val.split('\n'), function (idx, line) {
63 that.processInput(line);
64 });
65
66 this.buffer.push(inp_val);
67 this.buffer_pos = this.buffer.length;
68 }
69
70 inp.val('');
71 return false;
72
73 break;
74
75 case (ev.keyCode === 38): // up
76 if (this.buffer_pos > 0) {
77 this.buffer_pos--;
78 inp.val(this.buffer[this.buffer_pos]);
79 }
80 //suppress browsers default behavior as it would set the cursor at the beginning
81 return false;
82
83 case (ev.keyCode === 40): // down
84 if (this.buffer_pos < this.buffer.length) {
85 this.buffer_pos++;
86 inp.val(this.buffer[this.buffer_pos]);
87 }
88 break;
89
90 case (ev.keyCode === 219 && meta): // [ + meta
91 // Find all the tab elements and get the index of the active tab
92 var $tabs = $('#kiwi .tabs').find('li[class!=connection]');
93 var cur_tab_ind = (function() {
94 for (var idx=0; idx<$tabs.length; idx++){
95 if ($($tabs[idx]).hasClass('active'))
96 return idx;
97 }
98 })();
99
100 // Work out the previous tab along. Wrap around if needed
101 if (cur_tab_ind === 0) {
102 $prev_tab = $($tabs[$tabs.length - 1]);
103 } else {
104 $prev_tab = $($tabs[cur_tab_ind - 1]);
105 }
106
107 $prev_tab.click();
108 return false;
109
110 case (ev.keyCode === 221 && meta): // ] + meta
111 // Find all the tab elements and get the index of the active tab
112 var $tabs = $('#kiwi .tabs').find('li[class!=connection]');
113 var cur_tab_ind = (function() {
114 for (var idx=0; idx<$tabs.length; idx++){
115 if ($($tabs[idx]).hasClass('active'))
116 return idx;
117 }
118 })();
119
120 // Work out the next tab along. Wrap around if needed
121 if (cur_tab_ind === $tabs.length - 1) {
122 $next_tab = $($tabs[0]);
123 } else {
124 $next_tab = $($tabs[cur_tab_ind + 1]);
125 }
126
127 $next_tab.click();
128 return false;
129
130 case (ev.keyCode === 9): // tab
131 this.tabcomplete.active = true;
132 if (_.isEqual(this.tabcomplete.data, [])) {
133 // Get possible autocompletions
134 var ac_data = [],
135 members = _kiwi.app.panels().active.get('members');
136
137 // If we have a members list, get the models. Otherwise empty array
138 members = members ? members.models : [];
139
140 $.each(members, function (i, member) {
141 if (!member) return;
142 ac_data.push(member.get('nick'));
143 });
144
145 ac_data.push(_kiwi.app.panels().active.get('name'));
146
147 ac_data = _.sortBy(ac_data, function (nick) {
148 return nick;
149 });
150 this.tabcomplete.data = ac_data;
151 }
152
153 if (inp_val[inp[0].selectionStart - 1] === ' ') {
154 return false;
155 }
156
157 (function () {
158 var tokens, // Words before the cursor position
159 val, // New value being built up
160 p1, // Position in the value just before the nick
161 newnick, // New nick to be displayed (cycles through)
162 range, // TextRange for setting new text cursor position
163 nick, // Current nick in the value
164 trailing = ': '; // Text to be inserted after a tabbed nick
165
166 tokens = inp_val.substring(0, inp[0].selectionStart).split(' ');
167 if (tokens[tokens.length-1] == ':')
168 tokens.pop();
169
170 nick = tokens[tokens.length - 1];
171
172 if (this.tabcomplete.prefix === '') {
173 this.tabcomplete.prefix = nick;
174 }
175
176 this.tabcomplete.data = _.select(this.tabcomplete.data, function (n) {
177 return (n.toLowerCase().indexOf(that.tabcomplete.prefix.toLowerCase()) === 0);
178 });
179
180 if (this.tabcomplete.data.length > 0) {
181 // Get the current value before cursor position
182 p1 = inp[0].selectionStart - (nick.length);
183 val = inp_val.substr(0, p1);
184
185 // Include the current selected nick
186 newnick = this.tabcomplete.data.shift();
187 this.tabcomplete.data.push(newnick);
188 val += newnick;
189
190 if (inp_val.substr(inp[0].selectionStart, 2) !== trailing)
191 val += trailing;
192
193 // Now include the rest of the current value
194 val += inp_val.substr(inp[0].selectionStart);
195
196 inp.val(val);
197
198 // Move the cursor position to the end of the nick
199 if (inp[0].setSelectionRange) {
200 inp[0].setSelectionRange(p1 + newnick.length + trailing.length, p1 + newnick.length + trailing.length);
201 } else if (inp[0].createTextRange) { // not sure if this bit is actually needed....
202 range = inp[0].createTextRange();
203 range.collapse(true);
204 range.moveEnd('character', p1 + newnick.length + trailing.length);
205 range.moveStart('character', p1 + newnick.length + trailing.length);
206 range.select();
207 }
208 }
209 }).apply(this);
210 return false;
211 }
212 },
213
214
215 processInput: function (command_raw) {
216 var command, params,
217 pre_processed;
218
219 // The default command
220 if (command_raw[0] !== '/' || command_raw.substr(0, 2) === '//') {
221 // Remove any slash escaping at the start (ie. //)
222 command_raw = command_raw.replace(/^\/\//, '/');
223
224 // Prepend the default command
225 command_raw = '/msg ' + _kiwi.app.panels().active.get('name') + ' ' + command_raw;
226 }
227
228 // Process the raw command for any aliases
229 this.preprocessor.vars.server = _kiwi.app.connections.active_connection.get('name');
230 this.preprocessor.vars.channel = _kiwi.app.panels().active.get('name');
231 this.preprocessor.vars.destination = this.preprocessor.vars.channel;
232 command_raw = this.preprocessor.process(command_raw);
233
234 // Extract the command and parameters
235 params = command_raw.split(' ');
236 if (params[0][0] === '/') {
237 command = params[0].substr(1).toLowerCase();
238 params = params.splice(1, params.length - 1);
239 } else {
240 // Default command
241 command = 'msg';
242 params.unshift(_kiwi.app.panels().active.get('name'));
243 }
244
245 // Trigger the command events
246 this.trigger('command', {command: command, params: params});
247 this.trigger('command:' + command, {command: command, params: params});
248
249 // If we didn't have any listeners for this event, fire a special case
250 // TODO: This feels dirty. Should this really be done..?
251 if (!this._events['command:' + command]) {
252 this.trigger('unknown_command', {command: command, params: params});
253 }
254 },
255
256
257 addPluginIcon: function ($icon) {
258 var $tool = $('<div class="tool"></div>').append($icon);
259 this.$el.find('.input_tools').append($tool);
260 _kiwi.app.view.doLayout();
261 }
262});