##// END OF EJS Templates
DEV: Add various events.
Scott Sanderson -
Show More
@@ -1,569 +1,570
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3 /**
3 /**
4 *
4 *
5 *
5 *
6 * @module codecell
6 * @module codecell
7 * @namespace codecell
7 * @namespace codecell
8 * @class CodeCell
8 * @class CodeCell
9 */
9 */
10
10
11
11
12 define([
12 define([
13 'base/js/namespace',
13 'base/js/namespace',
14 'jquery',
14 'jquery',
15 'base/js/utils',
15 'base/js/utils',
16 'base/js/keyboard',
16 'base/js/keyboard',
17 'notebook/js/cell',
17 'notebook/js/cell',
18 'notebook/js/outputarea',
18 'notebook/js/outputarea',
19 'notebook/js/completer',
19 'notebook/js/completer',
20 'notebook/js/celltoolbar',
20 'notebook/js/celltoolbar',
21 'codemirror/lib/codemirror',
21 'codemirror/lib/codemirror',
22 'codemirror/mode/python/python',
22 'codemirror/mode/python/python',
23 'notebook/js/codemirror-ipython'
23 'notebook/js/codemirror-ipython'
24 ], function(IPython, $, utils, keyboard, cell, outputarea, completer, celltoolbar, CodeMirror, cmpython, cmip) {
24 ], function(IPython, $, utils, keyboard, cell, outputarea, completer, celltoolbar, CodeMirror, cmpython, cmip) {
25 "use strict";
25 "use strict";
26
26
27 var Cell = cell.Cell;
27 var Cell = cell.Cell;
28
28
29 /* local util for codemirror */
29 /* local util for codemirror */
30 var posEq = function(a, b) {return a.line == b.line && a.ch == b.ch;};
30 var posEq = function(a, b) {return a.line == b.line && a.ch == b.ch;};
31
31
32 /**
32 /**
33 *
33 *
34 * function to delete until previous non blanking space character
34 * function to delete until previous non blanking space character
35 * or first multiple of 4 tabstop.
35 * or first multiple of 4 tabstop.
36 * @private
36 * @private
37 */
37 */
38 CodeMirror.commands.delSpaceToPrevTabStop = function(cm){
38 CodeMirror.commands.delSpaceToPrevTabStop = function(cm){
39 var from = cm.getCursor(true), to = cm.getCursor(false), sel = !posEq(from, to);
39 var from = cm.getCursor(true), to = cm.getCursor(false), sel = !posEq(from, to);
40 if (!posEq(from, to)) { cm.replaceRange("", from, to); return; }
40 if (!posEq(from, to)) { cm.replaceRange("", from, to); return; }
41 var cur = cm.getCursor(), line = cm.getLine(cur.line);
41 var cur = cm.getCursor(), line = cm.getLine(cur.line);
42 var tabsize = cm.getOption('tabSize');
42 var tabsize = cm.getOption('tabSize');
43 var chToPrevTabStop = cur.ch-(Math.ceil(cur.ch/tabsize)-1)*tabsize;
43 var chToPrevTabStop = cur.ch-(Math.ceil(cur.ch/tabsize)-1)*tabsize;
44 from = {ch:cur.ch-chToPrevTabStop,line:cur.line};
44 from = {ch:cur.ch-chToPrevTabStop,line:cur.line};
45 var select = cm.getRange(from,cur);
45 var select = cm.getRange(from,cur);
46 if( select.match(/^\ +$/) !== null){
46 if( select.match(/^\ +$/) !== null){
47 cm.replaceRange("",from,cur);
47 cm.replaceRange("",from,cur);
48 } else {
48 } else {
49 cm.deleteH(-1,"char");
49 cm.deleteH(-1,"char");
50 }
50 }
51 };
51 };
52
52
53 var keycodes = keyboard.keycodes;
53 var keycodes = keyboard.keycodes;
54
54
55 var CodeCell = function (kernel, options) {
55 var CodeCell = function (kernel, options) {
56 // Constructor
56 // Constructor
57 //
57 //
58 // A Cell conceived to write code.
58 // A Cell conceived to write code.
59 //
59 //
60 // Parameters:
60 // Parameters:
61 // kernel: Kernel instance
61 // kernel: Kernel instance
62 // The kernel doesn't have to be set at creation time, in that case
62 // The kernel doesn't have to be set at creation time, in that case
63 // it will be null and set_kernel has to be called later.
63 // it will be null and set_kernel has to be called later.
64 // options: dictionary
64 // options: dictionary
65 // Dictionary of keyword arguments.
65 // Dictionary of keyword arguments.
66 // events: $(Events) instance
66 // events: $(Events) instance
67 // config: dictionary
67 // config: dictionary
68 // keyboard_manager: KeyboardManager instance
68 // keyboard_manager: KeyboardManager instance
69 // notebook: Notebook instance
69 // notebook: Notebook instance
70 // tooltip: Tooltip instance
70 // tooltip: Tooltip instance
71 this.kernel = kernel || null;
71 this.kernel = kernel || null;
72 this.notebook = options.notebook;
72 this.notebook = options.notebook;
73 this.collapsed = false;
73 this.collapsed = false;
74 this.events = options.events;
74 this.events = options.events;
75 this.tooltip = options.tooltip;
75 this.tooltip = options.tooltip;
76 this.config = options.config;
76 this.config = options.config;
77
77
78 // create all attributed in constructor function
78 // create all attributed in constructor function
79 // even if null for V8 VM optimisation
79 // even if null for V8 VM optimisation
80 this.input_prompt_number = null;
80 this.input_prompt_number = null;
81 this.celltoolbar = null;
81 this.celltoolbar = null;
82 this.output_area = null;
82 this.output_area = null;
83 // Keep a stack of the 'active' output areas (where active means the
83 // Keep a stack of the 'active' output areas (where active means the
84 // output area that recieves output). When a user activates an output
84 // output area that recieves output). When a user activates an output
85 // area, it gets pushed to the stack. Then, when the output area is
85 // area, it gets pushed to the stack. Then, when the output area is
86 // deactivated, it's popped from the stack. When the stack is empty,
86 // deactivated, it's popped from the stack. When the stack is empty,
87 // the cell's output area is used.
87 // the cell's output area is used.
88 this.active_output_areas = [];
88 this.active_output_areas = [];
89 var that = this;
89 var that = this;
90 Object.defineProperty(this, 'active_output_area', {
90 Object.defineProperty(this, 'active_output_area', {
91 get: function() {
91 get: function() {
92 if (that.active_output_areas && that.active_output_areas.length > 0) {
92 if (that.active_output_areas && that.active_output_areas.length > 0) {
93 return that.active_output_areas[that.active_output_areas.length-1];
93 return that.active_output_areas[that.active_output_areas.length-1];
94 } else {
94 } else {
95 return that.output_area;
95 return that.output_area;
96 }
96 }
97 },
97 },
98 });
98 });
99
99
100 this.last_msg_id = null;
100 this.last_msg_id = null;
101 this.completer = null;
101 this.completer = null;
102
102
103
103
104 var config = utils.mergeopt(CodeCell, this.config);
104 var config = utils.mergeopt(CodeCell, this.config);
105 Cell.apply(this,[{
105 Cell.apply(this,[{
106 config: config,
106 config: config,
107 keyboard_manager: options.keyboard_manager,
107 keyboard_manager: options.keyboard_manager,
108 events: this.events}]);
108 events: this.events}]);
109
109
110 // Attributes we want to override in this subclass.
110 // Attributes we want to override in this subclass.
111 this.cell_type = "code";
111 this.cell_type = "code";
112 this.element.focusout(
112 this.element.focusout(
113 function() { that.auto_highlight(); }
113 function() { that.auto_highlight(); }
114 );
114 );
115 };
115 };
116
116
117 CodeCell.options_default = {
117 CodeCell.options_default = {
118 cm_config : {
118 cm_config : {
119 extraKeys: {
119 extraKeys: {
120 "Tab" : "indentMore",
120 "Tab" : "indentMore",
121 "Shift-Tab" : "indentLess",
121 "Shift-Tab" : "indentLess",
122 "Backspace" : "delSpaceToPrevTabStop",
122 "Backspace" : "delSpaceToPrevTabStop",
123 "Cmd-/" : "toggleComment",
123 "Cmd-/" : "toggleComment",
124 "Ctrl-/" : "toggleComment"
124 "Ctrl-/" : "toggleComment"
125 },
125 },
126 mode: 'ipython',
126 mode: 'ipython',
127 theme: 'ipython',
127 theme: 'ipython',
128 matchBrackets: true
128 matchBrackets: true
129 }
129 }
130 };
130 };
131
131
132 CodeCell.msg_cells = {};
132 CodeCell.msg_cells = {};
133
133
134 CodeCell.prototype = Object.create(Cell.prototype);
134 CodeCell.prototype = Object.create(Cell.prototype);
135
135
136 /**
136 /**
137 * @method push_output_area
137 * @method push_output_area
138 */
138 */
139 CodeCell.prototype.push_output_area = function (output_area) {
139 CodeCell.prototype.push_output_area = function (output_area) {
140 this.active_output_areas.push(output_area);
140 this.active_output_areas.push(output_area);
141 };
141 };
142
142
143 /**
143 /**
144 * @method pop_output_area
144 * @method pop_output_area
145 */
145 */
146 CodeCell.prototype.pop_output_area = function (output_area) {
146 CodeCell.prototype.pop_output_area = function (output_area) {
147 var index = this.active_output_areas.lastIndexOf(output_area);
147 var index = this.active_output_areas.lastIndexOf(output_area);
148 if (index > -1) {
148 if (index > -1) {
149 this.active_output_areas.splice(index, 1);
149 this.active_output_areas.splice(index, 1);
150 }
150 }
151 };
151 };
152
152
153 /**
153 /**
154 * @method auto_highlight
154 * @method auto_highlight
155 */
155 */
156 CodeCell.prototype.auto_highlight = function () {
156 CodeCell.prototype.auto_highlight = function () {
157 this._auto_highlight(this.config.cell_magic_highlight);
157 this._auto_highlight(this.config.cell_magic_highlight);
158 };
158 };
159
159
160 /** @method create_element */
160 /** @method create_element */
161 CodeCell.prototype.create_element = function () {
161 CodeCell.prototype.create_element = function () {
162 Cell.prototype.create_element.apply(this, arguments);
162 Cell.prototype.create_element.apply(this, arguments);
163
163
164 var cell = $('<div></div>').addClass('cell code_cell');
164 var cell = $('<div></div>').addClass('cell code_cell');
165 cell.attr('tabindex','2');
165 cell.attr('tabindex','2');
166
166
167 var input = $('<div></div>').addClass('input');
167 var input = $('<div></div>').addClass('input');
168 var prompt = $('<div/>').addClass('prompt input_prompt');
168 var prompt = $('<div/>').addClass('prompt input_prompt');
169 var inner_cell = $('<div/>').addClass('inner_cell');
169 var inner_cell = $('<div/>').addClass('inner_cell');
170 this.celltoolbar = new celltoolbar.CellToolbar({
170 this.celltoolbar = new celltoolbar.CellToolbar({
171 cell: this,
171 cell: this,
172 notebook: this.notebook});
172 notebook: this.notebook});
173 inner_cell.append(this.celltoolbar.element);
173 inner_cell.append(this.celltoolbar.element);
174 var input_area = $('<div/>').addClass('input_area');
174 var input_area = $('<div/>').addClass('input_area');
175 this.code_mirror = new CodeMirror(input_area.get(0), this.cm_config);
175 this.code_mirror = new CodeMirror(input_area.get(0), this.cm_config);
176 this.code_mirror.on('keydown', $.proxy(this.handle_keyevent,this))
176 this.code_mirror.on('keydown', $.proxy(this.handle_keyevent,this))
177 $(this.code_mirror.getInputField()).attr("spellcheck", "false");
177 $(this.code_mirror.getInputField()).attr("spellcheck", "false");
178 inner_cell.append(input_area);
178 inner_cell.append(input_area);
179 input.append(prompt).append(inner_cell);
179 input.append(prompt).append(inner_cell);
180
180
181 var widget_area = $('<div/>')
181 var widget_area = $('<div/>')
182 .addClass('widget-area')
182 .addClass('widget-area')
183 .hide();
183 .hide();
184 this.widget_area = widget_area;
184 this.widget_area = widget_area;
185 var widget_prompt = $('<div/>')
185 var widget_prompt = $('<div/>')
186 .addClass('prompt')
186 .addClass('prompt')
187 .appendTo(widget_area);
187 .appendTo(widget_area);
188 var widget_subarea = $('<div/>')
188 var widget_subarea = $('<div/>')
189 .addClass('widget-subarea')
189 .addClass('widget-subarea')
190 .appendTo(widget_area);
190 .appendTo(widget_area);
191 this.widget_subarea = widget_subarea;
191 this.widget_subarea = widget_subarea;
192 var widget_clear_buton = $('<button />')
192 var widget_clear_buton = $('<button />')
193 .addClass('close')
193 .addClass('close')
194 .html('&times;')
194 .html('&times;')
195 .click(function() {
195 .click(function() {
196 widget_area.slideUp('', function(){ widget_subarea.html(''); });
196 widget_area.slideUp('', function(){ widget_subarea.html(''); });
197 })
197 })
198 .appendTo(widget_prompt);
198 .appendTo(widget_prompt);
199
199
200 var output = $('<div></div>');
200 var output = $('<div></div>');
201 cell.append(input).append(widget_area).append(output);
201 cell.append(input).append(widget_area).append(output);
202 this.element = cell;
202 this.element = cell;
203 this.output_area = new outputarea.OutputArea({
203 this.output_area = new outputarea.OutputArea({
204 selector: output,
204 selector: output,
205 prompt_area: true,
205 prompt_area: true,
206 events: this.events,
206 events: this.events,
207 keyboard_manager: this.keyboard_manager});
207 keyboard_manager: this.keyboard_manager});
208 this.completer = new completer.Completer(this, this.events);
208 this.completer = new completer.Completer(this, this.events);
209 };
209 };
210
210
211 /** @method bind_events */
211 /** @method bind_events */
212 CodeCell.prototype.bind_events = function () {
212 CodeCell.prototype.bind_events = function () {
213 Cell.prototype.bind_events.apply(this);
213 Cell.prototype.bind_events.apply(this);
214 var that = this;
214 var that = this;
215
215
216 this.element.focusout(
216 this.element.focusout(
217 function() { that.auto_highlight(); }
217 function() { that.auto_highlight(); }
218 );
218 );
219 };
219 };
220
220
221
221
222 /**
222 /**
223 * This method gets called in CodeMirror's onKeyDown/onKeyPress
223 * This method gets called in CodeMirror's onKeyDown/onKeyPress
224 * handlers and is used to provide custom key handling. Its return
224 * handlers and is used to provide custom key handling. Its return
225 * value is used to determine if CodeMirror should ignore the event:
225 * value is used to determine if CodeMirror should ignore the event:
226 * true = ignore, false = don't ignore.
226 * true = ignore, false = don't ignore.
227 * @method handle_codemirror_keyevent
227 * @method handle_codemirror_keyevent
228 */
228 */
229 CodeCell.prototype.handle_codemirror_keyevent = function (editor, event) {
229 CodeCell.prototype.handle_codemirror_keyevent = function (editor, event) {
230
230
231 var that = this;
231 var that = this;
232 // whatever key is pressed, first, cancel the tooltip request before
232 // whatever key is pressed, first, cancel the tooltip request before
233 // they are sent, and remove tooltip if any, except for tab again
233 // they are sent, and remove tooltip if any, except for tab again
234 var tooltip_closed = null;
234 var tooltip_closed = null;
235 if (event.type === 'keydown' && event.which != keycodes.tab ) {
235 if (event.type === 'keydown' && event.which != keycodes.tab ) {
236 tooltip_closed = this.tooltip.remove_and_cancel_tooltip();
236 tooltip_closed = this.tooltip.remove_and_cancel_tooltip();
237 }
237 }
238
238
239 var cur = editor.getCursor();
239 var cur = editor.getCursor();
240 if (event.keyCode === keycodes.enter){
240 if (event.keyCode === keycodes.enter){
241 this.auto_highlight();
241 this.auto_highlight();
242 }
242 }
243
243
244 if (event.which === keycodes.down && event.type === 'keypress' && this.tooltip.time_before_tooltip >= 0) {
244 if (event.which === keycodes.down && event.type === 'keypress' && this.tooltip.time_before_tooltip >= 0) {
245 // triger on keypress (!) otherwise inconsistent event.which depending on plateform
245 // triger on keypress (!) otherwise inconsistent event.which depending on plateform
246 // browser and keyboard layout !
246 // browser and keyboard layout !
247 // Pressing '(' , request tooltip, don't forget to reappend it
247 // Pressing '(' , request tooltip, don't forget to reappend it
248 // The second argument says to hide the tooltip if the docstring
248 // The second argument says to hide the tooltip if the docstring
249 // is actually empty
249 // is actually empty
250 this.tooltip.pending(that, true);
250 this.tooltip.pending(that, true);
251 } else if ( tooltip_closed && event.which === keycodes.esc && event.type === 'keydown') {
251 } else if ( tooltip_closed && event.which === keycodes.esc && event.type === 'keydown') {
252 // If tooltip is active, cancel it. The call to
252 // If tooltip is active, cancel it. The call to
253 // remove_and_cancel_tooltip above doesn't pass, force=true.
253 // remove_and_cancel_tooltip above doesn't pass, force=true.
254 // Because of this it won't actually close the tooltip
254 // Because of this it won't actually close the tooltip
255 // if it is in sticky mode. Thus, we have to check again if it is open
255 // if it is in sticky mode. Thus, we have to check again if it is open
256 // and close it with force=true.
256 // and close it with force=true.
257 if (!this.tooltip._hidden) {
257 if (!this.tooltip._hidden) {
258 this.tooltip.remove_and_cancel_tooltip(true);
258 this.tooltip.remove_and_cancel_tooltip(true);
259 }
259 }
260 // If we closed the tooltip, don't let CM or the global handlers
260 // If we closed the tooltip, don't let CM or the global handlers
261 // handle this event.
261 // handle this event.
262 event.codemirrorIgnore = true;
262 event.codemirrorIgnore = true;
263 event.preventDefault();
263 event.preventDefault();
264 return true;
264 return true;
265 } else if (event.keyCode === keycodes.tab && event.type === 'keydown' && event.shiftKey) {
265 } else if (event.keyCode === keycodes.tab && event.type === 'keydown' && event.shiftKey) {
266 if (editor.somethingSelected() || editor.getSelections().length !== 1){
266 if (editor.somethingSelected() || editor.getSelections().length !== 1){
267 var anchor = editor.getCursor("anchor");
267 var anchor = editor.getCursor("anchor");
268 var head = editor.getCursor("head");
268 var head = editor.getCursor("head");
269 if( anchor.line != head.line){
269 if( anchor.line != head.line){
270 return false;
270 return false;
271 }
271 }
272 }
272 }
273 this.tooltip.request(that);
273 this.tooltip.request(that);
274 event.codemirrorIgnore = true;
274 event.codemirrorIgnore = true;
275 event.preventDefault();
275 event.preventDefault();
276 return true;
276 return true;
277 } else if (event.keyCode === keycodes.tab && event.type == 'keydown') {
277 } else if (event.keyCode === keycodes.tab && event.type == 'keydown') {
278 // Tab completion.
278 // Tab completion.
279 this.tooltip.remove_and_cancel_tooltip();
279 this.tooltip.remove_and_cancel_tooltip();
280
280
281 // completion does not work on multicursor, it might be possible though in some cases
281 // completion does not work on multicursor, it might be possible though in some cases
282 if (editor.somethingSelected() || editor.getSelections().length > 1) {
282 if (editor.somethingSelected() || editor.getSelections().length > 1) {
283 return false;
283 return false;
284 }
284 }
285 var pre_cursor = editor.getRange({line:cur.line,ch:0},cur);
285 var pre_cursor = editor.getRange({line:cur.line,ch:0},cur);
286 if (pre_cursor.trim() === "") {
286 if (pre_cursor.trim() === "") {
287 // Don't autocomplete if the part of the line before the cursor
287 // Don't autocomplete if the part of the line before the cursor
288 // is empty. In this case, let CodeMirror handle indentation.
288 // is empty. In this case, let CodeMirror handle indentation.
289 return false;
289 return false;
290 } else {
290 } else {
291 event.codemirrorIgnore = true;
291 event.codemirrorIgnore = true;
292 event.preventDefault();
292 event.preventDefault();
293 this.completer.startCompletion();
293 this.completer.startCompletion();
294 return true;
294 return true;
295 }
295 }
296 }
296 }
297
297
298 // keyboard event wasn't one of those unique to code cells, let's see
298 // keyboard event wasn't one of those unique to code cells, let's see
299 // if it's one of the generic ones (i.e. check edit mode shortcuts)
299 // if it's one of the generic ones (i.e. check edit mode shortcuts)
300 return Cell.prototype.handle_codemirror_keyevent.apply(this, [editor, event]);
300 return Cell.prototype.handle_codemirror_keyevent.apply(this, [editor, event]);
301 };
301 };
302
302
303 // Kernel related calls.
303 // Kernel related calls.
304
304
305 CodeCell.prototype.set_kernel = function (kernel) {
305 CodeCell.prototype.set_kernel = function (kernel) {
306 this.kernel = kernel;
306 this.kernel = kernel;
307 };
307 };
308
308
309 /**
309 /**
310 * Execute current code cell to the kernel
310 * Execute current code cell to the kernel
311 * @method execute
311 * @method execute
312 */
312 */
313 CodeCell.prototype.execute = function () {
313 CodeCell.prototype.execute = function () {
314 if (!this.kernel || !this.kernel.is_connected()) {
314 if (!this.kernel || !this.kernel.is_connected()) {
315 console.log("Can't execute, kernel is not connected.");
315 console.log("Can't execute, kernel is not connected.");
316 return;
316 return;
317 }
317 }
318
318
319 this.active_output_area.clear_output();
319 this.active_output_area.clear_output();
320
320
321 // Clear widget area
321 // Clear widget area
322 this.widget_subarea.html('');
322 this.widget_subarea.html('');
323 this.widget_subarea.height('');
323 this.widget_subarea.height('');
324 this.widget_area.height('');
324 this.widget_area.height('');
325 this.widget_area.hide();
325 this.widget_area.hide();
326
326
327 this.set_input_prompt('*');
327 this.set_input_prompt('*');
328 this.element.addClass("running");
328 this.element.addClass("running");
329 if (this.last_msg_id) {
329 if (this.last_msg_id) {
330 this.kernel.clear_callbacks_for_msg(this.last_msg_id);
330 this.kernel.clear_callbacks_for_msg(this.last_msg_id);
331 }
331 }
332 var callbacks = this.get_callbacks();
332 var callbacks = this.get_callbacks();
333
333
334 var old_msg_id = this.last_msg_id;
334 var old_msg_id = this.last_msg_id;
335 this.last_msg_id = this.kernel.execute(this.get_text(), callbacks, {silent: false, store_history: true});
335 this.last_msg_id = this.kernel.execute(this.get_text(), callbacks, {silent: false, store_history: true});
336 if (old_msg_id) {
336 if (old_msg_id) {
337 delete CodeCell.msg_cells[old_msg_id];
337 delete CodeCell.msg_cells[old_msg_id];
338 }
338 }
339 CodeCell.msg_cells[this.last_msg_id] = this;
339 CodeCell.msg_cells[this.last_msg_id] = this;
340 this.render();
340 this.render();
341 this.events.trigger('execute.CodeCell');
341 };
342 };
342
343
343 /**
344 /**
344 * Construct the default callbacks for
345 * Construct the default callbacks for
345 * @method get_callbacks
346 * @method get_callbacks
346 */
347 */
347 CodeCell.prototype.get_callbacks = function () {
348 CodeCell.prototype.get_callbacks = function () {
348 var that = this;
349 var that = this;
349 return {
350 return {
350 shell : {
351 shell : {
351 reply : $.proxy(this._handle_execute_reply, this),
352 reply : $.proxy(this._handle_execute_reply, this),
352 payload : {
353 payload : {
353 set_next_input : $.proxy(this._handle_set_next_input, this),
354 set_next_input : $.proxy(this._handle_set_next_input, this),
354 page : $.proxy(this._open_with_pager, this)
355 page : $.proxy(this._open_with_pager, this)
355 }
356 }
356 },
357 },
357 iopub : {
358 iopub : {
358 output : function() {
359 output : function() {
359 that.active_output_area.handle_output.apply(that.active_output_area, arguments);
360 that.active_output_area.handle_output.apply(that.active_output_area, arguments);
360 },
361 },
361 clear_output : function() {
362 clear_output : function() {
362 that.active_output_area.handle_clear_output.apply(that.active_output_area, arguments);
363 that.active_output_area.handle_clear_output.apply(that.active_output_area, arguments);
363 },
364 },
364 },
365 },
365 input : $.proxy(this._handle_input_request, this)
366 input : $.proxy(this._handle_input_request, this)
366 };
367 };
367 };
368 };
368
369
369 CodeCell.prototype._open_with_pager = function (payload) {
370 CodeCell.prototype._open_with_pager = function (payload) {
370 this.events.trigger('open_with_text.Pager', payload);
371 this.events.trigger('open_with_text.Pager', payload);
371 };
372 };
372
373
373 /**
374 /**
374 * @method _handle_execute_reply
375 * @method _handle_execute_reply
375 * @private
376 * @private
376 */
377 */
377 CodeCell.prototype._handle_execute_reply = function (msg) {
378 CodeCell.prototype._handle_execute_reply = function (msg) {
378 this.set_input_prompt(msg.content.execution_count);
379 this.set_input_prompt(msg.content.execution_count);
379 this.element.removeClass("running");
380 this.element.removeClass("running");
380 this.events.trigger('set_dirty.Notebook', {value: true});
381 this.events.trigger('set_dirty.Notebook', {value: true});
381 };
382 };
382
383
383 /**
384 /**
384 * @method _handle_set_next_input
385 * @method _handle_set_next_input
385 * @private
386 * @private
386 */
387 */
387 CodeCell.prototype._handle_set_next_input = function (payload) {
388 CodeCell.prototype._handle_set_next_input = function (payload) {
388 var data = {'cell': this, 'text': payload.text};
389 var data = {'cell': this, 'text': payload.text};
389 this.events.trigger('set_next_input.Notebook', data);
390 this.events.trigger('set_next_input.Notebook', data);
390 };
391 };
391
392
392 /**
393 /**
393 * @method _handle_input_request
394 * @method _handle_input_request
394 * @private
395 * @private
395 */
396 */
396 CodeCell.prototype._handle_input_request = function (msg) {
397 CodeCell.prototype._handle_input_request = function (msg) {
397 this.active_output_area.append_raw_input(msg);
398 this.active_output_area.append_raw_input(msg);
398 };
399 };
399
400
400
401
401 // Basic cell manipulation.
402 // Basic cell manipulation.
402
403
403 CodeCell.prototype.select = function () {
404 CodeCell.prototype.select = function () {
404 var cont = Cell.prototype.select.apply(this);
405 var cont = Cell.prototype.select.apply(this);
405 if (cont) {
406 if (cont) {
406 this.code_mirror.refresh();
407 this.code_mirror.refresh();
407 this.auto_highlight();
408 this.auto_highlight();
408 }
409 }
409 return cont;
410 return cont;
410 };
411 };
411
412
412 CodeCell.prototype.render = function () {
413 CodeCell.prototype.render = function () {
413 var cont = Cell.prototype.render.apply(this);
414 var cont = Cell.prototype.render.apply(this);
414 // Always execute, even if we are already in the rendered state
415 // Always execute, even if we are already in the rendered state
415 return cont;
416 return cont;
416 };
417 };
417
418
418 CodeCell.prototype.select_all = function () {
419 CodeCell.prototype.select_all = function () {
419 var start = {line: 0, ch: 0};
420 var start = {line: 0, ch: 0};
420 var nlines = this.code_mirror.lineCount();
421 var nlines = this.code_mirror.lineCount();
421 var last_line = this.code_mirror.getLine(nlines-1);
422 var last_line = this.code_mirror.getLine(nlines-1);
422 var end = {line: nlines-1, ch: last_line.length};
423 var end = {line: nlines-1, ch: last_line.length};
423 this.code_mirror.setSelection(start, end);
424 this.code_mirror.setSelection(start, end);
424 };
425 };
425
426
426
427
427 CodeCell.prototype.collapse_output = function () {
428 CodeCell.prototype.collapse_output = function () {
428 this.output_area.collapse();
429 this.output_area.collapse();
429 };
430 };
430
431
431
432
432 CodeCell.prototype.expand_output = function () {
433 CodeCell.prototype.expand_output = function () {
433 this.output_area.expand();
434 this.output_area.expand();
434 this.output_area.unscroll_area();
435 this.output_area.unscroll_area();
435 };
436 };
436
437
437 CodeCell.prototype.scroll_output = function () {
438 CodeCell.prototype.scroll_output = function () {
438 this.output_area.expand();
439 this.output_area.expand();
439 this.output_area.scroll_if_long();
440 this.output_area.scroll_if_long();
440 };
441 };
441
442
442 CodeCell.prototype.toggle_output = function () {
443 CodeCell.prototype.toggle_output = function () {
443 this.output_area.toggle_output();
444 this.output_area.toggle_output();
444 };
445 };
445
446
446 CodeCell.prototype.toggle_output_scroll = function () {
447 CodeCell.prototype.toggle_output_scroll = function () {
447 this.output_area.toggle_scroll();
448 this.output_area.toggle_scroll();
448 };
449 };
449
450
450
451
451 CodeCell.input_prompt_classical = function (prompt_value, lines_number) {
452 CodeCell.input_prompt_classical = function (prompt_value, lines_number) {
452 var ns;
453 var ns;
453 if (prompt_value === undefined || prompt_value === null) {
454 if (prompt_value === undefined || prompt_value === null) {
454 ns = "&nbsp;";
455 ns = "&nbsp;";
455 } else {
456 } else {
456 ns = encodeURIComponent(prompt_value);
457 ns = encodeURIComponent(prompt_value);
457 }
458 }
458 return 'In&nbsp;[' + ns + ']:';
459 return 'In&nbsp;[' + ns + ']:';
459 };
460 };
460
461
461 CodeCell.input_prompt_continuation = function (prompt_value, lines_number) {
462 CodeCell.input_prompt_continuation = function (prompt_value, lines_number) {
462 var html = [CodeCell.input_prompt_classical(prompt_value, lines_number)];
463 var html = [CodeCell.input_prompt_classical(prompt_value, lines_number)];
463 for(var i=1; i < lines_number; i++) {
464 for(var i=1; i < lines_number; i++) {
464 html.push(['...:']);
465 html.push(['...:']);
465 }
466 }
466 return html.join('<br/>');
467 return html.join('<br/>');
467 };
468 };
468
469
469 CodeCell.input_prompt_function = CodeCell.input_prompt_classical;
470 CodeCell.input_prompt_function = CodeCell.input_prompt_classical;
470
471
471
472
472 CodeCell.prototype.set_input_prompt = function (number) {
473 CodeCell.prototype.set_input_prompt = function (number) {
473 var nline = 1;
474 var nline = 1;
474 if (this.code_mirror !== undefined) {
475 if (this.code_mirror !== undefined) {
475 nline = this.code_mirror.lineCount();
476 nline = this.code_mirror.lineCount();
476 }
477 }
477 this.input_prompt_number = number;
478 this.input_prompt_number = number;
478 var prompt_html = CodeCell.input_prompt_function(this.input_prompt_number, nline);
479 var prompt_html = CodeCell.input_prompt_function(this.input_prompt_number, nline);
479 // This HTML call is okay because the user contents are escaped.
480 // This HTML call is okay because the user contents are escaped.
480 this.element.find('div.input_prompt').html(prompt_html);
481 this.element.find('div.input_prompt').html(prompt_html);
481 };
482 };
482
483
483
484
484 CodeCell.prototype.clear_input = function () {
485 CodeCell.prototype.clear_input = function () {
485 this.code_mirror.setValue('');
486 this.code_mirror.setValue('');
486 };
487 };
487
488
488
489
489 CodeCell.prototype.get_text = function () {
490 CodeCell.prototype.get_text = function () {
490 return this.code_mirror.getValue();
491 return this.code_mirror.getValue();
491 };
492 };
492
493
493
494
494 CodeCell.prototype.set_text = function (code) {
495 CodeCell.prototype.set_text = function (code) {
495 return this.code_mirror.setValue(code);
496 return this.code_mirror.setValue(code);
496 };
497 };
497
498
498
499
499 CodeCell.prototype.clear_output = function (wait) {
500 CodeCell.prototype.clear_output = function (wait) {
500 this.active_output_area.clear_output(wait);
501 this.active_output_area.clear_output(wait);
501 this.set_input_prompt();
502 this.set_input_prompt();
502 };
503 };
503
504
504
505
505 // JSON serialization
506 // JSON serialization
506
507
507 CodeCell.prototype.fromJSON = function (data) {
508 CodeCell.prototype.fromJSON = function (data) {
508 Cell.prototype.fromJSON.apply(this, arguments);
509 Cell.prototype.fromJSON.apply(this, arguments);
509 if (data.cell_type === 'code') {
510 if (data.cell_type === 'code') {
510 if (data.source !== undefined) {
511 if (data.source !== undefined) {
511 this.set_text(data.source);
512 this.set_text(data.source);
512 // make this value the starting point, so that we can only undo
513 // make this value the starting point, so that we can only undo
513 // to this state, instead of a blank cell
514 // to this state, instead of a blank cell
514 this.code_mirror.clearHistory();
515 this.code_mirror.clearHistory();
515 this.auto_highlight();
516 this.auto_highlight();
516 }
517 }
517 this.set_input_prompt(data.execution_count);
518 this.set_input_prompt(data.execution_count);
518 this.output_area.trusted = data.metadata.trusted || false;
519 this.output_area.trusted = data.metadata.trusted || false;
519 this.output_area.fromJSON(data.outputs);
520 this.output_area.fromJSON(data.outputs);
520 if (data.metadata.collapsed !== undefined) {
521 if (data.metadata.collapsed !== undefined) {
521 if (data.metadata.collapsed) {
522 if (data.metadata.collapsed) {
522 this.collapse_output();
523 this.collapse_output();
523 } else {
524 } else {
524 this.expand_output();
525 this.expand_output();
525 }
526 }
526 }
527 }
527 }
528 }
528 };
529 };
529
530
530
531
531 CodeCell.prototype.toJSON = function () {
532 CodeCell.prototype.toJSON = function () {
532 var data = Cell.prototype.toJSON.apply(this);
533 var data = Cell.prototype.toJSON.apply(this);
533 data.source = this.get_text();
534 data.source = this.get_text();
534 // is finite protect against undefined and '*' value
535 // is finite protect against undefined and '*' value
535 if (isFinite(this.input_prompt_number)) {
536 if (isFinite(this.input_prompt_number)) {
536 data.execution_count = this.input_prompt_number;
537 data.execution_count = this.input_prompt_number;
537 } else {
538 } else {
538 data.execution_count = null;
539 data.execution_count = null;
539 }
540 }
540 var outputs = this.output_area.toJSON();
541 var outputs = this.output_area.toJSON();
541 data.outputs = outputs;
542 data.outputs = outputs;
542 data.metadata.trusted = this.output_area.trusted;
543 data.metadata.trusted = this.output_area.trusted;
543 data.metadata.collapsed = this.output_area.collapsed;
544 data.metadata.collapsed = this.output_area.collapsed;
544 return data;
545 return data;
545 };
546 };
546
547
547 /**
548 /**
548 * handle cell level logic when a cell is unselected
549 * handle cell level logic when a cell is unselected
549 * @method unselect
550 * @method unselect
550 * @return is the action being taken
551 * @return is the action being taken
551 */
552 */
552 CodeCell.prototype.unselect = function () {
553 CodeCell.prototype.unselect = function () {
553 var cont = Cell.prototype.unselect.apply(this);
554 var cont = Cell.prototype.unselect.apply(this);
554 if (cont) {
555 if (cont) {
555 // When a code cell is usnelected, make sure that the corresponding
556 // When a code cell is usnelected, make sure that the corresponding
556 // tooltip and completer to that cell is closed.
557 // tooltip and completer to that cell is closed.
557 this.tooltip.remove_and_cancel_tooltip(true);
558 this.tooltip.remove_and_cancel_tooltip(true);
558 if (this.completer !== null) {
559 if (this.completer !== null) {
559 this.completer.close();
560 this.completer.close();
560 }
561 }
561 }
562 }
562 return cont;
563 return cont;
563 };
564 };
564
565
565 // Backwards compatability.
566 // Backwards compatability.
566 IPython.CodeCell = CodeCell;
567 IPython.CodeCell = CodeCell;
567
568
568 return {'CodeCell': CodeCell};
569 return {'CodeCell': CodeCell};
569 });
570 });
@@ -1,498 +1,502
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 define([
4 define([
5 'base/js/namespace',
5 'base/js/namespace',
6 'jquery',
6 'jquery',
7 'base/js/utils',
7 'base/js/utils',
8 'base/js/dialog',
8 'base/js/dialog',
9 ], function(IPython, $, utils, dialog) {
9 'base/js/events',
10 ], function(IPython, $, utils, dialog, events) {
10 "use strict";
11 "use strict";
11
12
12 var NotebookList = function (selector, options) {
13 var NotebookList = function (selector, options) {
13 // Constructor
14 // Constructor
14 //
15 //
15 // Parameters:
16 // Parameters:
16 // selector: string
17 // selector: string
17 // options: dictionary
18 // options: dictionary
18 // Dictionary of keyword arguments.
19 // Dictionary of keyword arguments.
19 // session_list: SessionList instance
20 // session_list: SessionList instance
20 // element_name: string
21 // element_name: string
21 // base_url: string
22 // base_url: string
22 // notebook_path: string
23 // notebook_path: string
23 // contents: Contents instance
24 // contents: Contents instance
24 var that = this;
25 var that = this;
25 this.session_list = options.session_list;
26 this.session_list = options.session_list;
26 // allow code re-use by just changing element_name in kernellist.js
27 // allow code re-use by just changing element_name in kernellist.js
27 this.element_name = options.element_name || 'notebook';
28 this.element_name = options.element_name || 'notebook';
28 this.selector = selector;
29 this.selector = selector;
29 if (this.selector !== undefined) {
30 if (this.selector !== undefined) {
30 this.element = $(selector);
31 this.element = $(selector);
31 this.style();
32 this.style();
32 this.bind_events();
33 this.bind_events();
33 }
34 }
34 this.notebooks_list = [];
35 this.notebooks_list = [];
35 this.sessions = {};
36 this.sessions = {};
36 this.base_url = options.base_url || utils.get_body_data("baseUrl");
37 this.base_url = options.base_url || utils.get_body_data("baseUrl");
37 this.notebook_path = options.notebook_path || utils.get_body_data("notebookPath");
38 this.notebook_path = options.notebook_path || utils.get_body_data("notebookPath");
38 this.contents = options.contents;
39 this.contents = options.contents;
39 if (this.session_list && this.session_list.events) {
40 if (this.session_list && this.session_list.events) {
40 this.session_list.events.on('sessions_loaded.Dashboard',
41 this.session_list.events.on('sessions_loaded.Dashboard',
41 function(e, d) { that.sessions_loaded(d); });
42 function(e, d) { that.sessions_loaded(d); });
42 }
43 }
43 };
44 };
44
45
45 NotebookList.prototype.style = function () {
46 NotebookList.prototype.style = function () {
46 var prefix = '#' + this.element_name;
47 var prefix = '#' + this.element_name;
47 $(prefix + '_toolbar').addClass('list_toolbar');
48 $(prefix + '_toolbar').addClass('list_toolbar');
48 $(prefix + '_list_info').addClass('toolbar_info');
49 $(prefix + '_list_info').addClass('toolbar_info');
49 $(prefix + '_buttons').addClass('toolbar_buttons');
50 $(prefix + '_buttons').addClass('toolbar_buttons');
50 $(prefix + '_list_header').addClass('list_header');
51 $(prefix + '_list_header').addClass('list_header');
51 this.element.addClass("list_container");
52 this.element.addClass("list_container");
52 };
53 };
53
54
54
55
55 NotebookList.prototype.bind_events = function () {
56 NotebookList.prototype.bind_events = function () {
56 var that = this;
57 var that = this;
57 $('#refresh_' + this.element_name + '_list').click(function () {
58 $('#refresh_' + this.element_name + '_list').click(function () {
58 that.load_sessions();
59 that.load_sessions();
59 });
60 });
60 this.element.bind('dragover', function () {
61 this.element.bind('dragover', function () {
61 return false;
62 return false;
62 });
63 });
63 this.element.bind('drop', function(event){
64 this.element.bind('drop', function(event){
64 that.handleFilesUpload(event,'drop');
65 that.handleFilesUpload(event,'drop');
65 return false;
66 return false;
66 });
67 });
67 };
68 };
68
69
69 NotebookList.prototype.handleFilesUpload = function(event, dropOrForm) {
70 NotebookList.prototype.handleFilesUpload = function(event, dropOrForm) {
70 var that = this;
71 var that = this;
71 var files;
72 var files;
72 if(dropOrForm =='drop'){
73 if(dropOrForm =='drop'){
73 files = event.originalEvent.dataTransfer.files;
74 files = event.originalEvent.dataTransfer.files;
74 } else
75 } else
75 {
76 {
76 files = event.originalEvent.target.files;
77 files = event.originalEvent.target.files;
77 }
78 }
78 for (var i = 0; i < files.length; i++) {
79 for (var i = 0; i < files.length; i++) {
79 var f = files[i];
80 var f = files[i];
80 var name_and_ext = utils.splitext(f.name);
81 var name_and_ext = utils.splitext(f.name);
81 var file_ext = name_and_ext[1];
82 var file_ext = name_and_ext[1];
82
83
83 var reader = new FileReader();
84 var reader = new FileReader();
84 if (file_ext === '.ipynb') {
85 if (file_ext === '.ipynb') {
85 reader.readAsText(f);
86 reader.readAsText(f);
86 } else {
87 } else {
87 // read non-notebook files as binary
88 // read non-notebook files as binary
88 reader.readAsArrayBuffer(f);
89 reader.readAsArrayBuffer(f);
89 }
90 }
90 var item = that.new_item(0);
91 var item = that.new_item(0);
91 item.addClass('new-file');
92 item.addClass('new-file');
92 that.add_name_input(f.name, item, file_ext == '.ipynb' ? 'notebook' : 'file');
93 that.add_name_input(f.name, item, file_ext == '.ipynb' ? 'notebook' : 'file');
93 // Store the list item in the reader so we can use it later
94 // Store the list item in the reader so we can use it later
94 // to know which item it belongs to.
95 // to know which item it belongs to.
95 $(reader).data('item', item);
96 $(reader).data('item', item);
96 reader.onload = function (event) {
97 reader.onload = function (event) {
97 var item = $(event.target).data('item');
98 var item = $(event.target).data('item');
98 that.add_file_data(event.target.result, item);
99 that.add_file_data(event.target.result, item);
99 that.add_upload_button(item);
100 that.add_upload_button(item);
100 };
101 };
101 reader.onerror = function (event) {
102 reader.onerror = function (event) {
102 var item = $(event.target).data('item');
103 var item = $(event.target).data('item');
103 var name = item.data('name');
104 var name = item.data('name');
104 item.remove();
105 item.remove();
105 dialog.modal({
106 dialog.modal({
106 title : 'Failed to read file',
107 title : 'Failed to read file',
107 body : "Failed to read file '" + name + "'",
108 body : "Failed to read file '" + name + "'",
108 buttons : {'OK' : { 'class' : 'btn-primary' }}
109 buttons : {'OK' : { 'class' : 'btn-primary' }}
109 });
110 });
110 };
111 };
111 }
112 }
112 // Replace the file input form wth a clone of itself. This is required to
113 // Replace the file input form wth a clone of itself. This is required to
113 // reset the form. Otherwise, if you upload a file, delete it and try to
114 // reset the form. Otherwise, if you upload a file, delete it and try to
114 // upload it again, the changed event won't fire.
115 // upload it again, the changed event won't fire.
115 var form = $('input.fileinput');
116 var form = $('input.fileinput');
116 form.replaceWith(form.clone(true));
117 form.replaceWith(form.clone(true));
117 return false;
118 return false;
118 };
119 };
119
120
120 NotebookList.prototype.clear_list = function (remove_uploads) {
121 NotebookList.prototype.clear_list = function (remove_uploads) {
121 // Clears the navigation tree.
122 // Clears the navigation tree.
122 //
123 //
123 // Parameters
124 // Parameters
124 // remove_uploads: bool=False
125 // remove_uploads: bool=False
125 // Should upload prompts also be removed from the tree.
126 // Should upload prompts also be removed from the tree.
126 if (remove_uploads) {
127 if (remove_uploads) {
127 this.element.children('.list_item').remove();
128 this.element.children('.list_item').remove();
128 } else {
129 } else {
129 this.element.children('.list_item:not(.new-file)').remove();
130 this.element.children('.list_item:not(.new-file)').remove();
130 }
131 }
131 };
132 };
132
133
133 NotebookList.prototype.load_sessions = function(){
134 NotebookList.prototype.load_sessions = function(){
134 this.session_list.load_sessions();
135 this.session_list.load_sessions();
135 };
136 };
136
137
137
138
138 NotebookList.prototype.sessions_loaded = function(data){
139 NotebookList.prototype.sessions_loaded = function(data){
139 this.sessions = data;
140 this.sessions = data;
140 this.load_list();
141 this.load_list();
141 };
142 };
142
143
143 NotebookList.prototype.load_list = function () {
144 NotebookList.prototype.load_list = function () {
144 var that = this;
145 var that = this;
145 this.contents.list_contents(that.notebook_path).then(
146 this.contents.list_contents(that.notebook_path).then(
146 $.proxy(this.draw_notebook_list, this),
147 $.proxy(this.draw_notebook_list, this),
147 function(error) {
148 function(error) {
148 that.draw_notebook_list({content: []}, "Server error: " + error.message);
149 that.draw_notebook_list({content: []}, "Server error: " + error.message);
149 }
150 }
150 );
151 );
151 };
152 };
152
153
153 /**
154 /**
154 * Draw the list of notebooks
155 * Draw the list of notebooks
155 * @method draw_notebook_list
156 * @method draw_notebook_list
156 * @param {Array} list An array of dictionaries representing files or
157 * @param {Array} list An array of dictionaries representing files or
157 * directories.
158 * directories.
158 * @param {String} error_msg An error message
159 * @param {String} error_msg An error message
159 */
160 */
160 NotebookList.prototype.draw_notebook_list = function (list, error_msg) {
161 NotebookList.prototype.draw_notebook_list = function (list, error_msg) {
161 var message = error_msg || 'Notebook list empty.';
162 var message = error_msg || 'Notebook list empty.';
162 var item = null;
163 var item = null;
163 var model = null;
164 var model = null;
164 var len = list.content.length;
165 var len = list.content.length;
165 this.clear_list();
166 this.clear_list();
166 var n_uploads = this.element.children('.list_item').length;
167 var n_uploads = this.element.children('.list_item').length;
167 if (len === 0) {
168 if (len === 0) {
168 item = this.new_item(0);
169 item = this.new_item(0);
169 var span12 = item.children().first();
170 var span12 = item.children().first();
170 span12.empty();
171 span12.empty();
171 span12.append($('<div style="margin:auto;text-align:center;color:grey"/>').text(message));
172 span12.append($('<div style="margin:auto;text-align:center;color:grey"/>').text(message));
172 }
173 }
173 var path = this.notebook_path;
174 var path = this.notebook_path;
174 var offset = n_uploads;
175 var offset = n_uploads;
175 if (path !== '') {
176 if (path !== '') {
176 item = this.new_item(offset);
177 item = this.new_item(offset);
177 model = {
178 model = {
178 type: 'directory',
179 type: 'directory',
179 name: '..',
180 name: '..',
180 path: utils.url_path_split(path)[0],
181 path: utils.url_path_split(path)[0],
181 };
182 };
182 this.add_link(model, item);
183 this.add_link(model, item);
183 offset += 1;
184 offset += 1;
184 }
185 }
185 for (var i=0; i<len; i++) {
186 for (var i=0; i<len; i++) {
186 model = list.content[i];
187 model = list.content[i];
187 item = this.new_item(i+offset);
188 item = this.new_item(i+offset);
188 this.add_link(model, item);
189 this.add_link(model, item);
189 }
190 }
191 // Trigger an event when we've finished drawing the notebook list.
192 events.trigger('draw_notebook_list.NotebookList');
190 };
193 };
191
194
192
195
193 NotebookList.prototype.new_item = function (index) {
196 NotebookList.prototype.new_item = function (index) {
194 var item = $('<div/>').addClass("list_item").addClass("row");
197 var item = $('<div/>').addClass("list_item").addClass("row");
195 // item.addClass('list_item ui-widget ui-widget-content ui-helper-clearfix');
198 // item.addClass('list_item ui-widget ui-widget-content ui-helper-clearfix');
196 // item.css('border-top-style','none');
199 // item.css('border-top-style','none');
197 item.append($("<div/>").addClass("col-md-12").append(
200 item.append($("<div/>").addClass("col-md-12").append(
198 $('<i/>').addClass('item_icon')
201 $('<i/>').addClass('item_icon')
199 ).append(
202 ).append(
200 $("<a/>").addClass("item_link").append(
203 $("<a/>").addClass("item_link").append(
201 $("<span/>").addClass("item_name")
204 $("<span/>").addClass("item_name")
202 )
205 )
203 ).append(
206 ).append(
204 $('<div/>').addClass("item_buttons btn-group pull-right")
207 $('<div/>').addClass("item_buttons btn-group pull-right")
205 ));
208 ));
206
209
207 if (index === -1) {
210 if (index === -1) {
208 this.element.append(item);
211 this.element.append(item);
209 } else {
212 } else {
210 this.element.children().eq(index).after(item);
213 this.element.children().eq(index).after(item);
211 }
214 }
212 return item;
215 return item;
213 };
216 };
214
217
215
218
216 NotebookList.icons = {
219 NotebookList.icons = {
217 directory: 'folder_icon',
220 directory: 'folder_icon',
218 notebook: 'notebook_icon',
221 notebook: 'notebook_icon',
219 file: 'file_icon',
222 file: 'file_icon',
220 };
223 };
221
224
222 NotebookList.uri_prefixes = {
225 NotebookList.uri_prefixes = {
223 directory: 'tree',
226 directory: 'tree',
224 notebook: 'notebooks',
227 notebook: 'notebooks',
225 file: 'files',
228 file: 'files',
226 };
229 };
227
230
228
231
229 NotebookList.prototype.add_link = function (model, item) {
232 NotebookList.prototype.add_link = function (model, item) {
230 var path = model.path,
233 var path = model.path,
231 name = model.name;
234 name = model.name;
232 item.data('name', name);
235 item.data('name', name);
233 item.data('path', path);
236 item.data('path', path);
234 item.find(".item_name").text(name);
237 item.find(".item_name").text(name);
235 var icon = NotebookList.icons[model.type];
238 var icon = NotebookList.icons[model.type];
236 var uri_prefix = NotebookList.uri_prefixes[model.type];
239 var uri_prefix = NotebookList.uri_prefixes[model.type];
237 item.find(".item_icon").addClass(icon).addClass('icon-fixed-width');
240 item.find(".item_icon").addClass(icon).addClass('icon-fixed-width');
238 var link = item.find("a.item_link")
241 var link = item.find("a.item_link")
239 .attr('href',
242 .attr('href',
240 utils.url_join_encode(
243 utils.url_join_encode(
241 this.base_url,
244 this.base_url,
242 uri_prefix,
245 uri_prefix,
243 path
246 path
244 )
247 )
245 );
248 );
246 // directory nav doesn't open new tabs
249 // directory nav doesn't open new tabs
247 // files, notebooks do
250 // files, notebooks do
248 if (model.type !== "directory") {
251 if (model.type !== "directory") {
249 link.attr('target','_blank');
252 link.attr('target','_blank');
250 }
253 }
251 if (model.type !== 'directory') {
254 if (model.type !== 'directory') {
252 this.add_duplicate_button(item);
255 this.add_duplicate_button(item);
253 }
256 }
254 if (model.type == 'file') {
257 if (model.type == 'file') {
255 this.add_delete_button(item);
258 this.add_delete_button(item);
256 } else if (model.type == 'notebook') {
259 } else if (model.type == 'notebook') {
257 if (this.sessions[path] === undefined){
260 if (this.sessions[path] === undefined){
258 this.add_delete_button(item);
261 this.add_delete_button(item);
259 } else {
262 } else {
260 this.add_shutdown_button(item, this.sessions[path]);
263 this.add_shutdown_button(item, this.sessions[path]);
261 }
264 }
262 }
265 }
263 };
266 };
264
267
265
268
266 NotebookList.prototype.add_name_input = function (name, item, icon_type) {
269 NotebookList.prototype.add_name_input = function (name, item, icon_type) {
267 item.data('name', name);
270 item.data('name', name);
268 item.find(".item_icon").addClass(NotebookList.icons[icon_type]).addClass('icon-fixed-width');
271 item.find(".item_icon").addClass(NotebookList.icons[icon_type]).addClass('icon-fixed-width');
269 item.find(".item_name").empty().append(
272 item.find(".item_name").empty().append(
270 $('<input/>')
273 $('<input/>')
271 .addClass("filename_input")
274 .addClass("filename_input")
272 .attr('value', name)
275 .attr('value', name)
273 .attr('size', '30')
276 .attr('size', '30')
274 .attr('type', 'text')
277 .attr('type', 'text')
275 .keyup(function(event){
278 .keyup(function(event){
276 if(event.keyCode == 13){item.find('.upload_button').click();}
279 if(event.keyCode == 13){item.find('.upload_button').click();}
277 else if(event.keyCode == 27){item.remove();}
280 else if(event.keyCode == 27){item.remove();}
278 })
281 })
279 );
282 );
280 };
283 };
281
284
282
285
283 NotebookList.prototype.add_file_data = function (data, item) {
286 NotebookList.prototype.add_file_data = function (data, item) {
284 item.data('filedata', data);
287 item.data('filedata', data);
285 };
288 };
286
289
287
290
288 NotebookList.prototype.add_shutdown_button = function (item, session) {
291 NotebookList.prototype.add_shutdown_button = function (item, session) {
289 var that = this;
292 var that = this;
290 var shutdown_button = $("<button/>").text("Shutdown").addClass("btn btn-xs btn-danger").
293 var shutdown_button = $("<button/>").text("Shutdown").addClass("btn btn-xs btn-danger").
291 click(function (e) {
294 click(function (e) {
292 var settings = {
295 var settings = {
293 processData : false,
296 processData : false,
294 cache : false,
297 cache : false,
295 type : "DELETE",
298 type : "DELETE",
296 dataType : "json",
299 dataType : "json",
297 success : function () {
300 success : function () {
298 that.load_sessions();
301 that.load_sessions();
299 },
302 },
300 error : utils.log_ajax_error,
303 error : utils.log_ajax_error,
301 };
304 };
302 var url = utils.url_join_encode(
305 var url = utils.url_join_encode(
303 that.base_url,
306 that.base_url,
304 'api/sessions',
307 'api/sessions',
305 session
308 session
306 );
309 );
307 $.ajax(url, settings);
310 $.ajax(url, settings);
308 return false;
311 return false;
309 });
312 });
310 item.find(".item_buttons").append(shutdown_button);
313 item.find(".item_buttons").append(shutdown_button);
311 };
314 };
312
315
313 NotebookList.prototype.add_duplicate_button = function (item) {
316 NotebookList.prototype.add_duplicate_button = function (item) {
314 var notebooklist = this;
317 var notebooklist = this;
315 var duplicate_button = $("<button/>").text("Duplicate").addClass("btn btn-default btn-xs").
318 var duplicate_button = $("<button/>").text("Duplicate").addClass("btn btn-default btn-xs").
316 click(function (e) {
319 click(function (e) {
317 // $(this) is the button that was clicked.
320 // $(this) is the button that was clicked.
318 var that = $(this);
321 var that = $(this);
319 var name = item.data('name');
322 var name = item.data('name');
320 var path = item.data('path');
323 var path = item.data('path');
321 var message = 'Are you sure you want to duplicate ' + name + '?';
324 var message = 'Are you sure you want to duplicate ' + name + '?';
322 var copy_from = {copy_from : path};
325 var copy_from = {copy_from : path};
323 IPython.dialog.modal({
326 IPython.dialog.modal({
324 title : "Duplicate " + name,
327 title : "Duplicate " + name,
325 body : message,
328 body : message,
326 buttons : {
329 buttons : {
327 Duplicate : {
330 Duplicate : {
328 class: "btn-primary",
331 class: "btn-primary",
329 click: function() {
332 click: function() {
330 notebooklist.contents.copy(path, notebooklist.notebook_path).then(function () {
333 notebooklist.contents.copy(path, notebooklist.notebook_path).then(function () {
331 notebooklist.load_list();
334 notebooklist.load_list();
332 });
335 });
333 }
336 }
334 },
337 },
335 Cancel : {}
338 Cancel : {}
336 }
339 }
337 });
340 });
338 return false;
341 return false;
339 });
342 });
340 item.find(".item_buttons").append(duplicate_button);
343 item.find(".item_buttons").append(duplicate_button);
341 };
344 };
342
345
343 NotebookList.prototype.add_delete_button = function (item) {
346 NotebookList.prototype.add_delete_button = function (item) {
344 var notebooklist = this;
347 var notebooklist = this;
345 var delete_button = $("<button/>").text("Delete").addClass("btn btn-default btn-xs").
348 var delete_button = $("<button/>").text("Delete").addClass("btn btn-default btn-xs").
346 click(function (e) {
349 click(function (e) {
347 // $(this) is the button that was clicked.
350 // $(this) is the button that was clicked.
348 var that = $(this);
351 var that = $(this);
349 // We use the filename from the parent list_item element's
352 // We use the filename from the parent list_item element's
350 // data because the outer scope's values change as we iterate through the loop.
353 // data because the outer scope's values change as we iterate through the loop.
351 var parent_item = that.parents('div.list_item');
354 var parent_item = that.parents('div.list_item');
352 var name = parent_item.data('name');
355 var name = parent_item.data('name');
353 var path = parent_item.data('path');
356 var path = parent_item.data('path');
354 var message = 'Are you sure you want to permanently delete the file: ' + name + '?';
357 var message = 'Are you sure you want to permanently delete the file: ' + name + '?';
355 dialog.modal({
358 dialog.modal({
356 title : "Delete file",
359 title : "Delete file",
357 body : message,
360 body : message,
358 buttons : {
361 buttons : {
359 Delete : {
362 Delete : {
360 class: "btn-danger",
363 class: "btn-danger",
361 click: function() {
364 click: function() {
362 notebooklist.contents.delete(path).then(
365 notebooklist.contents.delete(path).then(
363 function() {
366 function() {
364 notebooklist.notebook_deleted(path);
367 notebooklist.notebook_deleted(path);
365 }
368 }
366 );
369 );
367 }
370 }
368 },
371 },
369 Cancel : {}
372 Cancel : {}
370 }
373 }
371 });
374 });
372 return false;
375 return false;
373 });
376 });
374 item.find(".item_buttons").append(delete_button);
377 item.find(".item_buttons").append(delete_button);
375 };
378 };
376
379
377 NotebookList.prototype.notebook_deleted = function(path) {
380 NotebookList.prototype.notebook_deleted = function(path) {
378 // Remove the deleted notebook.
381 // Remove the deleted notebook.
379 $( ":data(path)" ).each(function() {
382 $( ":data(path)" ).each(function() {
380 var element = $(this);
383 var element = $(this);
381 if (element.data("path") == path) {
384 if (element.data("path") == path) {
382 element.remove();
385 element.remove();
386 events.trigger('notebook_deleted.NotebookList');
383 }
387 }
384 });
388 });
385 };
389 };
386
390
387
391
388 NotebookList.prototype.add_upload_button = function (item) {
392 NotebookList.prototype.add_upload_button = function (item) {
389 var that = this;
393 var that = this;
390 var upload_button = $('<button/>').text("Upload")
394 var upload_button = $('<button/>').text("Upload")
391 .addClass('btn btn-primary btn-xs upload_button')
395 .addClass('btn btn-primary btn-xs upload_button')
392 .click(function (e) {
396 .click(function (e) {
393 var filename = item.find('.item_name > input').val();
397 var filename = item.find('.item_name > input').val();
394 var path = utils.url_path_join(that.notebook_path, filename);
398 var path = utils.url_path_join(that.notebook_path, filename);
395 var filedata = item.data('filedata');
399 var filedata = item.data('filedata');
396 var format = 'text';
400 var format = 'text';
397 if (filename.length === 0 || filename[0] === '.') {
401 if (filename.length === 0 || filename[0] === '.') {
398 dialog.modal({
402 dialog.modal({
399 title : 'Invalid file name',
403 title : 'Invalid file name',
400 body : "File names must be at least one character and not start with a dot",
404 body : "File names must be at least one character and not start with a dot",
401 buttons : {'OK' : { 'class' : 'btn-primary' }}
405 buttons : {'OK' : { 'class' : 'btn-primary' }}
402 });
406 });
403 return false;
407 return false;
404 }
408 }
405 if (filedata instanceof ArrayBuffer) {
409 if (filedata instanceof ArrayBuffer) {
406 // base64-encode binary file data
410 // base64-encode binary file data
407 var bytes = '';
411 var bytes = '';
408 var buf = new Uint8Array(filedata);
412 var buf = new Uint8Array(filedata);
409 var nbytes = buf.byteLength;
413 var nbytes = buf.byteLength;
410 for (var i=0; i<nbytes; i++) {
414 for (var i=0; i<nbytes; i++) {
411 bytes += String.fromCharCode(buf[i]);
415 bytes += String.fromCharCode(buf[i]);
412 }
416 }
413 filedata = btoa(bytes);
417 filedata = btoa(bytes);
414 format = 'base64';
418 format = 'base64';
415 }
419 }
416 var model = {};
420 var model = {};
417
421
418 var name_and_ext = utils.splitext(filename);
422 var name_and_ext = utils.splitext(filename);
419 var file_ext = name_and_ext[1];
423 var file_ext = name_and_ext[1];
420 var content_type;
424 var content_type;
421 if (file_ext === '.ipynb') {
425 if (file_ext === '.ipynb') {
422 model.type = 'notebook';
426 model.type = 'notebook';
423 model.format = 'json';
427 model.format = 'json';
424 try {
428 try {
425 model.content = JSON.parse(filedata);
429 model.content = JSON.parse(filedata);
426 } catch (e) {
430 } catch (e) {
427 dialog.modal({
431 dialog.modal({
428 title : 'Cannot upload invalid Notebook',
432 title : 'Cannot upload invalid Notebook',
429 body : "The error was: " + e,
433 body : "The error was: " + e,
430 buttons : {'OK' : {
434 buttons : {'OK' : {
431 'class' : 'btn-primary',
435 'class' : 'btn-primary',
432 click: function () {
436 click: function () {
433 item.remove();
437 item.remove();
434 }
438 }
435 }}
439 }}
436 });
440 });
437 return false;
441 return false;
438 }
442 }
439 content_type = 'application/json';
443 content_type = 'application/json';
440 } else {
444 } else {
441 model.type = 'file';
445 model.type = 'file';
442 model.format = format;
446 model.format = format;
443 model.content = filedata;
447 model.content = filedata;
444 content_type = 'application/octet-stream';
448 content_type = 'application/octet-stream';
445 }
449 }
446 filedata = item.data('filedata');
450 filedata = item.data('filedata');
447
451
448 var on_success = function () {
452 var on_success = function () {
449 item.removeClass('new-file');
453 item.removeClass('new-file');
450 that.add_link(model, item);
454 that.add_link(model, item);
451 that.add_delete_button(item);
455 that.add_delete_button(item);
452 that.session_list.load_sessions();
456 that.session_list.load_sessions();
453 };
457 };
454
458
455 var exists = false;
459 var exists = false;
456 $.each(that.element.find('.list_item:not(.new-file)'), function(k,v){
460 $.each(that.element.find('.list_item:not(.new-file)'), function(k,v){
457 if ($(v).data('name') === filename) { exists = true; return false; }
461 if ($(v).data('name') === filename) { exists = true; return false; }
458 });
462 });
459
463
460 if (exists) {
464 if (exists) {
461 dialog.modal({
465 dialog.modal({
462 title : "Replace file",
466 title : "Replace file",
463 body : 'There is already a file named ' + filename + ', do you want to replace it?',
467 body : 'There is already a file named ' + filename + ', do you want to replace it?',
464 buttons : {
468 buttons : {
465 Overwrite : {
469 Overwrite : {
466 class: "btn-danger",
470 class: "btn-danger",
467 click: function () {
471 click: function () {
468 that.contents.save(path, model).then(on_success);
472 that.contents.save(path, model).then(on_success);
469 }
473 }
470 },
474 },
471 Cancel : {
475 Cancel : {
472 click: function() { item.remove(); }
476 click: function() { item.remove(); }
473 }
477 }
474 }
478 }
475 });
479 });
476 } else {
480 } else {
477 that.contents.save(path, model).then(on_success);
481 that.contents.save(path, model).then(on_success);
478 }
482 }
479
483
480 return false;
484 return false;
481 });
485 });
482 var cancel_button = $('<button/>').text("Cancel")
486 var cancel_button = $('<button/>').text("Cancel")
483 .addClass("btn btn-default btn-xs")
487 .addClass("btn btn-default btn-xs")
484 .click(function (e) {
488 .click(function (e) {
485 item.remove();
489 item.remove();
486 return false;
490 return false;
487 });
491 });
488 item.find(".item_buttons").empty()
492 item.find(".item_buttons").empty()
489 .append(upload_button)
493 .append(upload_button)
490 .append(cancel_button);
494 .append(cancel_button);
491 };
495 };
492
496
493
497
494 // Backwards compatability.
498 // Backwards compatability.
495 IPython.NotebookList = NotebookList;
499 IPython.NotebookList = NotebookList;
496
500
497 return {'NotebookList': NotebookList};
501 return {'NotebookList': NotebookList};
498 });
502 });
General Comments 0
You need to be logged in to leave comments. Login now