##// END OF EJS Templates
In person review with @ellisonbg
jon -
Show More
@@ -1,563 +1,564 b''
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 ], function(IPython, $, utils) {
8 ], function(IPython, $, utils) {
9 "use strict";
9 "use strict";
10
10
11 // monkey patch CM to be able to syntax highlight cell magics
11 // monkey patch CM to be able to syntax highlight cell magics
12 // bug reported upstream,
12 // bug reported upstream,
13 // see https://github.com/marijnh/CodeMirror2/issues/670
13 // see https://github.com/marijnh/CodeMirror2/issues/670
14 if(CodeMirror.getMode(1,'text/plain').indent === undefined ){
14 if(CodeMirror.getMode(1,'text/plain').indent === undefined ){
15 CodeMirror.modes.null = function() {
15 CodeMirror.modes.null = function() {
16 return {token: function(stream) {stream.skipToEnd();},indent : function(){return 0;}};
16 return {token: function(stream) {stream.skipToEnd();},indent : function(){return 0;}};
17 };
17 };
18 }
18 }
19
19
20 CodeMirror.patchedGetMode = function(config, mode){
20 CodeMirror.patchedGetMode = function(config, mode){
21 var cmmode = CodeMirror.getMode(config, mode);
21 var cmmode = CodeMirror.getMode(config, mode);
22 if(cmmode.indent === null) {
22 if(cmmode.indent === null) {
23 console.log('patch mode "' , mode, '" on the fly');
23 console.log('patch mode "' , mode, '" on the fly');
24 cmmode.indent = function(){return 0;};
24 cmmode.indent = function(){return 0;};
25 }
25 }
26 return cmmode;
26 return cmmode;
27 };
27 };
28 // end monkey patching CodeMirror
28 // end monkey patching CodeMirror
29
29
30 /**
30 /**
31 * The Base `Cell` class from which to inherit
31 * The Base `Cell` class from which to inherit
32 * @class Cell
32 * @class Cell
33 **/
33 **/
34
34
35 /*
35 /*
36 * @constructor
36 * @constructor
37 *
37 *
38 * @param {object|undefined} [options]
38 * @param {object|undefined} [options]
39 * @param [options.cm_config] {object} config to pass to CodeMirror, will extend default parameters
39 * @param [options.cm_config] {object} config to pass to CodeMirror, will extend default parameters
40 */
40 */
41 var Cell = function (options, keyboard_manager, events) {
41 var Cell = function (options) {
42 this.keyboard_manager = keyboard_manager;
42 options = options || {};
43 this.events = events;
43 this.keyboard_manager = options.keyboard_manager;
44 options = this.mergeopt(Cell, options);
44 this.events = options.events;
45 var config = this.mergeopt(Cell, options.config);
45 // superclass default overwrite our default
46 // superclass default overwrite our default
46
47
47 this.placeholder = options.placeholder || '';
48 this.placeholder = config.placeholder || '';
48 this.read_only = options.cm_config.readOnly;
49 this.read_only = config.cm_config.readOnly;
49 this.selected = false;
50 this.selected = false;
50 this.rendered = false;
51 this.rendered = false;
51 this.mode = 'command';
52 this.mode = 'command';
52 this.metadata = {};
53 this.metadata = {};
53 // load this from metadata later ?
54 // load this from metadata later ?
54 this.user_highlight = 'auto';
55 this.user_highlight = 'auto';
55 this.cm_config = options.cm_config;
56 this.cm_config = config.cm_config;
56 this.cell_id = utils.uuid();
57 this.cell_id = utils.uuid();
57 this._options = options;
58 this._options = config;
58
59
59 // For JS VM engines optimization, attributes should be all set (even
60 // For JS VM engines optimization, attributes should be all set (even
60 // to null) in the constructor, and if possible, if different subclass
61 // to null) in the constructor, and if possible, if different subclass
61 // have new attributes with same name, they should be created in the
62 // have new attributes with same name, they should be created in the
62 // same order. Easiest is to create and set to null in parent class.
63 // same order. Easiest is to create and set to null in parent class.
63
64
64 this.element = null;
65 this.element = null;
65 this.cell_type = this.cell_type || null;
66 this.cell_type = this.cell_type || null;
66 this.code_mirror = null;
67 this.code_mirror = null;
67
68
68 this.create_element();
69 this.create_element();
69 if (this.element !== null) {
70 if (this.element !== null) {
70 this.element.data("cell", this);
71 this.element.data("cell", this);
71 this.bind_events();
72 this.bind_events();
72 this.init_classes();
73 this.init_classes();
73 }
74 }
74 };
75 };
75
76
76 Cell.options_default = {
77 Cell.options_default = {
77 cm_config : {
78 cm_config : {
78 indentUnit : 4,
79 indentUnit : 4,
79 readOnly: false,
80 readOnly: false,
80 theme: "default",
81 theme: "default",
81 extraKeys: {
82 extraKeys: {
82 "Cmd-Right":"goLineRight",
83 "Cmd-Right":"goLineRight",
83 "End":"goLineRight",
84 "End":"goLineRight",
84 "Cmd-Left":"goLineLeft"
85 "Cmd-Left":"goLineLeft"
85 }
86 }
86 }
87 }
87 };
88 };
88
89
89 // FIXME: Workaround CM Bug #332 (Safari segfault on drag)
90 // FIXME: Workaround CM Bug #332 (Safari segfault on drag)
90 // by disabling drag/drop altogether on Safari
91 // by disabling drag/drop altogether on Safari
91 // https://github.com/marijnh/CodeMirror/issues/332
92 // https://github.com/marijnh/CodeMirror/issues/332
92 if (utils.browser[0] == "Safari") {
93 if (utils.browser[0] == "Safari") {
93 Cell.options_default.cm_config.dragDrop = false;
94 Cell.options_default.cm_config.dragDrop = false;
94 }
95 }
95
96
96 Cell.prototype.mergeopt = function(_class, options, overwrite){
97 Cell.prototype.mergeopt = function(_class, options, overwrite){
97 options = options || {};
98 options = options || {};
98 overwrite = overwrite || {};
99 overwrite = overwrite || {};
99 return $.extend(true, {}, _class.options_default, options, overwrite);
100 return $.extend(true, {}, _class.options_default, options, overwrite);
100 };
101 };
101
102
102 /**
103 /**
103 * Empty. Subclasses must implement create_element.
104 * Empty. Subclasses must implement create_element.
104 * This should contain all the code to create the DOM element in notebook
105 * This should contain all the code to create the DOM element in notebook
105 * and will be called by Base Class constructor.
106 * and will be called by Base Class constructor.
106 * @method create_element
107 * @method create_element
107 */
108 */
108 Cell.prototype.create_element = function () {
109 Cell.prototype.create_element = function () {
109 };
110 };
110
111
111 Cell.prototype.init_classes = function () {
112 Cell.prototype.init_classes = function () {
112 // Call after this.element exists to initialize the css classes
113 // Call after this.element exists to initialize the css classes
113 // related to selected, rendered and mode.
114 // related to selected, rendered and mode.
114 if (this.selected) {
115 if (this.selected) {
115 this.element.addClass('selected');
116 this.element.addClass('selected');
116 } else {
117 } else {
117 this.element.addClass('unselected');
118 this.element.addClass('unselected');
118 }
119 }
119 if (this.rendered) {
120 if (this.rendered) {
120 this.element.addClass('rendered');
121 this.element.addClass('rendered');
121 } else {
122 } else {
122 this.element.addClass('unrendered');
123 this.element.addClass('unrendered');
123 }
124 }
124 if (this.mode === 'edit') {
125 if (this.mode === 'edit') {
125 this.element.addClass('edit_mode');
126 this.element.addClass('edit_mode');
126 } else {
127 } else {
127 this.element.addClass('command_mode');
128 this.element.addClass('command_mode');
128 }
129 }
129 };
130 };
130
131
131 /**
132 /**
132 * Subclasses can implement override bind_events.
133 * Subclasses can implement override bind_events.
133 * Be carefull to call the parent method when overwriting as it fires event.
134 * Be carefull to call the parent method when overwriting as it fires event.
134 * this will be triggerd after create_element in constructor.
135 * this will be triggerd after create_element in constructor.
135 * @method bind_events
136 * @method bind_events
136 */
137 */
137 Cell.prototype.bind_events = function () {
138 Cell.prototype.bind_events = function () {
138 var that = this;
139 var that = this;
139 // We trigger events so that Cell doesn't have to depend on Notebook.
140 // We trigger events so that Cell doesn't have to depend on Notebook.
140 that.element.click(function (event) {
141 that.element.click(function (event) {
141 if (!that.selected) {
142 if (!that.selected) {
142 that.events.trigger('select.Cell', {'cell':that});
143 that.events.trigger('select.Cell', {'cell':that});
143 }
144 }
144 });
145 });
145 that.element.focusin(function (event) {
146 that.element.focusin(function (event) {
146 if (!that.selected) {
147 if (!that.selected) {
147 that.events.trigger('select.Cell', {'cell':that});
148 that.events.trigger('select.Cell', {'cell':that});
148 }
149 }
149 });
150 });
150 if (this.code_mirror) {
151 if (this.code_mirror) {
151 this.code_mirror.on("change", function(cm, change) {
152 this.code_mirror.on("change", function(cm, change) {
152 that.events.trigger("set_dirty.Notebook", {value: true});
153 that.events.trigger("set_dirty.Notebook", {value: true});
153 });
154 });
154 }
155 }
155 if (this.code_mirror) {
156 if (this.code_mirror) {
156 this.code_mirror.on('focus', function(cm, change) {
157 this.code_mirror.on('focus', function(cm, change) {
157 that.events.trigger('edit_mode.Cell', {cell: that});
158 that.events.trigger('edit_mode.Cell', {cell: that});
158 });
159 });
159 }
160 }
160 if (this.code_mirror) {
161 if (this.code_mirror) {
161 this.code_mirror.on('blur', function(cm, change) {
162 this.code_mirror.on('blur', function(cm, change) {
162 that.events.trigger('command_mode.Cell', {cell: that});
163 that.events.trigger('command_mode.Cell', {cell: that});
163 });
164 });
164 }
165 }
165 };
166 };
166
167
167 /**
168 /**
168 * This method gets called in CodeMirror's onKeyDown/onKeyPress
169 * This method gets called in CodeMirror's onKeyDown/onKeyPress
169 * handlers and is used to provide custom key handling.
170 * handlers and is used to provide custom key handling.
170 *
171 *
171 * To have custom handling, subclasses should override this method, but still call it
172 * To have custom handling, subclasses should override this method, but still call it
172 * in order to process the Edit mode keyboard shortcuts.
173 * in order to process the Edit mode keyboard shortcuts.
173 *
174 *
174 * @method handle_codemirror_keyevent
175 * @method handle_codemirror_keyevent
175 * @param {CodeMirror} editor - The codemirror instance bound to the cell
176 * @param {CodeMirror} editor - The codemirror instance bound to the cell
176 * @param {event} event - key press event which either should or should not be handled by CodeMirror
177 * @param {event} event - key press event which either should or should not be handled by CodeMirror
177 * @return {Boolean} `true` if CodeMirror should ignore the event, `false` Otherwise
178 * @return {Boolean} `true` if CodeMirror should ignore the event, `false` Otherwise
178 */
179 */
179 Cell.prototype.handle_codemirror_keyevent = function (editor, event) {
180 Cell.prototype.handle_codemirror_keyevent = function (editor, event) {
180 var that = this;
181 var that = this;
181 var shortcuts = this.keyboard_manager.edit_shortcuts;
182 var shortcuts = this.keyboard_manager.edit_shortcuts;
182
183
183 // if this is an edit_shortcuts shortcut, the global keyboard/shortcut
184 // if this is an edit_shortcuts shortcut, the global keyboard/shortcut
184 // manager will handle it
185 // manager will handle it
185 if (shortcuts.handles(event)) { return true; }
186 if (shortcuts.handles(event)) { return true; }
186
187
187 return false;
188 return false;
188 };
189 };
189
190
190
191
191 /**
192 /**
192 * Triger typsetting of math by mathjax on current cell element
193 * Triger typsetting of math by mathjax on current cell element
193 * @method typeset
194 * @method typeset
194 */
195 */
195 Cell.prototype.typeset = function () {
196 Cell.prototype.typeset = function () {
196 if (window.MathJax) {
197 if (window.MathJax) {
197 var cell_math = this.element.get(0);
198 var cell_math = this.element.get(0);
198 MathJax.Hub.Queue(["Typeset", MathJax.Hub, cell_math]);
199 MathJax.Hub.Queue(["Typeset", MathJax.Hub, cell_math]);
199 }
200 }
200 };
201 };
201
202
202 /**
203 /**
203 * handle cell level logic when a cell is selected
204 * handle cell level logic when a cell is selected
204 * @method select
205 * @method select
205 * @return is the action being taken
206 * @return is the action being taken
206 */
207 */
207 Cell.prototype.select = function () {
208 Cell.prototype.select = function () {
208 if (!this.selected) {
209 if (!this.selected) {
209 this.element.addClass('selected');
210 this.element.addClass('selected');
210 this.element.removeClass('unselected');
211 this.element.removeClass('unselected');
211 this.selected = true;
212 this.selected = true;
212 return true;
213 return true;
213 } else {
214 } else {
214 return false;
215 return false;
215 }
216 }
216 };
217 };
217
218
218 /**
219 /**
219 * handle cell level logic when a cell is unselected
220 * handle cell level logic when a cell is unselected
220 * @method unselect
221 * @method unselect
221 * @return is the action being taken
222 * @return is the action being taken
222 */
223 */
223 Cell.prototype.unselect = function () {
224 Cell.prototype.unselect = function () {
224 if (this.selected) {
225 if (this.selected) {
225 this.element.addClass('unselected');
226 this.element.addClass('unselected');
226 this.element.removeClass('selected');
227 this.element.removeClass('selected');
227 this.selected = false;
228 this.selected = false;
228 return true;
229 return true;
229 } else {
230 } else {
230 return false;
231 return false;
231 }
232 }
232 };
233 };
233
234
234 /**
235 /**
235 * handle cell level logic when a cell is rendered
236 * handle cell level logic when a cell is rendered
236 * @method render
237 * @method render
237 * @return is the action being taken
238 * @return is the action being taken
238 */
239 */
239 Cell.prototype.render = function () {
240 Cell.prototype.render = function () {
240 if (!this.rendered) {
241 if (!this.rendered) {
241 this.element.addClass('rendered');
242 this.element.addClass('rendered');
242 this.element.removeClass('unrendered');
243 this.element.removeClass('unrendered');
243 this.rendered = true;
244 this.rendered = true;
244 return true;
245 return true;
245 } else {
246 } else {
246 return false;
247 return false;
247 }
248 }
248 };
249 };
249
250
250 /**
251 /**
251 * handle cell level logic when a cell is unrendered
252 * handle cell level logic when a cell is unrendered
252 * @method unrender
253 * @method unrender
253 * @return is the action being taken
254 * @return is the action being taken
254 */
255 */
255 Cell.prototype.unrender = function () {
256 Cell.prototype.unrender = function () {
256 if (this.rendered) {
257 if (this.rendered) {
257 this.element.addClass('unrendered');
258 this.element.addClass('unrendered');
258 this.element.removeClass('rendered');
259 this.element.removeClass('rendered');
259 this.rendered = false;
260 this.rendered = false;
260 return true;
261 return true;
261 } else {
262 } else {
262 return false;
263 return false;
263 }
264 }
264 };
265 };
265
266
266 /**
267 /**
267 * Delegates keyboard shortcut handling to either IPython keyboard
268 * Delegates keyboard shortcut handling to either IPython keyboard
268 * manager when in command mode, or CodeMirror when in edit mode
269 * manager when in command mode, or CodeMirror when in edit mode
269 *
270 *
270 * @method handle_keyevent
271 * @method handle_keyevent
271 * @param {CodeMirror} editor - The codemirror instance bound to the cell
272 * @param {CodeMirror} editor - The codemirror instance bound to the cell
272 * @param {event} - key event to be handled
273 * @param {event} - key event to be handled
273 * @return {Boolean} `true` if CodeMirror should ignore the event, `false` Otherwise
274 * @return {Boolean} `true` if CodeMirror should ignore the event, `false` Otherwise
274 */
275 */
275 Cell.prototype.handle_keyevent = function (editor, event) {
276 Cell.prototype.handle_keyevent = function (editor, event) {
276
277
277 // console.log('CM', this.mode, event.which, event.type)
278 // console.log('CM', this.mode, event.which, event.type)
278
279
279 if (this.mode === 'command') {
280 if (this.mode === 'command') {
280 return true;
281 return true;
281 } else if (this.mode === 'edit') {
282 } else if (this.mode === 'edit') {
282 return this.handle_codemirror_keyevent(editor, event);
283 return this.handle_codemirror_keyevent(editor, event);
283 }
284 }
284 };
285 };
285
286
286 /**
287 /**
287 * @method at_top
288 * @method at_top
288 * @return {Boolean}
289 * @return {Boolean}
289 */
290 */
290 Cell.prototype.at_top = function () {
291 Cell.prototype.at_top = function () {
291 var cm = this.code_mirror;
292 var cm = this.code_mirror;
292 var cursor = cm.getCursor();
293 var cursor = cm.getCursor();
293 if (cursor.line === 0 && cursor.ch === 0) {
294 if (cursor.line === 0 && cursor.ch === 0) {
294 return true;
295 return true;
295 }
296 }
296 return false;
297 return false;
297 };
298 };
298
299
299 /**
300 /**
300 * @method at_bottom
301 * @method at_bottom
301 * @return {Boolean}
302 * @return {Boolean}
302 * */
303 * */
303 Cell.prototype.at_bottom = function () {
304 Cell.prototype.at_bottom = function () {
304 var cm = this.code_mirror;
305 var cm = this.code_mirror;
305 var cursor = cm.getCursor();
306 var cursor = cm.getCursor();
306 if (cursor.line === (cm.lineCount()-1) && cursor.ch === cm.getLine(cursor.line).length) {
307 if (cursor.line === (cm.lineCount()-1) && cursor.ch === cm.getLine(cursor.line).length) {
307 return true;
308 return true;
308 }
309 }
309 return false;
310 return false;
310 };
311 };
311
312
312 /**
313 /**
313 * enter the command mode for the cell
314 * enter the command mode for the cell
314 * @method command_mode
315 * @method command_mode
315 * @return is the action being taken
316 * @return is the action being taken
316 */
317 */
317 Cell.prototype.command_mode = function () {
318 Cell.prototype.command_mode = function () {
318 if (this.mode !== 'command') {
319 if (this.mode !== 'command') {
319 this.element.addClass('command_mode');
320 this.element.addClass('command_mode');
320 this.element.removeClass('edit_mode');
321 this.element.removeClass('edit_mode');
321 this.mode = 'command';
322 this.mode = 'command';
322 return true;
323 return true;
323 } else {
324 } else {
324 return false;
325 return false;
325 }
326 }
326 };
327 };
327
328
328 /**
329 /**
329 * enter the edit mode for the cell
330 * enter the edit mode for the cell
330 * @method command_mode
331 * @method command_mode
331 * @return is the action being taken
332 * @return is the action being taken
332 */
333 */
333 Cell.prototype.edit_mode = function () {
334 Cell.prototype.edit_mode = function () {
334 if (this.mode !== 'edit') {
335 if (this.mode !== 'edit') {
335 this.element.addClass('edit_mode');
336 this.element.addClass('edit_mode');
336 this.element.removeClass('command_mode');
337 this.element.removeClass('command_mode');
337 this.mode = 'edit';
338 this.mode = 'edit';
338 return true;
339 return true;
339 } else {
340 } else {
340 return false;
341 return false;
341 }
342 }
342 };
343 };
343
344
344 /**
345 /**
345 * Focus the cell in the DOM sense
346 * Focus the cell in the DOM sense
346 * @method focus_cell
347 * @method focus_cell
347 */
348 */
348 Cell.prototype.focus_cell = function () {
349 Cell.prototype.focus_cell = function () {
349 this.element.focus();
350 this.element.focus();
350 };
351 };
351
352
352 /**
353 /**
353 * Focus the editor area so a user can type
354 * Focus the editor area so a user can type
354 *
355 *
355 * NOTE: If codemirror is focused via a mouse click event, you don't want to
356 * NOTE: If codemirror is focused via a mouse click event, you don't want to
356 * call this because it will cause a page jump.
357 * call this because it will cause a page jump.
357 * @method focus_editor
358 * @method focus_editor
358 */
359 */
359 Cell.prototype.focus_editor = function () {
360 Cell.prototype.focus_editor = function () {
360 this.refresh();
361 this.refresh();
361 this.code_mirror.focus();
362 this.code_mirror.focus();
362 };
363 };
363
364
364 /**
365 /**
365 * Refresh codemirror instance
366 * Refresh codemirror instance
366 * @method refresh
367 * @method refresh
367 */
368 */
368 Cell.prototype.refresh = function () {
369 Cell.prototype.refresh = function () {
369 this.code_mirror.refresh();
370 this.code_mirror.refresh();
370 };
371 };
371
372
372 /**
373 /**
373 * should be overritten by subclass
374 * should be overritten by subclass
374 * @method get_text
375 * @method get_text
375 */
376 */
376 Cell.prototype.get_text = function () {
377 Cell.prototype.get_text = function () {
377 };
378 };
378
379
379 /**
380 /**
380 * should be overritten by subclass
381 * should be overritten by subclass
381 * @method set_text
382 * @method set_text
382 * @param {string} text
383 * @param {string} text
383 */
384 */
384 Cell.prototype.set_text = function (text) {
385 Cell.prototype.set_text = function (text) {
385 };
386 };
386
387
387 /**
388 /**
388 * should be overritten by subclass
389 * should be overritten by subclass
389 * serialise cell to json.
390 * serialise cell to json.
390 * @method toJSON
391 * @method toJSON
391 **/
392 **/
392 Cell.prototype.toJSON = function () {
393 Cell.prototype.toJSON = function () {
393 var data = {};
394 var data = {};
394 data.metadata = this.metadata;
395 data.metadata = this.metadata;
395 data.cell_type = this.cell_type;
396 data.cell_type = this.cell_type;
396 return data;
397 return data;
397 };
398 };
398
399
399
400
400 /**
401 /**
401 * should be overritten by subclass
402 * should be overritten by subclass
402 * @method fromJSON
403 * @method fromJSON
403 **/
404 **/
404 Cell.prototype.fromJSON = function (data) {
405 Cell.prototype.fromJSON = function (data) {
405 if (data.metadata !== undefined) {
406 if (data.metadata !== undefined) {
406 this.metadata = data.metadata;
407 this.metadata = data.metadata;
407 }
408 }
408 this.celltoolbar.rebuild();
409 this.celltoolbar.rebuild();
409 };
410 };
410
411
411
412
412 /**
413 /**
413 * can the cell be split into two cells
414 * can the cell be split into two cells
414 * @method is_splittable
415 * @method is_splittable
415 **/
416 **/
416 Cell.prototype.is_splittable = function () {
417 Cell.prototype.is_splittable = function () {
417 return true;
418 return true;
418 };
419 };
419
420
420
421
421 /**
422 /**
422 * can the cell be merged with other cells
423 * can the cell be merged with other cells
423 * @method is_mergeable
424 * @method is_mergeable
424 **/
425 **/
425 Cell.prototype.is_mergeable = function () {
426 Cell.prototype.is_mergeable = function () {
426 return true;
427 return true;
427 };
428 };
428
429
429
430
430 /**
431 /**
431 * @return {String} - the text before the cursor
432 * @return {String} - the text before the cursor
432 * @method get_pre_cursor
433 * @method get_pre_cursor
433 **/
434 **/
434 Cell.prototype.get_pre_cursor = function () {
435 Cell.prototype.get_pre_cursor = function () {
435 var cursor = this.code_mirror.getCursor();
436 var cursor = this.code_mirror.getCursor();
436 var text = this.code_mirror.getRange({line:0, ch:0}, cursor);
437 var text = this.code_mirror.getRange({line:0, ch:0}, cursor);
437 text = text.replace(/^\n+/, '').replace(/\n+$/, '');
438 text = text.replace(/^\n+/, '').replace(/\n+$/, '');
438 return text;
439 return text;
439 };
440 };
440
441
441
442
442 /**
443 /**
443 * @return {String} - the text after the cursor
444 * @return {String} - the text after the cursor
444 * @method get_post_cursor
445 * @method get_post_cursor
445 **/
446 **/
446 Cell.prototype.get_post_cursor = function () {
447 Cell.prototype.get_post_cursor = function () {
447 var cursor = this.code_mirror.getCursor();
448 var cursor = this.code_mirror.getCursor();
448 var last_line_num = this.code_mirror.lineCount()-1;
449 var last_line_num = this.code_mirror.lineCount()-1;
449 var last_line_len = this.code_mirror.getLine(last_line_num).length;
450 var last_line_len = this.code_mirror.getLine(last_line_num).length;
450 var end = {line:last_line_num, ch:last_line_len};
451 var end = {line:last_line_num, ch:last_line_len};
451 var text = this.code_mirror.getRange(cursor, end);
452 var text = this.code_mirror.getRange(cursor, end);
452 text = text.replace(/^\n+/, '').replace(/\n+$/, '');
453 text = text.replace(/^\n+/, '').replace(/\n+$/, '');
453 return text;
454 return text;
454 };
455 };
455
456
456 /**
457 /**
457 * Show/Hide CodeMirror LineNumber
458 * Show/Hide CodeMirror LineNumber
458 * @method show_line_numbers
459 * @method show_line_numbers
459 *
460 *
460 * @param value {Bool} show (true), or hide (false) the line number in CodeMirror
461 * @param value {Bool} show (true), or hide (false) the line number in CodeMirror
461 **/
462 **/
462 Cell.prototype.show_line_numbers = function (value) {
463 Cell.prototype.show_line_numbers = function (value) {
463 this.code_mirror.setOption('lineNumbers', value);
464 this.code_mirror.setOption('lineNumbers', value);
464 this.code_mirror.refresh();
465 this.code_mirror.refresh();
465 };
466 };
466
467
467 /**
468 /**
468 * Toggle CodeMirror LineNumber
469 * Toggle CodeMirror LineNumber
469 * @method toggle_line_numbers
470 * @method toggle_line_numbers
470 **/
471 **/
471 Cell.prototype.toggle_line_numbers = function () {
472 Cell.prototype.toggle_line_numbers = function () {
472 var val = this.code_mirror.getOption('lineNumbers');
473 var val = this.code_mirror.getOption('lineNumbers');
473 this.show_line_numbers(!val);
474 this.show_line_numbers(!val);
474 };
475 };
475
476
476 /**
477 /**
477 * Force codemirror highlight mode
478 * Force codemirror highlight mode
478 * @method force_highlight
479 * @method force_highlight
479 * @param {object} - CodeMirror mode
480 * @param {object} - CodeMirror mode
480 **/
481 **/
481 Cell.prototype.force_highlight = function(mode) {
482 Cell.prototype.force_highlight = function(mode) {
482 this.user_highlight = mode;
483 this.user_highlight = mode;
483 this.auto_highlight();
484 this.auto_highlight();
484 };
485 };
485
486
486 /**
487 /**
487 * Try to autodetect cell highlight mode, or use selected mode
488 * Try to autodetect cell highlight mode, or use selected mode
488 * @methods _auto_highlight
489 * @methods _auto_highlight
489 * @private
490 * @private
490 * @param {String|object|undefined} - CodeMirror mode | 'auto'
491 * @param {String|object|undefined} - CodeMirror mode | 'auto'
491 **/
492 **/
492 Cell.prototype._auto_highlight = function (modes) {
493 Cell.prototype._auto_highlight = function (modes) {
493 //Here we handle manually selected modes
494 //Here we handle manually selected modes
494 var mode;
495 var mode;
495 if( this.user_highlight !== undefined && this.user_highlight != 'auto' )
496 if( this.user_highlight !== undefined && this.user_highlight != 'auto' )
496 {
497 {
497 mode = this.user_highlight;
498 mode = this.user_highlight;
498 CodeMirror.autoLoadMode(this.code_mirror, mode);
499 CodeMirror.autoLoadMode(this.code_mirror, mode);
499 this.code_mirror.setOption('mode', mode);
500 this.code_mirror.setOption('mode', mode);
500 return;
501 return;
501 }
502 }
502 var current_mode = this.code_mirror.getOption('mode', mode);
503 var current_mode = this.code_mirror.getOption('mode', mode);
503 var first_line = this.code_mirror.getLine(0);
504 var first_line = this.code_mirror.getLine(0);
504 // loop on every pairs
505 // loop on every pairs
505 for(mode in modes) {
506 for(mode in modes) {
506 var regs = modes[mode].reg;
507 var regs = modes[mode].reg;
507 // only one key every time but regexp can't be keys...
508 // only one key every time but regexp can't be keys...
508 for(var i=0; i<regs.length; i++) {
509 for(var i=0; i<regs.length; i++) {
509 // here we handle non magic_modes
510 // here we handle non magic_modes
510 if(first_line.match(regs[i]) !== null) {
511 if(first_line.match(regs[i]) !== null) {
511 if(current_mode == mode){
512 if(current_mode == mode){
512 return;
513 return;
513 }
514 }
514 if (mode.search('magic_') !== 0) {
515 if (mode.search('magic_') !== 0) {
515 this.code_mirror.setOption('mode', mode);
516 this.code_mirror.setOption('mode', mode);
516 CodeMirror.autoLoadMode(this.code_mirror, mode);
517 CodeMirror.autoLoadMode(this.code_mirror, mode);
517 return;
518 return;
518 }
519 }
519 var open = modes[mode].open || "%%";
520 var open = modes[mode].open || "%%";
520 var close = modes[mode].close || "%%end";
521 var close = modes[mode].close || "%%end";
521 var mmode = mode;
522 var mmode = mode;
522 mode = mmode.substr(6);
523 mode = mmode.substr(6);
523 if(current_mode == mode){
524 if(current_mode == mode){
524 return;
525 return;
525 }
526 }
526 CodeMirror.autoLoadMode(this.code_mirror, mode);
527 CodeMirror.autoLoadMode(this.code_mirror, mode);
527 // create on the fly a mode that swhitch between
528 // create on the fly a mode that swhitch between
528 // plain/text and smth else otherwise `%%` is
529 // plain/text and smth else otherwise `%%` is
529 // source of some highlight issues.
530 // source of some highlight issues.
530 // we use patchedGetMode to circumvent a bug in CM
531 // we use patchedGetMode to circumvent a bug in CM
531 CodeMirror.defineMode(mmode , function(config) {
532 CodeMirror.defineMode(mmode , function(config) {
532 return CodeMirror.multiplexingMode(
533 return CodeMirror.multiplexingMode(
533 CodeMirror.patchedGetMode(config, 'text/plain'),
534 CodeMirror.patchedGetMode(config, 'text/plain'),
534 // always set someting on close
535 // always set someting on close
535 {open: open, close: close,
536 {open: open, close: close,
536 mode: CodeMirror.patchedGetMode(config, mode),
537 mode: CodeMirror.patchedGetMode(config, mode),
537 delimStyle: "delimit"
538 delimStyle: "delimit"
538 }
539 }
539 );
540 );
540 });
541 });
541 this.code_mirror.setOption('mode', mmode);
542 this.code_mirror.setOption('mode', mmode);
542 return;
543 return;
543 }
544 }
544 }
545 }
545 }
546 }
546 // fallback on default
547 // fallback on default
547 var default_mode;
548 var default_mode;
548 try {
549 try {
549 default_mode = this._options.cm_config.mode;
550 default_mode = this._options.cm_config.mode;
550 } catch(e) {
551 } catch(e) {
551 default_mode = 'text/plain';
552 default_mode = 'text/plain';
552 }
553 }
553 if( current_mode === default_mode){
554 if( current_mode === default_mode){
554 return;
555 return;
555 }
556 }
556 this.code_mirror.setOption('mode', default_mode);
557 this.code_mirror.setOption('mode', default_mode);
557 };
558 };
558
559
559 // Backwards compatability.
560 // Backwards compatability.
560 IPython.Cell = Cell;
561 IPython.Cell = Cell;
561
562
562 return {'Cell': Cell};
563 return {'Cell': Cell};
563 });
564 });
@@ -1,516 +1,519 b''
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 'notebook/js/tooltip',
8 'notebook/js/tooltip',
9 'base/js/keyboard',
9 'base/js/keyboard',
10 'notebook/js/cell',
10 'notebook/js/cell',
11 'notebook/js/outputarea',
11 'notebook/js/outputarea',
12 'notebook/js/completer',
12 'notebook/js/completer',
13 'notebook/js/celltoolbar',
13 'notebook/js/celltoolbar',
14 ], function(IPython, $, utils, tooltip, keyboard, cell, outputarea, completer, celltoolbar) {
14 ], function(IPython, $, utils, tooltip, keyboard, cell, outputarea, completer, celltoolbar) {
15 "use strict";
15 "use strict";
16 var Cell = cell.Cell;
16 var Cell = cell.Cell;
17
17
18 /* local util for codemirror */
18 /* local util for codemirror */
19 var posEq = function(a, b) {return a.line == b.line && a.ch == b.ch;};
19 var posEq = function(a, b) {return a.line == b.line && a.ch == b.ch;};
20
20
21 /**
21 /**
22 *
22 *
23 * function to delete until previous non blanking space character
23 * function to delete until previous non blanking space character
24 * or first multiple of 4 tabstop.
24 * or first multiple of 4 tabstop.
25 * @private
25 * @private
26 */
26 */
27 CodeMirror.commands.delSpaceToPrevTabStop = function(cm){
27 CodeMirror.commands.delSpaceToPrevTabStop = function(cm){
28 var from = cm.getCursor(true), to = cm.getCursor(false), sel = !posEq(from, to);
28 var from = cm.getCursor(true), to = cm.getCursor(false), sel = !posEq(from, to);
29 if (!posEq(from, to)) { cm.replaceRange("", from, to); return; }
29 if (!posEq(from, to)) { cm.replaceRange("", from, to); return; }
30 var cur = cm.getCursor(), line = cm.getLine(cur.line);
30 var cur = cm.getCursor(), line = cm.getLine(cur.line);
31 var tabsize = cm.getOption('tabSize');
31 var tabsize = cm.getOption('tabSize');
32 var chToPrevTabStop = cur.ch-(Math.ceil(cur.ch/tabsize)-1)*tabsize;
32 var chToPrevTabStop = cur.ch-(Math.ceil(cur.ch/tabsize)-1)*tabsize;
33 from = {ch:cur.ch-chToPrevTabStop,line:cur.line};
33 from = {ch:cur.ch-chToPrevTabStop,line:cur.line};
34 var select = cm.getRange(from,cur);
34 var select = cm.getRange(from,cur);
35 if( select.match(/^\ +$/) !== null){
35 if( select.match(/^\ +$/) !== null){
36 cm.replaceRange("",from,cur);
36 cm.replaceRange("",from,cur);
37 } else {
37 } else {
38 cm.deleteH(-1,"char");
38 cm.deleteH(-1,"char");
39 }
39 }
40 };
40 };
41
41
42 var keycodes = keyboard.keycodes;
42 var keycodes = keyboard.keycodes;
43
43
44 /**
44 /**
45 * A Cell conceived to write code.
45 * A Cell conceived to write code.
46 *
46 *
47 * The kernel doesn't have to be set at creation time, in that case
47 * The kernel doesn't have to be set at creation time, in that case
48 * it will be null and set_kernel has to be called later.
48 * it will be null and set_kernel has to be called later.
49 * @class CodeCell
49 * @class CodeCell
50 * @extends Cell
50 * @extends Cell
51 *
51 *
52 * @constructor
52 * @constructor
53 * @param {Object|null} kernel
53 * @param {Object|null} kernel
54 * @param {object|undefined} [options]
54 * @param {object|undefined} [options]
55 * @param [options.cm_config] {object} config to pass to CodeMirror
55 * @param [options.cm_config] {object} config to pass to CodeMirror
56 */
56 */
57 var CodeCell = function (kernel, options, events, config, keyboard_manager, notebook) {
57 var CodeCell = function (kernel, options) {
58 options = options || {};
58 this.kernel = kernel || null;
59 this.kernel = kernel || null;
59 this.notebook = notebook;
60 this.notebook = options.notebook;
60 this.collapsed = false;
61 this.collapsed = false;
61 this.tooltip = new tooltip.Tooltip(events);
62 this.events = options.events;
62 this.events = events;
63 this.tooltip = new tooltip.Tooltip(this.events);
63 this.config = config;
64 this.config = options.config;
64
65
65 // create all attributed in constructor function
66 // create all attributed in constructor function
66 // even if null for V8 VM optimisation
67 // even if null for V8 VM optimisation
67 this.input_prompt_number = null;
68 this.input_prompt_number = null;
68 this.celltoolbar = null;
69 this.celltoolbar = null;
69 this.output_area = null;
70 this.output_area = null;
70 this.last_msg_id = null;
71 this.last_msg_id = null;
71 this.completer = null;
72 this.completer = null;
72
73
73
74
74 var cm_overwrite_options = {
75 var cm_overwrite_options = {
75 onKeyEvent: $.proxy(this.handle_keyevent,this)
76 onKeyEvent: $.proxy(this.handle_keyevent,this)
76 };
77 };
77
78
78 options = this.mergeopt(CodeCell, options, {cm_config:cm_overwrite_options});
79 var config = this.mergeopt(CodeCell, this.config, {cm_config: cm_overwrite_options});
79
80 Cell.apply(this,[{
80 Cell.apply(this,[options, keyboard_manager, events]);
81 config: config,
82 keyboard_manager: options.keyboard_manager,
83 events: this.events}]);
81
84
82 // Attributes we want to override in this subclass.
85 // Attributes we want to override in this subclass.
83 this.cell_type = "code";
86 this.cell_type = "code";
84
87
85 var that = this;
88 var that = this;
86 this.element.focusout(
89 this.element.focusout(
87 function() { that.auto_highlight(); }
90 function() { that.auto_highlight(); }
88 );
91 );
89 };
92 };
90
93
91 CodeCell.options_default = {
94 CodeCell.options_default = {
92 cm_config : {
95 cm_config : {
93 extraKeys: {
96 extraKeys: {
94 "Tab" : "indentMore",
97 "Tab" : "indentMore",
95 "Shift-Tab" : "indentLess",
98 "Shift-Tab" : "indentLess",
96 "Backspace" : "delSpaceToPrevTabStop",
99 "Backspace" : "delSpaceToPrevTabStop",
97 "Cmd-/" : "toggleComment",
100 "Cmd-/" : "toggleComment",
98 "Ctrl-/" : "toggleComment"
101 "Ctrl-/" : "toggleComment"
99 },
102 },
100 mode: 'ipython',
103 mode: 'ipython',
101 theme: 'ipython',
104 theme: 'ipython',
102 matchBrackets: true,
105 matchBrackets: true,
103 // don't auto-close strings because of CodeMirror #2385
106 // don't auto-close strings because of CodeMirror #2385
104 autoCloseBrackets: "()[]{}"
107 autoCloseBrackets: "()[]{}"
105 }
108 }
106 };
109 };
107
110
108 CodeCell.msg_cells = {};
111 CodeCell.msg_cells = {};
109
112
110 CodeCell.prototype = new Cell();
113 CodeCell.prototype = new Cell();
111
114
112 /**
115 /**
113 * @method auto_highlight
116 * @method auto_highlight
114 */
117 */
115 CodeCell.prototype.auto_highlight = function () {
118 CodeCell.prototype.auto_highlight = function () {
116 this._auto_highlight(this.config.cell_magic_highlight);
119 this._auto_highlight(this.config.cell_magic_highlight);
117 };
120 };
118
121
119 /** @method create_element */
122 /** @method create_element */
120 CodeCell.prototype.create_element = function () {
123 CodeCell.prototype.create_element = function () {
121 Cell.prototype.create_element.apply(this, arguments);
124 Cell.prototype.create_element.apply(this, arguments);
122
125
123 var cell = $('<div></div>').addClass('cell border-box-sizing code_cell');
126 var cell = $('<div></div>').addClass('cell border-box-sizing code_cell');
124 cell.attr('tabindex','2');
127 cell.attr('tabindex','2');
125
128
126 var input = $('<div></div>').addClass('input');
129 var input = $('<div></div>').addClass('input');
127 var prompt = $('<div/>').addClass('prompt input_prompt');
130 var prompt = $('<div/>').addClass('prompt input_prompt');
128 var inner_cell = $('<div/>').addClass('inner_cell');
131 var inner_cell = $('<div/>').addClass('inner_cell');
129 this.celltoolbar = new celltoolbar.CellToolbar(this, this.events, this.notebook);
132 this.celltoolbar = new celltoolbar.CellToolbar(this, this.events, this.notebook);
130 inner_cell.append(this.celltoolbar.element);
133 inner_cell.append(this.celltoolbar.element);
131 var input_area = $('<div/>').addClass('input_area');
134 var input_area = $('<div/>').addClass('input_area');
132 this.code_mirror = CodeMirror(input_area.get(0), this.cm_config);
135 this.code_mirror = CodeMirror(input_area.get(0), this.cm_config);
133 $(this.code_mirror.getInputField()).attr("spellcheck", "false");
136 $(this.code_mirror.getInputField()).attr("spellcheck", "false");
134 inner_cell.append(input_area);
137 inner_cell.append(input_area);
135 input.append(prompt).append(inner_cell);
138 input.append(prompt).append(inner_cell);
136
139
137 var widget_area = $('<div/>')
140 var widget_area = $('<div/>')
138 .addClass('widget-area')
141 .addClass('widget-area')
139 .hide();
142 .hide();
140 this.widget_area = widget_area;
143 this.widget_area = widget_area;
141 var widget_prompt = $('<div/>')
144 var widget_prompt = $('<div/>')
142 .addClass('prompt')
145 .addClass('prompt')
143 .appendTo(widget_area);
146 .appendTo(widget_area);
144 var widget_subarea = $('<div/>')
147 var widget_subarea = $('<div/>')
145 .addClass('widget-subarea')
148 .addClass('widget-subarea')
146 .appendTo(widget_area);
149 .appendTo(widget_area);
147 this.widget_subarea = widget_subarea;
150 this.widget_subarea = widget_subarea;
148 var widget_clear_buton = $('<button />')
151 var widget_clear_buton = $('<button />')
149 .addClass('close')
152 .addClass('close')
150 .html('&times;')
153 .html('&times;')
151 .click(function() {
154 .click(function() {
152 widget_area.slideUp('', function(){ widget_subarea.html(''); });
155 widget_area.slideUp('', function(){ widget_subarea.html(''); });
153 })
156 })
154 .appendTo(widget_prompt);
157 .appendTo(widget_prompt);
155
158
156 var output = $('<div></div>');
159 var output = $('<div></div>');
157 cell.append(input).append(widget_area).append(output);
160 cell.append(input).append(widget_area).append(output);
158 this.element = cell;
161 this.element = cell;
159 this.output_area = new outputarea.OutputArea(output, true, this.events, this.keyboard_manager);
162 this.output_area = new outputarea.OutputArea(output, true, this.events, this.keyboard_manager);
160 this.completer = new completer.Completer(this, this.events);
163 this.completer = new completer.Completer(this, this.events);
161 };
164 };
162
165
163 /** @method bind_events */
166 /** @method bind_events */
164 CodeCell.prototype.bind_events = function () {
167 CodeCell.prototype.bind_events = function () {
165 Cell.prototype.bind_events.apply(this);
168 Cell.prototype.bind_events.apply(this);
166 var that = this;
169 var that = this;
167
170
168 this.element.focusout(
171 this.element.focusout(
169 function() { that.auto_highlight(); }
172 function() { that.auto_highlight(); }
170 );
173 );
171 };
174 };
172
175
173
176
174 /**
177 /**
175 * This method gets called in CodeMirror's onKeyDown/onKeyPress
178 * This method gets called in CodeMirror's onKeyDown/onKeyPress
176 * handlers and is used to provide custom key handling. Its return
179 * handlers and is used to provide custom key handling. Its return
177 * value is used to determine if CodeMirror should ignore the event:
180 * value is used to determine if CodeMirror should ignore the event:
178 * true = ignore, false = don't ignore.
181 * true = ignore, false = don't ignore.
179 * @method handle_codemirror_keyevent
182 * @method handle_codemirror_keyevent
180 */
183 */
181 CodeCell.prototype.handle_codemirror_keyevent = function (editor, event) {
184 CodeCell.prototype.handle_codemirror_keyevent = function (editor, event) {
182
185
183 var that = this;
186 var that = this;
184 // whatever key is pressed, first, cancel the tooltip request before
187 // whatever key is pressed, first, cancel the tooltip request before
185 // they are sent, and remove tooltip if any, except for tab again
188 // they are sent, and remove tooltip if any, except for tab again
186 var tooltip_closed = null;
189 var tooltip_closed = null;
187 if (event.type === 'keydown' && event.which != keycodes.tab ) {
190 if (event.type === 'keydown' && event.which != keycodes.tab ) {
188 tooltip_closed = this.tooltip.remove_and_cancel_tooltip();
191 tooltip_closed = this.tooltip.remove_and_cancel_tooltip();
189 }
192 }
190
193
191 var cur = editor.getCursor();
194 var cur = editor.getCursor();
192 if (event.keyCode === keycodes.enter){
195 if (event.keyCode === keycodes.enter){
193 this.auto_highlight();
196 this.auto_highlight();
194 }
197 }
195
198
196 if (event.which === keycodes.down && event.type === 'keypress' && this.tooltip.time_before_tooltip >= 0) {
199 if (event.which === keycodes.down && event.type === 'keypress' && this.tooltip.time_before_tooltip >= 0) {
197 // triger on keypress (!) otherwise inconsistent event.which depending on plateform
200 // triger on keypress (!) otherwise inconsistent event.which depending on plateform
198 // browser and keyboard layout !
201 // browser and keyboard layout !
199 // Pressing '(' , request tooltip, don't forget to reappend it
202 // Pressing '(' , request tooltip, don't forget to reappend it
200 // The second argument says to hide the tooltip if the docstring
203 // The second argument says to hide the tooltip if the docstring
201 // is actually empty
204 // is actually empty
202 this.tooltip.pending(that, true);
205 this.tooltip.pending(that, true);
203 } else if ( tooltip_closed && event.which === keycodes.esc && event.type === 'keydown') {
206 } else if ( tooltip_closed && event.which === keycodes.esc && event.type === 'keydown') {
204 // If tooltip is active, cancel it. The call to
207 // If tooltip is active, cancel it. The call to
205 // remove_and_cancel_tooltip above doesn't pass, force=true.
208 // remove_and_cancel_tooltip above doesn't pass, force=true.
206 // Because of this it won't actually close the tooltip
209 // Because of this it won't actually close the tooltip
207 // if it is in sticky mode. Thus, we have to check again if it is open
210 // if it is in sticky mode. Thus, we have to check again if it is open
208 // and close it with force=true.
211 // and close it with force=true.
209 if (!this.tooltip._hidden) {
212 if (!this.tooltip._hidden) {
210 this.tooltip.remove_and_cancel_tooltip(true);
213 this.tooltip.remove_and_cancel_tooltip(true);
211 }
214 }
212 // If we closed the tooltip, don't let CM or the global handlers
215 // If we closed the tooltip, don't let CM or the global handlers
213 // handle this event.
216 // handle this event.
214 event.stop();
217 event.stop();
215 return true;
218 return true;
216 } else if (event.keyCode === keycodes.tab && event.type === 'keydown' && event.shiftKey) {
219 } else if (event.keyCode === keycodes.tab && event.type === 'keydown' && event.shiftKey) {
217 if (editor.somethingSelected()){
220 if (editor.somethingSelected()){
218 var anchor = editor.getCursor("anchor");
221 var anchor = editor.getCursor("anchor");
219 var head = editor.getCursor("head");
222 var head = editor.getCursor("head");
220 if( anchor.line != head.line){
223 if( anchor.line != head.line){
221 return false;
224 return false;
222 }
225 }
223 }
226 }
224 this.tooltip.request(that);
227 this.tooltip.request(that);
225 event.stop();
228 event.stop();
226 return true;
229 return true;
227 } else if (event.keyCode === keycodes.tab && event.type == 'keydown') {
230 } else if (event.keyCode === keycodes.tab && event.type == 'keydown') {
228 // Tab completion.
231 // Tab completion.
229 this.tooltip.remove_and_cancel_tooltip();
232 this.tooltip.remove_and_cancel_tooltip();
230 if (editor.somethingSelected()) {
233 if (editor.somethingSelected()) {
231 return false;
234 return false;
232 }
235 }
233 var pre_cursor = editor.getRange({line:cur.line,ch:0},cur);
236 var pre_cursor = editor.getRange({line:cur.line,ch:0},cur);
234 if (pre_cursor.trim() === "") {
237 if (pre_cursor.trim() === "") {
235 // Don't autocomplete if the part of the line before the cursor
238 // Don't autocomplete if the part of the line before the cursor
236 // is empty. In this case, let CodeMirror handle indentation.
239 // is empty. In this case, let CodeMirror handle indentation.
237 return false;
240 return false;
238 } else {
241 } else {
239 event.stop();
242 event.stop();
240 this.completer.startCompletion();
243 this.completer.startCompletion();
241 return true;
244 return true;
242 }
245 }
243 }
246 }
244
247
245 // keyboard event wasn't one of those unique to code cells, let's see
248 // keyboard event wasn't one of those unique to code cells, let's see
246 // if it's one of the generic ones (i.e. check edit mode shortcuts)
249 // if it's one of the generic ones (i.e. check edit mode shortcuts)
247 return Cell.prototype.handle_codemirror_keyevent.apply(this, [editor, event]);
250 return Cell.prototype.handle_codemirror_keyevent.apply(this, [editor, event]);
248 };
251 };
249
252
250 // Kernel related calls.
253 // Kernel related calls.
251
254
252 CodeCell.prototype.set_kernel = function (kernel) {
255 CodeCell.prototype.set_kernel = function (kernel) {
253 this.kernel = kernel;
256 this.kernel = kernel;
254 };
257 };
255
258
256 /**
259 /**
257 * Execute current code cell to the kernel
260 * Execute current code cell to the kernel
258 * @method execute
261 * @method execute
259 */
262 */
260 CodeCell.prototype.execute = function () {
263 CodeCell.prototype.execute = function () {
261 this.output_area.clear_output();
264 this.output_area.clear_output();
262
265
263 // Clear widget area
266 // Clear widget area
264 this.widget_subarea.html('');
267 this.widget_subarea.html('');
265 this.widget_subarea.height('');
268 this.widget_subarea.height('');
266 this.widget_area.height('');
269 this.widget_area.height('');
267 this.widget_area.hide();
270 this.widget_area.hide();
268
271
269 this.set_input_prompt('*');
272 this.set_input_prompt('*');
270 this.element.addClass("running");
273 this.element.addClass("running");
271 if (this.last_msg_id) {
274 if (this.last_msg_id) {
272 this.kernel.clear_callbacks_for_msg(this.last_msg_id);
275 this.kernel.clear_callbacks_for_msg(this.last_msg_id);
273 }
276 }
274 var callbacks = this.get_callbacks();
277 var callbacks = this.get_callbacks();
275
278
276 var old_msg_id = this.last_msg_id;
279 var old_msg_id = this.last_msg_id;
277 this.last_msg_id = this.kernel.execute(this.get_text(), callbacks, {silent: false, store_history: true});
280 this.last_msg_id = this.kernel.execute(this.get_text(), callbacks, {silent: false, store_history: true});
278 if (old_msg_id) {
281 if (old_msg_id) {
279 delete CodeCell.msg_cells[old_msg_id];
282 delete CodeCell.msg_cells[old_msg_id];
280 }
283 }
281 CodeCell.msg_cells[this.last_msg_id] = this;
284 CodeCell.msg_cells[this.last_msg_id] = this;
282 };
285 };
283
286
284 /**
287 /**
285 * Construct the default callbacks for
288 * Construct the default callbacks for
286 * @method get_callbacks
289 * @method get_callbacks
287 */
290 */
288 CodeCell.prototype.get_callbacks = function () {
291 CodeCell.prototype.get_callbacks = function () {
289 return {
292 return {
290 shell : {
293 shell : {
291 reply : $.proxy(this._handle_execute_reply, this),
294 reply : $.proxy(this._handle_execute_reply, this),
292 payload : {
295 payload : {
293 set_next_input : $.proxy(this._handle_set_next_input, this),
296 set_next_input : $.proxy(this._handle_set_next_input, this),
294 page : $.proxy(this._open_with_pager, this)
297 page : $.proxy(this._open_with_pager, this)
295 }
298 }
296 },
299 },
297 iopub : {
300 iopub : {
298 output : $.proxy(this.output_area.handle_output, this.output_area),
301 output : $.proxy(this.output_area.handle_output, this.output_area),
299 clear_output : $.proxy(this.output_area.handle_clear_output, this.output_area),
302 clear_output : $.proxy(this.output_area.handle_clear_output, this.output_area),
300 },
303 },
301 input : $.proxy(this._handle_input_request, this)
304 input : $.proxy(this._handle_input_request, this)
302 };
305 };
303 };
306 };
304
307
305 CodeCell.prototype._open_with_pager = function (payload) {
308 CodeCell.prototype._open_with_pager = function (payload) {
306 this.events.trigger('open_with_text.Pager', payload);
309 this.events.trigger('open_with_text.Pager', payload);
307 };
310 };
308
311
309 /**
312 /**
310 * @method _handle_execute_reply
313 * @method _handle_execute_reply
311 * @private
314 * @private
312 */
315 */
313 CodeCell.prototype._handle_execute_reply = function (msg) {
316 CodeCell.prototype._handle_execute_reply = function (msg) {
314 this.set_input_prompt(msg.content.execution_count);
317 this.set_input_prompt(msg.content.execution_count);
315 this.element.removeClass("running");
318 this.element.removeClass("running");
316 this.events.trigger('set_dirty.Notebook', {value: true});
319 this.events.trigger('set_dirty.Notebook', {value: true});
317 };
320 };
318
321
319 /**
322 /**
320 * @method _handle_set_next_input
323 * @method _handle_set_next_input
321 * @private
324 * @private
322 */
325 */
323 CodeCell.prototype._handle_set_next_input = function (payload) {
326 CodeCell.prototype._handle_set_next_input = function (payload) {
324 var data = {'cell': this, 'text': payload.text};
327 var data = {'cell': this, 'text': payload.text};
325 this.events.trigger('set_next_input.Notebook', data);
328 this.events.trigger('set_next_input.Notebook', data);
326 };
329 };
327
330
328 /**
331 /**
329 * @method _handle_input_request
332 * @method _handle_input_request
330 * @private
333 * @private
331 */
334 */
332 CodeCell.prototype._handle_input_request = function (msg) {
335 CodeCell.prototype._handle_input_request = function (msg) {
333 this.output_area.append_raw_input(msg);
336 this.output_area.append_raw_input(msg);
334 };
337 };
335
338
336
339
337 // Basic cell manipulation.
340 // Basic cell manipulation.
338
341
339 CodeCell.prototype.select = function () {
342 CodeCell.prototype.select = function () {
340 var cont = Cell.prototype.select.apply(this);
343 var cont = Cell.prototype.select.apply(this);
341 if (cont) {
344 if (cont) {
342 this.code_mirror.refresh();
345 this.code_mirror.refresh();
343 this.auto_highlight();
346 this.auto_highlight();
344 }
347 }
345 return cont;
348 return cont;
346 };
349 };
347
350
348 CodeCell.prototype.render = function () {
351 CodeCell.prototype.render = function () {
349 var cont = Cell.prototype.render.apply(this);
352 var cont = Cell.prototype.render.apply(this);
350 // Always execute, even if we are already in the rendered state
353 // Always execute, even if we are already in the rendered state
351 return cont;
354 return cont;
352 };
355 };
353
356
354 CodeCell.prototype.unrender = function () {
357 CodeCell.prototype.unrender = function () {
355 // CodeCell is always rendered
358 // CodeCell is always rendered
356 return false;
359 return false;
357 };
360 };
358
361
359 CodeCell.prototype.select_all = function () {
362 CodeCell.prototype.select_all = function () {
360 var start = {line: 0, ch: 0};
363 var start = {line: 0, ch: 0};
361 var nlines = this.code_mirror.lineCount();
364 var nlines = this.code_mirror.lineCount();
362 var last_line = this.code_mirror.getLine(nlines-1);
365 var last_line = this.code_mirror.getLine(nlines-1);
363 var end = {line: nlines-1, ch: last_line.length};
366 var end = {line: nlines-1, ch: last_line.length};
364 this.code_mirror.setSelection(start, end);
367 this.code_mirror.setSelection(start, end);
365 };
368 };
366
369
367
370
368 CodeCell.prototype.collapse_output = function () {
371 CodeCell.prototype.collapse_output = function () {
369 this.collapsed = true;
372 this.collapsed = true;
370 this.output_area.collapse();
373 this.output_area.collapse();
371 };
374 };
372
375
373
376
374 CodeCell.prototype.expand_output = function () {
377 CodeCell.prototype.expand_output = function () {
375 this.collapsed = false;
378 this.collapsed = false;
376 this.output_area.expand();
379 this.output_area.expand();
377 this.output_area.unscroll_area();
380 this.output_area.unscroll_area();
378 };
381 };
379
382
380 CodeCell.prototype.scroll_output = function () {
383 CodeCell.prototype.scroll_output = function () {
381 this.output_area.expand();
384 this.output_area.expand();
382 this.output_area.scroll_if_long();
385 this.output_area.scroll_if_long();
383 };
386 };
384
387
385 CodeCell.prototype.toggle_output = function () {
388 CodeCell.prototype.toggle_output = function () {
386 this.collapsed = Boolean(1 - this.collapsed);
389 this.collapsed = Boolean(1 - this.collapsed);
387 this.output_area.toggle_output();
390 this.output_area.toggle_output();
388 };
391 };
389
392
390 CodeCell.prototype.toggle_output_scroll = function () {
393 CodeCell.prototype.toggle_output_scroll = function () {
391 this.output_area.toggle_scroll();
394 this.output_area.toggle_scroll();
392 };
395 };
393
396
394
397
395 CodeCell.input_prompt_classical = function (prompt_value, lines_number) {
398 CodeCell.input_prompt_classical = function (prompt_value, lines_number) {
396 var ns;
399 var ns;
397 if (prompt_value === undefined) {
400 if (prompt_value === undefined) {
398 ns = "&nbsp;";
401 ns = "&nbsp;";
399 } else {
402 } else {
400 ns = encodeURIComponent(prompt_value);
403 ns = encodeURIComponent(prompt_value);
401 }
404 }
402 return 'In&nbsp;[' + ns + ']:';
405 return 'In&nbsp;[' + ns + ']:';
403 };
406 };
404
407
405 CodeCell.input_prompt_continuation = function (prompt_value, lines_number) {
408 CodeCell.input_prompt_continuation = function (prompt_value, lines_number) {
406 var html = [CodeCell.input_prompt_classical(prompt_value, lines_number)];
409 var html = [CodeCell.input_prompt_classical(prompt_value, lines_number)];
407 for(var i=1; i < lines_number; i++) {
410 for(var i=1; i < lines_number; i++) {
408 html.push(['...:']);
411 html.push(['...:']);
409 }
412 }
410 return html.join('<br/>');
413 return html.join('<br/>');
411 };
414 };
412
415
413 CodeCell.input_prompt_function = CodeCell.input_prompt_classical;
416 CodeCell.input_prompt_function = CodeCell.input_prompt_classical;
414
417
415
418
416 CodeCell.prototype.set_input_prompt = function (number) {
419 CodeCell.prototype.set_input_prompt = function (number) {
417 var nline = 1;
420 var nline = 1;
418 if (this.code_mirror !== undefined) {
421 if (this.code_mirror !== undefined) {
419 nline = this.code_mirror.lineCount();
422 nline = this.code_mirror.lineCount();
420 }
423 }
421 this.input_prompt_number = number;
424 this.input_prompt_number = number;
422 var prompt_html = CodeCell.input_prompt_function(this.input_prompt_number, nline);
425 var prompt_html = CodeCell.input_prompt_function(this.input_prompt_number, nline);
423 // This HTML call is okay because the user contents are escaped.
426 // This HTML call is okay because the user contents are escaped.
424 this.element.find('div.input_prompt').html(prompt_html);
427 this.element.find('div.input_prompt').html(prompt_html);
425 };
428 };
426
429
427
430
428 CodeCell.prototype.clear_input = function () {
431 CodeCell.prototype.clear_input = function () {
429 this.code_mirror.setValue('');
432 this.code_mirror.setValue('');
430 };
433 };
431
434
432
435
433 CodeCell.prototype.get_text = function () {
436 CodeCell.prototype.get_text = function () {
434 return this.code_mirror.getValue();
437 return this.code_mirror.getValue();
435 };
438 };
436
439
437
440
438 CodeCell.prototype.set_text = function (code) {
441 CodeCell.prototype.set_text = function (code) {
439 return this.code_mirror.setValue(code);
442 return this.code_mirror.setValue(code);
440 };
443 };
441
444
442
445
443 CodeCell.prototype.clear_output = function (wait) {
446 CodeCell.prototype.clear_output = function (wait) {
444 this.output_area.clear_output(wait);
447 this.output_area.clear_output(wait);
445 this.set_input_prompt();
448 this.set_input_prompt();
446 };
449 };
447
450
448
451
449 // JSON serialization
452 // JSON serialization
450
453
451 CodeCell.prototype.fromJSON = function (data) {
454 CodeCell.prototype.fromJSON = function (data) {
452 Cell.prototype.fromJSON.apply(this, arguments);
455 Cell.prototype.fromJSON.apply(this, arguments);
453 if (data.cell_type === 'code') {
456 if (data.cell_type === 'code') {
454 if (data.input !== undefined) {
457 if (data.input !== undefined) {
455 this.set_text(data.input);
458 this.set_text(data.input);
456 // make this value the starting point, so that we can only undo
459 // make this value the starting point, so that we can only undo
457 // to this state, instead of a blank cell
460 // to this state, instead of a blank cell
458 this.code_mirror.clearHistory();
461 this.code_mirror.clearHistory();
459 this.auto_highlight();
462 this.auto_highlight();
460 }
463 }
461 if (data.prompt_number !== undefined) {
464 if (data.prompt_number !== undefined) {
462 this.set_input_prompt(data.prompt_number);
465 this.set_input_prompt(data.prompt_number);
463 } else {
466 } else {
464 this.set_input_prompt();
467 this.set_input_prompt();
465 }
468 }
466 this.output_area.trusted = data.trusted || false;
469 this.output_area.trusted = data.trusted || false;
467 this.output_area.fromJSON(data.outputs);
470 this.output_area.fromJSON(data.outputs);
468 if (data.collapsed !== undefined) {
471 if (data.collapsed !== undefined) {
469 if (data.collapsed) {
472 if (data.collapsed) {
470 this.collapse_output();
473 this.collapse_output();
471 } else {
474 } else {
472 this.expand_output();
475 this.expand_output();
473 }
476 }
474 }
477 }
475 }
478 }
476 };
479 };
477
480
478
481
479 CodeCell.prototype.toJSON = function () {
482 CodeCell.prototype.toJSON = function () {
480 var data = Cell.prototype.toJSON.apply(this);
483 var data = Cell.prototype.toJSON.apply(this);
481 data.input = this.get_text();
484 data.input = this.get_text();
482 // is finite protect against undefined and '*' value
485 // is finite protect against undefined and '*' value
483 if (isFinite(this.input_prompt_number)) {
486 if (isFinite(this.input_prompt_number)) {
484 data.prompt_number = this.input_prompt_number;
487 data.prompt_number = this.input_prompt_number;
485 }
488 }
486 var outputs = this.output_area.toJSON();
489 var outputs = this.output_area.toJSON();
487 data.outputs = outputs;
490 data.outputs = outputs;
488 data.language = 'python';
491 data.language = 'python';
489 data.trusted = this.output_area.trusted;
492 data.trusted = this.output_area.trusted;
490 data.collapsed = this.collapsed;
493 data.collapsed = this.collapsed;
491 return data;
494 return data;
492 };
495 };
493
496
494 /**
497 /**
495 * handle cell level logic when a cell is unselected
498 * handle cell level logic when a cell is unselected
496 * @method unselect
499 * @method unselect
497 * @return is the action being taken
500 * @return is the action being taken
498 */
501 */
499 CodeCell.prototype.unselect = function () {
502 CodeCell.prototype.unselect = function () {
500 var cont = Cell.prototype.unselect.apply(this);
503 var cont = Cell.prototype.unselect.apply(this);
501 if (cont) {
504 if (cont) {
502 // When a code cell is usnelected, make sure that the corresponding
505 // When a code cell is usnelected, make sure that the corresponding
503 // tooltip and completer to that cell is closed.
506 // tooltip and completer to that cell is closed.
504 this.tooltip.remove_and_cancel_tooltip(true);
507 this.tooltip.remove_and_cancel_tooltip(true);
505 if (this.completer !== null) {
508 if (this.completer !== null) {
506 this.completer.close();
509 this.completer.close();
507 }
510 }
508 }
511 }
509 return cont;
512 return cont;
510 };
513 };
511
514
512 // Backwards compatability.
515 // Backwards compatability.
513 IPython.CodeCell = CodeCell;
516 IPython.CodeCell = CodeCell;
514
517
515 return {'CodeCell': CodeCell};
518 return {'CodeCell': CodeCell};
516 });
519 });
@@ -1,559 +1,559 b''
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/keyboard',
8 'base/js/keyboard',
9 ], function(IPython, $, utils, keyboard) {
9 ], function(IPython, $, utils, keyboard) {
10 "use strict";
10 "use strict";
11
11
12 var browser = utils.browser[0];
12 var browser = utils.browser[0];
13 var platform = utils.platform;
13 var platform = utils.platform;
14
14
15 // Main keyboard manager for the notebook
15 // Main keyboard manager for the notebook
16 var keycodes = keyboard.keycodes;
16 var keycodes = keyboard.keycodes;
17
17
18 var KeyboardManager = function (pager, events) {
18 var KeyboardManager = function (options) {
19 this.mode = 'command';
19 this.mode = 'command';
20 this.enabled = true;
20 this.enabled = true;
21 this.pager = pager;
21 this.pager = options.pager;
22 this.quick_help = undefined;
22 this.quick_help = undefined;
23 this.notebook = undefined;
23 this.notebook = undefined;
24 this.bind_events();
24 this.bind_events();
25 this.command_shortcuts = new keyboard.ShortcutManager(undefined, events);
25 this.command_shortcuts = new keyboard.ShortcutManager(undefined, options.events);
26 this.command_shortcuts.add_shortcuts(this.get_default_common_shortcuts());
26 this.command_shortcuts.add_shortcuts(this.get_default_common_shortcuts());
27 this.command_shortcuts.add_shortcuts(this.get_default_command_shortcuts());
27 this.command_shortcuts.add_shortcuts(this.get_default_command_shortcuts());
28 this.edit_shortcuts = new keyboard.ShortcutManager(undefined, events);
28 this.edit_shortcuts = new keyboard.ShortcutManager(undefined, options.events);
29 this.edit_shortcuts.add_shortcuts(this.get_default_common_shortcuts());
29 this.edit_shortcuts.add_shortcuts(this.get_default_common_shortcuts());
30 this.edit_shortcuts.add_shortcuts(this.get_default_edit_shortcuts());
30 this.edit_shortcuts.add_shortcuts(this.get_default_edit_shortcuts());
31 };
31 };
32
32
33 KeyboardManager.prototype.get_default_common_shortcuts = function() {
33 KeyboardManager.prototype.get_default_common_shortcuts = function() {
34 var that = this;
34 var that = this;
35 var shortcuts = {
35 var shortcuts = {
36 'shift' : {
36 'shift' : {
37 help : '',
37 help : '',
38 help_index : '',
38 help_index : '',
39 handler : function (event) {
39 handler : function (event) {
40 // ignore shift keydown
40 // ignore shift keydown
41 return true;
41 return true;
42 }
42 }
43 },
43 },
44 'shift-enter' : {
44 'shift-enter' : {
45 help : 'run cell, select below',
45 help : 'run cell, select below',
46 help_index : 'ba',
46 help_index : 'ba',
47 handler : function (event) {
47 handler : function (event) {
48 that.notebook.execute_cell_and_select_below();
48 that.notebook.execute_cell_and_select_below();
49 return false;
49 return false;
50 }
50 }
51 },
51 },
52 'ctrl-enter' : {
52 'ctrl-enter' : {
53 help : 'run cell',
53 help : 'run cell',
54 help_index : 'bb',
54 help_index : 'bb',
55 handler : function (event) {
55 handler : function (event) {
56 that.notebook.execute_cell();
56 that.notebook.execute_cell();
57 return false;
57 return false;
58 }
58 }
59 },
59 },
60 'alt-enter' : {
60 'alt-enter' : {
61 help : 'run cell, insert below',
61 help : 'run cell, insert below',
62 help_index : 'bc',
62 help_index : 'bc',
63 handler : function (event) {
63 handler : function (event) {
64 that.notebook.execute_cell_and_insert_below();
64 that.notebook.execute_cell_and_insert_below();
65 return false;
65 return false;
66 }
66 }
67 }
67 }
68 };
68 };
69
69
70 if (platform === 'MacOS') {
70 if (platform === 'MacOS') {
71 shortcuts['cmd-s'] =
71 shortcuts['cmd-s'] =
72 {
72 {
73 help : 'save notebook',
73 help : 'save notebook',
74 help_index : 'fb',
74 help_index : 'fb',
75 handler : function (event) {
75 handler : function (event) {
76 that.notebook.save_checkpoint();
76 that.notebook.save_checkpoint();
77 event.preventDefault();
77 event.preventDefault();
78 return false;
78 return false;
79 }
79 }
80 };
80 };
81 } else {
81 } else {
82 shortcuts['ctrl-s'] =
82 shortcuts['ctrl-s'] =
83 {
83 {
84 help : 'save notebook',
84 help : 'save notebook',
85 help_index : 'fb',
85 help_index : 'fb',
86 handler : function (event) {
86 handler : function (event) {
87 that.notebook.save_checkpoint();
87 that.notebook.save_checkpoint();
88 event.preventDefault();
88 event.preventDefault();
89 return false;
89 return false;
90 }
90 }
91 };
91 };
92 }
92 }
93 return shortcuts;
93 return shortcuts;
94 };
94 };
95
95
96 KeyboardManager.prototype.get_default_edit_shortcuts = function() {
96 KeyboardManager.prototype.get_default_edit_shortcuts = function() {
97 var that = this;
97 var that = this;
98 return {
98 return {
99 'esc' : {
99 'esc' : {
100 help : 'command mode',
100 help : 'command mode',
101 help_index : 'aa',
101 help_index : 'aa',
102 handler : function (event) {
102 handler : function (event) {
103 that.notebook.command_mode();
103 that.notebook.command_mode();
104 return false;
104 return false;
105 }
105 }
106 },
106 },
107 'ctrl-m' : {
107 'ctrl-m' : {
108 help : 'command mode',
108 help : 'command mode',
109 help_index : 'ab',
109 help_index : 'ab',
110 handler : function (event) {
110 handler : function (event) {
111 that.notebook.command_mode();
111 that.notebook.command_mode();
112 return false;
112 return false;
113 }
113 }
114 },
114 },
115 'up' : {
115 'up' : {
116 help : '',
116 help : '',
117 help_index : '',
117 help_index : '',
118 handler : function (event) {
118 handler : function (event) {
119 var index = that.notebook.get_selected_index();
119 var index = that.notebook.get_selected_index();
120 var cell = that.notebook.get_cell(index);
120 var cell = that.notebook.get_cell(index);
121 if (cell && cell.at_top() && index !== 0) {
121 if (cell && cell.at_top() && index !== 0) {
122 event.preventDefault();
122 event.preventDefault();
123 that.notebook.command_mode();
123 that.notebook.command_mode();
124 that.notebook.select_prev();
124 that.notebook.select_prev();
125 that.notebook.edit_mode();
125 that.notebook.edit_mode();
126 var cm = that.notebook.get_selected_cell().code_mirror;
126 var cm = that.notebook.get_selected_cell().code_mirror;
127 cm.setCursor(cm.lastLine(), 0);
127 cm.setCursor(cm.lastLine(), 0);
128 return false;
128 return false;
129 } else if (cell) {
129 } else if (cell) {
130 var cm = cell.code_mirror;
130 var cm = cell.code_mirror;
131 cm.execCommand('goLineUp');
131 cm.execCommand('goLineUp');
132 return false;
132 return false;
133 }
133 }
134 }
134 }
135 },
135 },
136 'down' : {
136 'down' : {
137 help : '',
137 help : '',
138 help_index : '',
138 help_index : '',
139 handler : function (event) {
139 handler : function (event) {
140 var index = that.notebook.get_selected_index();
140 var index = that.notebook.get_selected_index();
141 var cell = that.notebook.get_cell(index);
141 var cell = that.notebook.get_cell(index);
142 if (cell.at_bottom() && index !== (that.notebook.ncells()-1)) {
142 if (cell.at_bottom() && index !== (that.notebook.ncells()-1)) {
143 event.preventDefault();
143 event.preventDefault();
144 that.notebook.command_mode();
144 that.notebook.command_mode();
145 that.notebook.select_next();
145 that.notebook.select_next();
146 that.notebook.edit_mode();
146 that.notebook.edit_mode();
147 var cm = that.notebook.get_selected_cell().code_mirror;
147 var cm = that.notebook.get_selected_cell().code_mirror;
148 cm.setCursor(0, 0);
148 cm.setCursor(0, 0);
149 return false;
149 return false;
150 } else {
150 } else {
151 var cm = cell.code_mirror;
151 var cm = cell.code_mirror;
152 cm.execCommand('goLineDown');
152 cm.execCommand('goLineDown');
153 return false;
153 return false;
154 }
154 }
155 }
155 }
156 },
156 },
157 'ctrl-shift--' : {
157 'ctrl-shift--' : {
158 help : 'split cell',
158 help : 'split cell',
159 help_index : 'ea',
159 help_index : 'ea',
160 handler : function (event) {
160 handler : function (event) {
161 that.notebook.split_cell();
161 that.notebook.split_cell();
162 return false;
162 return false;
163 }
163 }
164 },
164 },
165 'ctrl-shift-subtract' : {
165 'ctrl-shift-subtract' : {
166 help : '',
166 help : '',
167 help_index : 'eb',
167 help_index : 'eb',
168 handler : function (event) {
168 handler : function (event) {
169 that.notebook.split_cell();
169 that.notebook.split_cell();
170 return false;
170 return false;
171 }
171 }
172 },
172 },
173 };
173 };
174 };
174 };
175
175
176 KeyboardManager.prototype.get_default_command_shortcuts = function() {
176 KeyboardManager.prototype.get_default_command_shortcuts = function() {
177 var that = this;
177 var that = this;
178 return {
178 return {
179 'enter' : {
179 'enter' : {
180 help : 'edit mode',
180 help : 'edit mode',
181 help_index : 'aa',
181 help_index : 'aa',
182 handler : function (event) {
182 handler : function (event) {
183 that.notebook.edit_mode();
183 that.notebook.edit_mode();
184 return false;
184 return false;
185 }
185 }
186 },
186 },
187 'up' : {
187 'up' : {
188 help : 'select previous cell',
188 help : 'select previous cell',
189 help_index : 'da',
189 help_index : 'da',
190 handler : function (event) {
190 handler : function (event) {
191 var index = that.notebook.get_selected_index();
191 var index = that.notebook.get_selected_index();
192 if (index !== 0 && index !== null) {
192 if (index !== 0 && index !== null) {
193 that.notebook.select_prev();
193 that.notebook.select_prev();
194 that.notebook.focus_cell();
194 that.notebook.focus_cell();
195 }
195 }
196 return false;
196 return false;
197 }
197 }
198 },
198 },
199 'down' : {
199 'down' : {
200 help : 'select next cell',
200 help : 'select next cell',
201 help_index : 'db',
201 help_index : 'db',
202 handler : function (event) {
202 handler : function (event) {
203 var index = that.notebook.get_selected_index();
203 var index = that.notebook.get_selected_index();
204 if (index !== (that.notebook.ncells()-1) && index !== null) {
204 if (index !== (that.notebook.ncells()-1) && index !== null) {
205 that.notebook.select_next();
205 that.notebook.select_next();
206 that.notebook.focus_cell();
206 that.notebook.focus_cell();
207 }
207 }
208 return false;
208 return false;
209 }
209 }
210 },
210 },
211 'k' : {
211 'k' : {
212 help : 'select previous cell',
212 help : 'select previous cell',
213 help_index : 'dc',
213 help_index : 'dc',
214 handler : function (event) {
214 handler : function (event) {
215 var index = that.notebook.get_selected_index();
215 var index = that.notebook.get_selected_index();
216 if (index !== 0 && index !== null) {
216 if (index !== 0 && index !== null) {
217 that.notebook.select_prev();
217 that.notebook.select_prev();
218 that.notebook.focus_cell();
218 that.notebook.focus_cell();
219 }
219 }
220 return false;
220 return false;
221 }
221 }
222 },
222 },
223 'j' : {
223 'j' : {
224 help : 'select next cell',
224 help : 'select next cell',
225 help_index : 'dd',
225 help_index : 'dd',
226 handler : function (event) {
226 handler : function (event) {
227 var index = that.notebook.get_selected_index();
227 var index = that.notebook.get_selected_index();
228 if (index !== (that.notebook.ncells()-1) && index !== null) {
228 if (index !== (that.notebook.ncells()-1) && index !== null) {
229 that.notebook.select_next();
229 that.notebook.select_next();
230 that.notebook.focus_cell();
230 that.notebook.focus_cell();
231 }
231 }
232 return false;
232 return false;
233 }
233 }
234 },
234 },
235 'x' : {
235 'x' : {
236 help : 'cut cell',
236 help : 'cut cell',
237 help_index : 'ee',
237 help_index : 'ee',
238 handler : function (event) {
238 handler : function (event) {
239 that.notebook.cut_cell();
239 that.notebook.cut_cell();
240 return false;
240 return false;
241 }
241 }
242 },
242 },
243 'c' : {
243 'c' : {
244 help : 'copy cell',
244 help : 'copy cell',
245 help_index : 'ef',
245 help_index : 'ef',
246 handler : function (event) {
246 handler : function (event) {
247 that.notebook.copy_cell();
247 that.notebook.copy_cell();
248 return false;
248 return false;
249 }
249 }
250 },
250 },
251 'shift-v' : {
251 'shift-v' : {
252 help : 'paste cell above',
252 help : 'paste cell above',
253 help_index : 'eg',
253 help_index : 'eg',
254 handler : function (event) {
254 handler : function (event) {
255 that.notebook.paste_cell_above();
255 that.notebook.paste_cell_above();
256 return false;
256 return false;
257 }
257 }
258 },
258 },
259 'v' : {
259 'v' : {
260 help : 'paste cell below',
260 help : 'paste cell below',
261 help_index : 'eh',
261 help_index : 'eh',
262 handler : function (event) {
262 handler : function (event) {
263 that.notebook.paste_cell_below();
263 that.notebook.paste_cell_below();
264 return false;
264 return false;
265 }
265 }
266 },
266 },
267 'd' : {
267 'd' : {
268 help : 'delete cell (press twice)',
268 help : 'delete cell (press twice)',
269 help_index : 'ej',
269 help_index : 'ej',
270 count: 2,
270 count: 2,
271 handler : function (event) {
271 handler : function (event) {
272 that.notebook.delete_cell();
272 that.notebook.delete_cell();
273 return false;
273 return false;
274 }
274 }
275 },
275 },
276 'a' : {
276 'a' : {
277 help : 'insert cell above',
277 help : 'insert cell above',
278 help_index : 'ec',
278 help_index : 'ec',
279 handler : function (event) {
279 handler : function (event) {
280 that.notebook.insert_cell_above();
280 that.notebook.insert_cell_above();
281 that.notebook.select_prev();
281 that.notebook.select_prev();
282 that.notebook.focus_cell();
282 that.notebook.focus_cell();
283 return false;
283 return false;
284 }
284 }
285 },
285 },
286 'b' : {
286 'b' : {
287 help : 'insert cell below',
287 help : 'insert cell below',
288 help_index : 'ed',
288 help_index : 'ed',
289 handler : function (event) {
289 handler : function (event) {
290 that.notebook.insert_cell_below();
290 that.notebook.insert_cell_below();
291 that.notebook.select_next();
291 that.notebook.select_next();
292 that.notebook.focus_cell();
292 that.notebook.focus_cell();
293 return false;
293 return false;
294 }
294 }
295 },
295 },
296 'y' : {
296 'y' : {
297 help : 'to code',
297 help : 'to code',
298 help_index : 'ca',
298 help_index : 'ca',
299 handler : function (event) {
299 handler : function (event) {
300 that.notebook.to_code();
300 that.notebook.to_code();
301 return false;
301 return false;
302 }
302 }
303 },
303 },
304 'm' : {
304 'm' : {
305 help : 'to markdown',
305 help : 'to markdown',
306 help_index : 'cb',
306 help_index : 'cb',
307 handler : function (event) {
307 handler : function (event) {
308 that.notebook.to_markdown();
308 that.notebook.to_markdown();
309 return false;
309 return false;
310 }
310 }
311 },
311 },
312 'r' : {
312 'r' : {
313 help : 'to raw',
313 help : 'to raw',
314 help_index : 'cc',
314 help_index : 'cc',
315 handler : function (event) {
315 handler : function (event) {
316 that.notebook.to_raw();
316 that.notebook.to_raw();
317 return false;
317 return false;
318 }
318 }
319 },
319 },
320 '1' : {
320 '1' : {
321 help : 'to heading 1',
321 help : 'to heading 1',
322 help_index : 'cd',
322 help_index : 'cd',
323 handler : function (event) {
323 handler : function (event) {
324 that.notebook.to_heading(undefined, 1);
324 that.notebook.to_heading(undefined, 1);
325 return false;
325 return false;
326 }
326 }
327 },
327 },
328 '2' : {
328 '2' : {
329 help : 'to heading 2',
329 help : 'to heading 2',
330 help_index : 'ce',
330 help_index : 'ce',
331 handler : function (event) {
331 handler : function (event) {
332 that.notebook.to_heading(undefined, 2);
332 that.notebook.to_heading(undefined, 2);
333 return false;
333 return false;
334 }
334 }
335 },
335 },
336 '3' : {
336 '3' : {
337 help : 'to heading 3',
337 help : 'to heading 3',
338 help_index : 'cf',
338 help_index : 'cf',
339 handler : function (event) {
339 handler : function (event) {
340 that.notebook.to_heading(undefined, 3);
340 that.notebook.to_heading(undefined, 3);
341 return false;
341 return false;
342 }
342 }
343 },
343 },
344 '4' : {
344 '4' : {
345 help : 'to heading 4',
345 help : 'to heading 4',
346 help_index : 'cg',
346 help_index : 'cg',
347 handler : function (event) {
347 handler : function (event) {
348 that.notebook.to_heading(undefined, 4);
348 that.notebook.to_heading(undefined, 4);
349 return false;
349 return false;
350 }
350 }
351 },
351 },
352 '5' : {
352 '5' : {
353 help : 'to heading 5',
353 help : 'to heading 5',
354 help_index : 'ch',
354 help_index : 'ch',
355 handler : function (event) {
355 handler : function (event) {
356 that.notebook.to_heading(undefined, 5);
356 that.notebook.to_heading(undefined, 5);
357 return false;
357 return false;
358 }
358 }
359 },
359 },
360 '6' : {
360 '6' : {
361 help : 'to heading 6',
361 help : 'to heading 6',
362 help_index : 'ci',
362 help_index : 'ci',
363 handler : function (event) {
363 handler : function (event) {
364 that.notebook.to_heading(undefined, 6);
364 that.notebook.to_heading(undefined, 6);
365 return false;
365 return false;
366 }
366 }
367 },
367 },
368 'o' : {
368 'o' : {
369 help : 'toggle output',
369 help : 'toggle output',
370 help_index : 'gb',
370 help_index : 'gb',
371 handler : function (event) {
371 handler : function (event) {
372 that.notebook.toggle_output();
372 that.notebook.toggle_output();
373 return false;
373 return false;
374 }
374 }
375 },
375 },
376 'shift-o' : {
376 'shift-o' : {
377 help : 'toggle output scrolling',
377 help : 'toggle output scrolling',
378 help_index : 'gc',
378 help_index : 'gc',
379 handler : function (event) {
379 handler : function (event) {
380 that.notebook.toggle_output_scroll();
380 that.notebook.toggle_output_scroll();
381 return false;
381 return false;
382 }
382 }
383 },
383 },
384 's' : {
384 's' : {
385 help : 'save notebook',
385 help : 'save notebook',
386 help_index : 'fa',
386 help_index : 'fa',
387 handler : function (event) {
387 handler : function (event) {
388 that.notebook.save_checkpoint();
388 that.notebook.save_checkpoint();
389 return false;
389 return false;
390 }
390 }
391 },
391 },
392 'ctrl-j' : {
392 'ctrl-j' : {
393 help : 'move cell down',
393 help : 'move cell down',
394 help_index : 'eb',
394 help_index : 'eb',
395 handler : function (event) {
395 handler : function (event) {
396 that.notebook.move_cell_down();
396 that.notebook.move_cell_down();
397 return false;
397 return false;
398 }
398 }
399 },
399 },
400 'ctrl-k' : {
400 'ctrl-k' : {
401 help : 'move cell up',
401 help : 'move cell up',
402 help_index : 'ea',
402 help_index : 'ea',
403 handler : function (event) {
403 handler : function (event) {
404 that.notebook.move_cell_up();
404 that.notebook.move_cell_up();
405 return false;
405 return false;
406 }
406 }
407 },
407 },
408 'l' : {
408 'l' : {
409 help : 'toggle line numbers',
409 help : 'toggle line numbers',
410 help_index : 'ga',
410 help_index : 'ga',
411 handler : function (event) {
411 handler : function (event) {
412 that.notebook.cell_toggle_line_numbers();
412 that.notebook.cell_toggle_line_numbers();
413 return false;
413 return false;
414 }
414 }
415 },
415 },
416 'i' : {
416 'i' : {
417 help : 'interrupt kernel (press twice)',
417 help : 'interrupt kernel (press twice)',
418 help_index : 'ha',
418 help_index : 'ha',
419 count: 2,
419 count: 2,
420 handler : function (event) {
420 handler : function (event) {
421 that.notebook.kernel.interrupt();
421 that.notebook.kernel.interrupt();
422 return false;
422 return false;
423 }
423 }
424 },
424 },
425 '0' : {
425 '0' : {
426 help : 'restart kernel (press twice)',
426 help : 'restart kernel (press twice)',
427 help_index : 'hb',
427 help_index : 'hb',
428 count: 2,
428 count: 2,
429 handler : function (event) {
429 handler : function (event) {
430 that.notebook.restart_kernel();
430 that.notebook.restart_kernel();
431 return false;
431 return false;
432 }
432 }
433 },
433 },
434 'h' : {
434 'h' : {
435 help : 'keyboard shortcuts',
435 help : 'keyboard shortcuts',
436 help_index : 'ge',
436 help_index : 'ge',
437 handler : function (event) {
437 handler : function (event) {
438 that.quick_help.show_keyboard_shortcuts();
438 that.quick_help.show_keyboard_shortcuts();
439 return false;
439 return false;
440 }
440 }
441 },
441 },
442 'z' : {
442 'z' : {
443 help : 'undo last delete',
443 help : 'undo last delete',
444 help_index : 'ei',
444 help_index : 'ei',
445 handler : function (event) {
445 handler : function (event) {
446 that.notebook.undelete_cell();
446 that.notebook.undelete_cell();
447 return false;
447 return false;
448 }
448 }
449 },
449 },
450 'shift-m' : {
450 'shift-m' : {
451 help : 'merge cell below',
451 help : 'merge cell below',
452 help_index : 'ek',
452 help_index : 'ek',
453 handler : function (event) {
453 handler : function (event) {
454 that.notebook.merge_cell_below();
454 that.notebook.merge_cell_below();
455 return false;
455 return false;
456 }
456 }
457 },
457 },
458 'q' : {
458 'q' : {
459 help : 'close pager',
459 help : 'close pager',
460 help_index : 'gd',
460 help_index : 'gd',
461 handler : function (event) {
461 handler : function (event) {
462 that.pager.collapse();
462 that.pager.collapse();
463 return false;
463 return false;
464 }
464 }
465 },
465 },
466 };
466 };
467 };
467 };
468
468
469 KeyboardManager.prototype.bind_events = function () {
469 KeyboardManager.prototype.bind_events = function () {
470 var that = this;
470 var that = this;
471 $(document).keydown(function (event) {
471 $(document).keydown(function (event) {
472 return that.handle_keydown(event);
472 return that.handle_keydown(event);
473 });
473 });
474 };
474 };
475
475
476 KeyboardManager.prototype.handle_keydown = function (event) {
476 KeyboardManager.prototype.handle_keydown = function (event) {
477 var notebook = this.notebook;
477 var notebook = this.notebook;
478
478
479 if (event.which === keycodes.esc) {
479 if (event.which === keycodes.esc) {
480 // Intercept escape at highest level to avoid closing
480 // Intercept escape at highest level to avoid closing
481 // websocket connection with firefox
481 // websocket connection with firefox
482 event.preventDefault();
482 event.preventDefault();
483 }
483 }
484
484
485 if (!this.enabled) {
485 if (!this.enabled) {
486 if (event.which === keycodes.esc) {
486 if (event.which === keycodes.esc) {
487 // ESC
487 // ESC
488 notebook.command_mode();
488 notebook.command_mode();
489 return false;
489 return false;
490 }
490 }
491 return true;
491 return true;
492 }
492 }
493
493
494 if (this.mode === 'edit') {
494 if (this.mode === 'edit') {
495 return this.edit_shortcuts.call_handler(event);
495 return this.edit_shortcuts.call_handler(event);
496 } else if (this.mode === 'command') {
496 } else if (this.mode === 'command') {
497 return this.command_shortcuts.call_handler(event);
497 return this.command_shortcuts.call_handler(event);
498 }
498 }
499 return true;
499 return true;
500 };
500 };
501
501
502 KeyboardManager.prototype.edit_mode = function () {
502 KeyboardManager.prototype.edit_mode = function () {
503 this.last_mode = this.mode;
503 this.last_mode = this.mode;
504 this.mode = 'edit';
504 this.mode = 'edit';
505 };
505 };
506
506
507 KeyboardManager.prototype.command_mode = function () {
507 KeyboardManager.prototype.command_mode = function () {
508 this.last_mode = this.mode;
508 this.last_mode = this.mode;
509 this.mode = 'command';
509 this.mode = 'command';
510 };
510 };
511
511
512 KeyboardManager.prototype.enable = function () {
512 KeyboardManager.prototype.enable = function () {
513 this.enabled = true;
513 this.enabled = true;
514 };
514 };
515
515
516 KeyboardManager.prototype.disable = function () {
516 KeyboardManager.prototype.disable = function () {
517 this.enabled = false;
517 this.enabled = false;
518 };
518 };
519
519
520 KeyboardManager.prototype.register_events = function (e) {
520 KeyboardManager.prototype.register_events = function (e) {
521 var that = this;
521 var that = this;
522 var handle_focus = function () {
522 var handle_focus = function () {
523 that.disable();
523 that.disable();
524 };
524 };
525 var handle_blur = function () {
525 var handle_blur = function () {
526 that.enable();
526 that.enable();
527 };
527 };
528 e.on('focusin', handle_focus);
528 e.on('focusin', handle_focus);
529 e.on('focusout', handle_blur);
529 e.on('focusout', handle_blur);
530 // TODO: Very strange. The focusout event does not seem fire for the
530 // TODO: Very strange. The focusout event does not seem fire for the
531 // bootstrap textboxes on FF25&26... This works around that by
531 // bootstrap textboxes on FF25&26... This works around that by
532 // registering focus and blur events recursively on all inputs within
532 // registering focus and blur events recursively on all inputs within
533 // registered element.
533 // registered element.
534 e.find('input').blur(handle_blur);
534 e.find('input').blur(handle_blur);
535 e.on('DOMNodeInserted', function (event) {
535 e.on('DOMNodeInserted', function (event) {
536 var target = $(event.target);
536 var target = $(event.target);
537 if (target.is('input')) {
537 if (target.is('input')) {
538 target.blur(handle_blur);
538 target.blur(handle_blur);
539 } else {
539 } else {
540 target.find('input').blur(handle_blur);
540 target.find('input').blur(handle_blur);
541 }
541 }
542 });
542 });
543 // There are times (raw_input) where we remove the element from the DOM before
543 // There are times (raw_input) where we remove the element from the DOM before
544 // focusout is called. In this case we bind to the remove event of jQueryUI,
544 // focusout is called. In this case we bind to the remove event of jQueryUI,
545 // which gets triggered upon removal, iff it is focused at the time.
545 // which gets triggered upon removal, iff it is focused at the time.
546 // is_focused must be used to check for the case where an element within
546 // is_focused must be used to check for the case where an element within
547 // the element being removed is focused.
547 // the element being removed is focused.
548 e.on('remove', function () {
548 e.on('remove', function () {
549 if (utils.is_focused(e[0])) {
549 if (utils.is_focused(e[0])) {
550 that.enable();
550 that.enable();
551 }
551 }
552 });
552 });
553 };
553 };
554
554
555 // For backwards compatability.
555 // For backwards compatability.
556 IPython.KeyboardManager = KeyboardManager;
556 IPython.KeyboardManager = KeyboardManager;
557
557
558 return {'KeyboardManager': KeyboardManager};
558 return {'KeyboardManager': KeyboardManager};
559 });
559 });
@@ -1,109 +1,132 b''
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 var ipython = ipython || {};
4 var ipython = ipython || {};
5 require([
5 require([
6 'base/js/namespace',
6 'base/js/namespace',
7 'jquery',
7 'jquery',
8 'notebook/js/notebook',
8 'notebook/js/notebook',
9 'base/js/utils',
9 'base/js/utils',
10 'base/js/page',
10 'base/js/page',
11 'notebook/js/layoutmanager',
11 'notebook/js/layoutmanager',
12 'base/js/events',
12 'base/js/events',
13 'auth/js/loginwidget',
13 'auth/js/loginwidget',
14 'notebook/js/maintoolbar',
14 'notebook/js/maintoolbar',
15 'notebook/js/pager',
15 'notebook/js/pager',
16 'notebook/js/quickhelp',
16 'notebook/js/quickhelp',
17 'notebook/js/menubar',
17 'notebook/js/menubar',
18 'notebook/js/notificationarea',
18 'notebook/js/notificationarea',
19 'notebook/js/savewidget',
19 'notebook/js/savewidget',
20 'notebook/js/keyboardmanager',
20 'notebook/js/keyboardmanager',
21 'notebook/js/config',
21 'notebook/js/config',
22 ], function(
22 ], function(
23 IPython,
23 IPython,
24 $,
24 $,
25 notebook,
25 notebook,
26 utils,
26 utils,
27 page,
27 page,
28 layoutmanager,
28 layoutmanager,
29 events,
29 events,
30 loginwidget,
30 loginwidget,
31 maintoolbar,
31 maintoolbar,
32 pager,
32 pager,
33 quickhelp,
33 quickhelp,
34 menubar,
34 menubar,
35 notificationarea,
35 notificationarea,
36 savewidget,
36 savewidget,
37 keyboardmanager,
37 keyboardmanager,
38 config
38 config
39 ) {
39 ) {
40 "use strict";
40 "use strict";
41
41
42 $('#ipython-main-app').addClass('border-box-sizing');
42 $('#ipython-main-app').addClass('border-box-sizing');
43 $('div#notebook_panel').addClass('border-box-sizing');
43 $('div#notebook_panel').addClass('border-box-sizing');
44
44
45 var options = {
45 var common_options = {
46 base_url : utils.get_body_data("baseUrl"),
46 base_url : utils.get_body_data("baseUrl"),
47 notebook_path : utils.get_body_data("notebookPath"),
47 notebook_path : utils.get_body_data("notebookPath"),
48 notebook_name : utils.get_body_data('notebookName')
48 notebook_name : utils.get_body_data('notebookName')
49 };
49 };
50
50
51 var user_config = $.extend({}, config.default_config);
51 var user_config = $.extend({}, config.default_config);
52 var page = new page.Page();
52 var page = new page.Page();
53 var layout_manager = new layoutmanager.LayoutManager();
53 var layout_manager = new layoutmanager.LayoutManager();
54 var events = $([new events.Events()]);
54 var events = $([new events.Events()]);
55 var pager = new pager.Pager('div#pager', 'div#pager_splitter', layout_manager, events);
55 var pager = new pager.Pager('div#pager', 'div#pager_splitter', {
56 var keyboard_manager = new keyboardmanager.KeyboardManager(pager, events);
56 layout_manager: layout_manager,
57 events: events});
58 var keyboard_manager = new keyboardmanager.KeyboardManager({
59 pager: pager,
60 events: events});
57 var save_widget = new savewidget.SaveWidget('span#save_widget', events);
61 var save_widget = new savewidget.SaveWidget('span#save_widget', events);
58 var notebook = new notebook.Notebook('div#notebook', options, events, keyboard_manager, save_widget, user_config);
62 var notebook = new notebook.Notebook('div#notebook', $.extend({
59 var login_widget = new loginwidget.LoginWidget('span#login_widget', options);
63 events: events,
60 var toolbar = new maintoolbar.MainToolBar('#maintoolbar-container', layout_manager, notebook, events);
64 keyboard_manager: keyboard_manager,
61 var quick_help = new quickhelp.QuickHelp(undefined, keyboard_manager, events);
65 save_widget: save_widget,
62 var menubar = new menubar.MenuBar('#menubar', options, notebook, layout_manager, events, save_widget, quick_help);
66 config: user_config},
63 var notification_area = new notificationarea.NotificationArea('#notification_area', events, save_widget, notebook);
67 common_options));
68 var login_widget = new loginwidget.LoginWidget('span#login_widget', common_options);
69 var toolbar = new maintoolbar.MainToolBar('#maintoolbar-container', {
70 notebook: notebook,
71 events: events});
72 var quick_help = new quickhelp.QuickHelp({
73 keyboard_manager: keyboard_manager,
74 events: events});
75 var menubar = new menubar.MenuBar('#menubar', $.extend({
76 notebook: notebook,
77 layout_manager: layout_manager,
78 events: events,
79 save_widget: save_widget,
80 quick_help: quick_help},
81 common_options));
82 var notification_area = new notificationarea.NotificationArea(
83 '#notification_area', {
84 events: events,
85 save_widget: save_widget,
86 notebook: notebook});
64 notification_area.init_notification_widgets();
87 notification_area.init_notification_widgets();
65
88
66 $('body').append('<div id="fonttest"><pre><span id="test1">x</span>'+
89 $('body').append('<div id="fonttest"><pre><span id="test1">x</span>'+
67 '<span id="test2" style="font-weight: bold;">x</span>'+
90 '<span id="test2" style="font-weight: bold;">x</span>'+
68 '<span id="test3" style="font-style: italic;">x</span></pre></div>');
91 '<span id="test3" style="font-style: italic;">x</span></pre></div>');
69 var nh = $('#test1').innerHeight();
92 var nh = $('#test1').innerHeight();
70 var bh = $('#test2').innerHeight();
93 var bh = $('#test2').innerHeight();
71 var ih = $('#test3').innerHeight();
94 var ih = $('#test3').innerHeight();
72 if(nh != bh || nh != ih) {
95 if(nh != bh || nh != ih) {
73 $('head').append('<style>.CodeMirror span { vertical-align: bottom; }</style>');
96 $('head').append('<style>.CodeMirror span { vertical-align: bottom; }</style>');
74 }
97 }
75 $('#fonttest').remove();
98 $('#fonttest').remove();
76
99
77 page.show();
100 page.show();
78
101
79 layout_manager.do_resize();
102 layout_manager.do_resize();
80 var first_load = function () {
103 var first_load = function () {
81 layout_manager.do_resize();
104 layout_manager.do_resize();
82 var hash = document.location.hash;
105 var hash = document.location.hash;
83 if (hash) {
106 if (hash) {
84 document.location.hash = '';
107 document.location.hash = '';
85 document.location.hash = hash;
108 document.location.hash = hash;
86 }
109 }
87 notebook.set_autosave_interval(notebook.minimum_autosave_interval);
110 notebook.set_autosave_interval(notebook.minimum_autosave_interval);
88 // only do this once
111 // only do this once
89 events.off('notebook_loaded.Notebook', first_load);
112 events.off('notebook_loaded.Notebook', first_load);
90 };
113 };
91
114
92 events.on('notebook_loaded.Notebook', first_load);
115 events.on('notebook_loaded.Notebook', first_load);
93 events.trigger('app_initialized.NotebookApp');
116 events.trigger('app_initialized.NotebookApp');
94 notebook.load_notebook(options.notebook_name, options.notebook_path);
117 notebook.load_notebook(common_options.notebook_name, common_options.notebook_path);
95
118
96 ipython.page = page;
119 ipython.page = page;
97 ipython.layout_manager = layout_manager;
120 ipython.layout_manager = layout_manager;
98 ipython.notebook = notebook;
121 ipython.notebook = notebook;
99 ipython.pager = pager;
122 ipython.pager = pager;
100 ipython.quick_help = quick_help;
123 ipython.quick_help = quick_help;
101 ipython.login_widget = login_widget;
124 ipython.login_widget = login_widget;
102 ipython.menubar = menubar;
125 ipython.menubar = menubar;
103 ipython.toolbar = toolbar;
126 ipython.toolbar = toolbar;
104 ipython.notification_area = notification_area;
127 ipython.notification_area = notification_area;
105 ipython.events = events;
128 ipython.events = events;
106 ipython.keyboard_manager = keyboard_manager;
129 ipython.keyboard_manager = keyboard_manager;
107 ipython.save_widget = save_widget;
130 ipython.save_widget = save_widget;
108 ipython.config = user_config;
131 ipython.config = user_config;
109 });
132 });
@@ -1,221 +1,221 b''
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 'notebook/js/toolbar',
7 'notebook/js/toolbar',
8 'notebook/js/celltoolbar',
8 'notebook/js/celltoolbar',
9 ], function(IPython, $, toolbar, celltoolbar) {
9 ], function(IPython, $, toolbar, celltoolbar) {
10 "use strict";
10 "use strict";
11
11
12 var MainToolBar = function (selector, layout_manager, notebook, events) {
12 var MainToolBar = function (selector, options) {
13 toolbar.ToolBar.apply(this, arguments);
13 toolbar.ToolBar.apply(this, arguments);
14 this.events = events;
14 this.events = options.events;
15 this.notebook = notebook;
15 this.notebook = options.notebook;
16 this.construct();
16 this.construct();
17 this.add_celltype_list();
17 this.add_celltype_list();
18 this.add_celltoolbar_list();
18 this.add_celltoolbar_list();
19 this.bind_events();
19 this.bind_events();
20 };
20 };
21
21
22 MainToolBar.prototype = new toolbar.ToolBar();
22 MainToolBar.prototype = new toolbar.ToolBar();
23
23
24 MainToolBar.prototype.construct = function () {
24 MainToolBar.prototype.construct = function () {
25 var that = this;
25 var that = this;
26 this.add_buttons_group([
26 this.add_buttons_group([
27 {
27 {
28 id : 'save_b',
28 id : 'save_b',
29 label : 'Save and Checkpoint',
29 label : 'Save and Checkpoint',
30 icon : 'icon-save',
30 icon : 'icon-save',
31 callback : function () {
31 callback : function () {
32 that.notebook.save_checkpoint();
32 that.notebook.save_checkpoint();
33 }
33 }
34 }
34 }
35 ]);
35 ]);
36
36
37 this.add_buttons_group([
37 this.add_buttons_group([
38 {
38 {
39 id : 'insert_below_b',
39 id : 'insert_below_b',
40 label : 'Insert Cell Below',
40 label : 'Insert Cell Below',
41 icon : 'icon-plus-sign',
41 icon : 'icon-plus-sign',
42 callback : function () {
42 callback : function () {
43 that.notebook.insert_cell_below('code');
43 that.notebook.insert_cell_below('code');
44 that.notebook.select_next();
44 that.notebook.select_next();
45 that.notebook.focus_cell();
45 that.notebook.focus_cell();
46 }
46 }
47 }
47 }
48 ],'insert_above_below');
48 ],'insert_above_below');
49
49
50 this.add_buttons_group([
50 this.add_buttons_group([
51 {
51 {
52 id : 'cut_b',
52 id : 'cut_b',
53 label : 'Cut Cell',
53 label : 'Cut Cell',
54 icon : 'icon-cut',
54 icon : 'icon-cut',
55 callback : function () {
55 callback : function () {
56 that.notebook.cut_cell();
56 that.notebook.cut_cell();
57 }
57 }
58 },
58 },
59 {
59 {
60 id : 'copy_b',
60 id : 'copy_b',
61 label : 'Copy Cell',
61 label : 'Copy Cell',
62 icon : 'icon-copy',
62 icon : 'icon-copy',
63 callback : function () {
63 callback : function () {
64 that.notebook.copy_cell();
64 that.notebook.copy_cell();
65 }
65 }
66 },
66 },
67 {
67 {
68 id : 'paste_b',
68 id : 'paste_b',
69 label : 'Paste Cell Below',
69 label : 'Paste Cell Below',
70 icon : 'icon-paste',
70 icon : 'icon-paste',
71 callback : function () {
71 callback : function () {
72 that.notebook.paste_cell_below();
72 that.notebook.paste_cell_below();
73 }
73 }
74 }
74 }
75 ],'cut_copy_paste');
75 ],'cut_copy_paste');
76
76
77 this.add_buttons_group([
77 this.add_buttons_group([
78 {
78 {
79 id : 'move_up_b',
79 id : 'move_up_b',
80 label : 'Move Cell Up',
80 label : 'Move Cell Up',
81 icon : 'icon-arrow-up',
81 icon : 'icon-arrow-up',
82 callback : function () {
82 callback : function () {
83 that.notebook.move_cell_up();
83 that.notebook.move_cell_up();
84 }
84 }
85 },
85 },
86 {
86 {
87 id : 'move_down_b',
87 id : 'move_down_b',
88 label : 'Move Cell Down',
88 label : 'Move Cell Down',
89 icon : 'icon-arrow-down',
89 icon : 'icon-arrow-down',
90 callback : function () {
90 callback : function () {
91 that.notebook.move_cell_down();
91 that.notebook.move_cell_down();
92 }
92 }
93 }
93 }
94 ],'move_up_down');
94 ],'move_up_down');
95
95
96
96
97 this.add_buttons_group([
97 this.add_buttons_group([
98 {
98 {
99 id : 'run_b',
99 id : 'run_b',
100 label : 'Run Cell',
100 label : 'Run Cell',
101 icon : 'icon-play',
101 icon : 'icon-play',
102 callback : function () {
102 callback : function () {
103 // emulate default shift-enter behavior
103 // emulate default shift-enter behavior
104 that.notebook.execute_cell_and_select_below();
104 that.notebook.execute_cell_and_select_below();
105 }
105 }
106 },
106 },
107 {
107 {
108 id : 'interrupt_b',
108 id : 'interrupt_b',
109 label : 'Interrupt',
109 label : 'Interrupt',
110 icon : 'icon-stop',
110 icon : 'icon-stop',
111 callback : function () {
111 callback : function () {
112 that.notebook.session.interrupt_kernel();
112 that.notebook.session.interrupt_kernel();
113 }
113 }
114 },
114 },
115 {
115 {
116 id : 'repeat_b',
116 id : 'repeat_b',
117 label : 'Restart Kernel',
117 label : 'Restart Kernel',
118 icon : 'icon-repeat',
118 icon : 'icon-repeat',
119 callback : function () {
119 callback : function () {
120 that.notebook.restart_kernel();
120 that.notebook.restart_kernel();
121 }
121 }
122 }
122 }
123 ],'run_int');
123 ],'run_int');
124 };
124 };
125
125
126 MainToolBar.prototype.add_celltype_list = function () {
126 MainToolBar.prototype.add_celltype_list = function () {
127 this.element
127 this.element
128 .append($('<select/>')
128 .append($('<select/>')
129 .attr('id','cell_type')
129 .attr('id','cell_type')
130 .addClass('form-control select-xs')
130 .addClass('form-control select-xs')
131 // .addClass('ui-widget-content')
131 // .addClass('ui-widget-content')
132 .append($('<option/>').attr('value','code').text('Code'))
132 .append($('<option/>').attr('value','code').text('Code'))
133 .append($('<option/>').attr('value','markdown').text('Markdown'))
133 .append($('<option/>').attr('value','markdown').text('Markdown'))
134 .append($('<option/>').attr('value','raw').text('Raw NBConvert'))
134 .append($('<option/>').attr('value','raw').text('Raw NBConvert'))
135 .append($('<option/>').attr('value','heading1').text('Heading 1'))
135 .append($('<option/>').attr('value','heading1').text('Heading 1'))
136 .append($('<option/>').attr('value','heading2').text('Heading 2'))
136 .append($('<option/>').attr('value','heading2').text('Heading 2'))
137 .append($('<option/>').attr('value','heading3').text('Heading 3'))
137 .append($('<option/>').attr('value','heading3').text('Heading 3'))
138 .append($('<option/>').attr('value','heading4').text('Heading 4'))
138 .append($('<option/>').attr('value','heading4').text('Heading 4'))
139 .append($('<option/>').attr('value','heading5').text('Heading 5'))
139 .append($('<option/>').attr('value','heading5').text('Heading 5'))
140 .append($('<option/>').attr('value','heading6').text('Heading 6'))
140 .append($('<option/>').attr('value','heading6').text('Heading 6'))
141 );
141 );
142 };
142 };
143
143
144
144
145 MainToolBar.prototype.add_celltoolbar_list = function () {
145 MainToolBar.prototype.add_celltoolbar_list = function () {
146 var label = $('<span/>').addClass("navbar-text").text('Cell Toolbar:');
146 var label = $('<span/>').addClass("navbar-text").text('Cell Toolbar:');
147 var select = $('<select/>')
147 var select = $('<select/>')
148 // .addClass('ui-widget-content')
148 // .addClass('ui-widget-content')
149 .attr('id', 'ctb_select')
149 .attr('id', 'ctb_select')
150 .addClass('form-control select-xs')
150 .addClass('form-control select-xs')
151 .append($('<option/>').attr('value', '').text('None'));
151 .append($('<option/>').attr('value', '').text('None'));
152 this.element.append(label).append(select);
152 this.element.append(label).append(select);
153 select.change(function() {
153 select.change(function() {
154 var val = $(this).val();
154 var val = $(this).val();
155 if (val ==='') {
155 if (val ==='') {
156 celltoolbar.CellToolbar.global_hide();
156 celltoolbar.CellToolbar.global_hide();
157 delete that.notebook.metadata.celltoolbar;
157 delete that.notebook.metadata.celltoolbar;
158 } else {
158 } else {
159 celltoolbar.CellToolbar.global_show();
159 celltoolbar.CellToolbar.global_show();
160 celltoolbar.CellToolbar.activate_preset(val);
160 celltoolbar.CellToolbar.activate_preset(val);
161 that.notebook.metadata.celltoolbar = val;
161 that.notebook.metadata.celltoolbar = val;
162 }
162 }
163 });
163 });
164 // Setup the currently registered presets.
164 // Setup the currently registered presets.
165 var presets = celltoolbar.CellToolbar.list_presets();
165 var presets = celltoolbar.CellToolbar.list_presets();
166 for (var i=0; i<presets.length; i++) {
166 for (var i=0; i<presets.length; i++) {
167 var name = presets[i];
167 var name = presets[i];
168 select.append($('<option/>').attr('value', name).text(name));
168 select.append($('<option/>').attr('value', name).text(name));
169 }
169 }
170 // Setup future preset registrations.
170 // Setup future preset registrations.
171 this.events.on('preset_added.CellToolbar', function (event, data) {
171 this.events.on('preset_added.CellToolbar', function (event, data) {
172 var name = data.name;
172 var name = data.name;
173 select.append($('<option/>').attr('value', name).text(name));
173 select.append($('<option/>').attr('value', name).text(name));
174 });
174 });
175 // Update select value when a preset is activated.
175 // Update select value when a preset is activated.
176 this.events.on('preset_activated.CellToolbar', function (event, data) {
176 this.events.on('preset_activated.CellToolbar', function (event, data) {
177 if (select.val() !== data.name)
177 if (select.val() !== data.name)
178 select.val(data.name);
178 select.val(data.name);
179 });
179 });
180 };
180 };
181
181
182
182
183 MainToolBar.prototype.bind_events = function () {
183 MainToolBar.prototype.bind_events = function () {
184 var that = this;
184 var that = this;
185
185
186 this.element.find('#cell_type').change(function () {
186 this.element.find('#cell_type').change(function () {
187 var cell_type = $(this).val();
187 var cell_type = $(this).val();
188 if (cell_type === 'code') {
188 if (cell_type === 'code') {
189 that.notebook.to_code();
189 that.notebook.to_code();
190 } else if (cell_type === 'markdown') {
190 } else if (cell_type === 'markdown') {
191 that.notebook.to_markdown();
191 that.notebook.to_markdown();
192 } else if (cell_type === 'raw') {
192 } else if (cell_type === 'raw') {
193 that.notebook.to_raw();
193 that.notebook.to_raw();
194 } else if (cell_type === 'heading1') {
194 } else if (cell_type === 'heading1') {
195 that.notebook.to_heading(undefined, 1);
195 that.notebook.to_heading(undefined, 1);
196 } else if (cell_type === 'heading2') {
196 } else if (cell_type === 'heading2') {
197 that.notebook.to_heading(undefined, 2);
197 that.notebook.to_heading(undefined, 2);
198 } else if (cell_type === 'heading3') {
198 } else if (cell_type === 'heading3') {
199 that.notebook.to_heading(undefined, 3);
199 that.notebook.to_heading(undefined, 3);
200 } else if (cell_type === 'heading4') {
200 } else if (cell_type === 'heading4') {
201 that.notebook.to_heading(undefined, 4);
201 that.notebook.to_heading(undefined, 4);
202 } else if (cell_type === 'heading5') {
202 } else if (cell_type === 'heading5') {
203 that.notebook.to_heading(undefined, 5);
203 that.notebook.to_heading(undefined, 5);
204 } else if (cell_type === 'heading6') {
204 } else if (cell_type === 'heading6') {
205 that.notebook.to_heading(undefined, 6);
205 that.notebook.to_heading(undefined, 6);
206 }
206 }
207 });
207 });
208 this.events.on('selected_cell_type_changed.Notebook', function (event, data) {
208 this.events.on('selected_cell_type_changed.Notebook', function (event, data) {
209 if (data.cell_type === 'heading') {
209 if (data.cell_type === 'heading') {
210 that.element.find('#cell_type').val(data.cell_type+data.level);
210 that.element.find('#cell_type').val(data.cell_type+data.level);
211 } else {
211 } else {
212 that.element.find('#cell_type').val(data.cell_type);
212 that.element.find('#cell_type').val(data.cell_type);
213 }
213 }
214 });
214 });
215 };
215 };
216
216
217 // Backwards compatability.
217 // Backwards compatability.
218 IPython.MainToolBar = MainToolBar;
218 IPython.MainToolBar = MainToolBar;
219
219
220 return {'MainToolBar': MainToolBar};
220 return {'MainToolBar': MainToolBar};
221 });
221 });
@@ -1,347 +1,347 b''
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 'notebook/js/tour',
8 'notebook/js/tour',
9 'components/bootstrap-tour/build/js/bootstrap-tour.min',
9 'components/bootstrap-tour/build/js/bootstrap-tour.min',
10 ], function(IPython, $, utils, tour) {
10 ], function(IPython, $, utils, tour) {
11 "use strict";
11 "use strict";
12
12
13 /**
13 /**
14 * A MenuBar Class to generate the menubar of IPython notebook
14 * A MenuBar Class to generate the menubar of IPython notebook
15 * @Class MenuBar
15 * @Class MenuBar
16 *
16 *
17 * @constructor
17 * @constructor
18 *
18 *
19 *
19 *
20 * @param selector {string} selector for the menubar element in DOM
20 * @param selector {string} selector for the menubar element in DOM
21 * @param {object} [options]
21 * @param {object} [options]
22 * @param [options.base_url] {String} String to use for the
22 * @param [options.base_url] {String} String to use for the
23 * base project url. Default is to inspect
23 * base project url. Default is to inspect
24 * $('body').data('baseUrl');
24 * $('body').data('baseUrl');
25 * does not support change for now is set through this option
25 * does not support change for now is set through this option
26 */
26 */
27 var MenuBar = function (selector, options, notebook, layout_manager, events, save_widget, quick_help) {
27 var MenuBar = function (selector, options) {
28 options = options || {};
28 options = options || {};
29 this.base_url = options.base_url || utils.get_body_data("baseUrl");
29 this.base_url = options.base_url || utils.get_body_data("baseUrl");
30 this.selector = selector;
30 this.selector = selector;
31 this.notebook = notebook;
31 this.notebook = options.notebook;
32 this.layout_manager = layout_manager;
32 this.layout_manager = options.layout_manager;
33 this.events = events;
33 this.events = options.events;
34 this.save_widget = save_widget;
34 this.save_widget = options.save_widget;
35 this.quick_help = quick_help;
35 this.quick_help = options.quick_help;
36
36
37 try {
37 try {
38 this.tour = new tour.Tour(notebook, events);
38 this.tour = new tour.Tour(this.notebook, this.events);
39 } catch (e) {
39 } catch (e) {
40 this.tour = undefined;
40 this.tour = undefined;
41 console.log("Failed to instantiate Notebook Tour", e);
41 console.log("Failed to instantiate Notebook Tour", e);
42 }
42 }
43
43
44 if (this.selector !== undefined) {
44 if (this.selector !== undefined) {
45 this.element = $(selector);
45 this.element = $(selector);
46 this.style();
46 this.style();
47 this.bind_events();
47 this.bind_events();
48 }
48 }
49 };
49 };
50
50
51 MenuBar.prototype.style = function () {
51 MenuBar.prototype.style = function () {
52 var that = this;
52 var that = this;
53 this.element.addClass('border-box-sizing');
53 this.element.addClass('border-box-sizing');
54 this.element.find("li").click(function (event, ui) {
54 this.element.find("li").click(function (event, ui) {
55 // The selected cell loses focus when the menu is entered, so we
55 // The selected cell loses focus when the menu is entered, so we
56 // re-select it upon selection.
56 // re-select it upon selection.
57 var i = that.notebook.get_selected_index();
57 var i = that.notebook.get_selected_index();
58 that.notebook.select(i);
58 that.notebook.select(i);
59 }
59 }
60 );
60 );
61 };
61 };
62
62
63 MenuBar.prototype._nbconvert = function (format, download) {
63 MenuBar.prototype._nbconvert = function (format, download) {
64 download = download || false;
64 download = download || false;
65 var notebook_path = this.notebook.notebook_path;
65 var notebook_path = this.notebook.notebook_path;
66 var notebook_name = this.notebook.notebook_name;
66 var notebook_name = this.notebook.notebook_name;
67 if (this.notebook.dirty) {
67 if (this.notebook.dirty) {
68 this.notebook.save_notebook({async : false});
68 this.notebook.save_notebook({async : false});
69 }
69 }
70 var url = utils.url_join_encode(
70 var url = utils.url_join_encode(
71 this.base_url,
71 this.base_url,
72 'nbconvert',
72 'nbconvert',
73 format,
73 format,
74 notebook_path,
74 notebook_path,
75 notebook_name
75 notebook_name
76 ) + "?download=" + download.toString();
76 ) + "?download=" + download.toString();
77
77
78 window.open(url);
78 window.open(url);
79 };
79 };
80
80
81 MenuBar.prototype.bind_events = function () {
81 MenuBar.prototype.bind_events = function () {
82 // File
82 // File
83 var that = this;
83 var that = this;
84 this.element.find('#new_notebook').click(function () {
84 this.element.find('#new_notebook').click(function () {
85 that.notebook.new_notebook();
85 that.notebook.new_notebook();
86 });
86 });
87 this.element.find('#open_notebook').click(function () {
87 this.element.find('#open_notebook').click(function () {
88 window.open(utils.url_join_encode(
88 window.open(utils.url_join_encode(
89 that.notebook.base_url,
89 that.notebook.base_url,
90 'tree',
90 'tree',
91 that.notebook.notebook_path
91 that.notebook.notebook_path
92 ));
92 ));
93 });
93 });
94 this.element.find('#copy_notebook').click(function () {
94 this.element.find('#copy_notebook').click(function () {
95 that.notebook.copy_notebook();
95 that.notebook.copy_notebook();
96 return false;
96 return false;
97 });
97 });
98 this.element.find('#download_ipynb').click(function () {
98 this.element.find('#download_ipynb').click(function () {
99 var base_url = that.notebook.base_url;
99 var base_url = that.notebook.base_url;
100 var notebook_path = that.notebook.notebook_path;
100 var notebook_path = that.notebook.notebook_path;
101 var notebook_name = that.notebook.notebook_name;
101 var notebook_name = that.notebook.notebook_name;
102 if (that.notebook.dirty) {
102 if (that.notebook.dirty) {
103 that.notebook.save_notebook({async : false});
103 that.notebook.save_notebook({async : false});
104 }
104 }
105
105
106 var url = utils.url_join_encode(
106 var url = utils.url_join_encode(
107 base_url,
107 base_url,
108 'files',
108 'files',
109 notebook_path,
109 notebook_path,
110 notebook_name
110 notebook_name
111 );
111 );
112 window.location.assign(url);
112 window.location.assign(url);
113 });
113 });
114
114
115 this.element.find('#print_preview').click(function () {
115 this.element.find('#print_preview').click(function () {
116 that._nbconvert('html', false);
116 that._nbconvert('html', false);
117 });
117 });
118
118
119 this.element.find('#download_py').click(function () {
119 this.element.find('#download_py').click(function () {
120 that._nbconvert('python', true);
120 that._nbconvert('python', true);
121 });
121 });
122
122
123 this.element.find('#download_html').click(function () {
123 this.element.find('#download_html').click(function () {
124 that._nbconvert('html', true);
124 that._nbconvert('html', true);
125 });
125 });
126
126
127 this.element.find('#download_rst').click(function () {
127 this.element.find('#download_rst').click(function () {
128 that._nbconvert('rst', true);
128 that._nbconvert('rst', true);
129 });
129 });
130
130
131 this.element.find('#download_pdf').click(function () {
131 this.element.find('#download_pdf').click(function () {
132 that._nbconvert('pdf', true);
132 that._nbconvert('pdf', true);
133 });
133 });
134
134
135 this.element.find('#rename_notebook').click(function () {
135 this.element.find('#rename_notebook').click(function () {
136 that.save_widget.rename_notebook();
136 that.save_widget.rename_notebook();
137 });
137 });
138 this.element.find('#save_checkpoint').click(function () {
138 this.element.find('#save_checkpoint').click(function () {
139 that.notebook.save_checkpoint();
139 that.notebook.save_checkpoint();
140 });
140 });
141 this.element.find('#restore_checkpoint').click(function () {
141 this.element.find('#restore_checkpoint').click(function () {
142 });
142 });
143 this.element.find('#trust_notebook').click(function () {
143 this.element.find('#trust_notebook').click(function () {
144 that.notebook.trust_notebook();
144 that.notebook.trust_notebook();
145 });
145 });
146 this.events.on('trust_changed.Notebook', function (event, trusted) {
146 this.events.on('trust_changed.Notebook', function (event, trusted) {
147 if (trusted) {
147 if (trusted) {
148 that.element.find('#trust_notebook')
148 that.element.find('#trust_notebook')
149 .addClass("disabled")
149 .addClass("disabled")
150 .find("a").text("Trusted Notebook");
150 .find("a").text("Trusted Notebook");
151 } else {
151 } else {
152 that.element.find('#trust_notebook')
152 that.element.find('#trust_notebook')
153 .removeClass("disabled")
153 .removeClass("disabled")
154 .find("a").text("Trust Notebook");
154 .find("a").text("Trust Notebook");
155 }
155 }
156 });
156 });
157 this.element.find('#kill_and_exit').click(function () {
157 this.element.find('#kill_and_exit').click(function () {
158 that.notebook.session.delete();
158 that.notebook.session.delete();
159 setTimeout(function(){
159 setTimeout(function(){
160 // allow closing of new tabs in Chromium, impossible in FF
160 // allow closing of new tabs in Chromium, impossible in FF
161 window.open('', '_self', '');
161 window.open('', '_self', '');
162 window.close();
162 window.close();
163 }, 500);
163 }, 500);
164 });
164 });
165 // Edit
165 // Edit
166 this.element.find('#cut_cell').click(function () {
166 this.element.find('#cut_cell').click(function () {
167 that.notebook.cut_cell();
167 that.notebook.cut_cell();
168 });
168 });
169 this.element.find('#copy_cell').click(function () {
169 this.element.find('#copy_cell').click(function () {
170 that.notebook.copy_cell();
170 that.notebook.copy_cell();
171 });
171 });
172 this.element.find('#delete_cell').click(function () {
172 this.element.find('#delete_cell').click(function () {
173 that.notebook.delete_cell();
173 that.notebook.delete_cell();
174 });
174 });
175 this.element.find('#undelete_cell').click(function () {
175 this.element.find('#undelete_cell').click(function () {
176 that.notebook.undelete_cell();
176 that.notebook.undelete_cell();
177 });
177 });
178 this.element.find('#split_cell').click(function () {
178 this.element.find('#split_cell').click(function () {
179 that.notebook.split_cell();
179 that.notebook.split_cell();
180 });
180 });
181 this.element.find('#merge_cell_above').click(function () {
181 this.element.find('#merge_cell_above').click(function () {
182 that.notebook.merge_cell_above();
182 that.notebook.merge_cell_above();
183 });
183 });
184 this.element.find('#merge_cell_below').click(function () {
184 this.element.find('#merge_cell_below').click(function () {
185 that.notebook.merge_cell_below();
185 that.notebook.merge_cell_below();
186 });
186 });
187 this.element.find('#move_cell_up').click(function () {
187 this.element.find('#move_cell_up').click(function () {
188 that.notebook.move_cell_up();
188 that.notebook.move_cell_up();
189 });
189 });
190 this.element.find('#move_cell_down').click(function () {
190 this.element.find('#move_cell_down').click(function () {
191 that.notebook.move_cell_down();
191 that.notebook.move_cell_down();
192 });
192 });
193 this.element.find('#edit_nb_metadata').click(function () {
193 this.element.find('#edit_nb_metadata').click(function () {
194 that.notebook.edit_metadata();
194 that.notebook.edit_metadata();
195 });
195 });
196
196
197 // View
197 // View
198 this.element.find('#toggle_header').click(function () {
198 this.element.find('#toggle_header').click(function () {
199 $('div#header').toggle();
199 $('div#header').toggle();
200 that.layout_manager.do_resize();
200 that.layout_manager.do_resize();
201 });
201 });
202 this.element.find('#toggle_toolbar').click(function () {
202 this.element.find('#toggle_toolbar').click(function () {
203 $('div#maintoolbar').toggle();
203 $('div#maintoolbar').toggle();
204 that.layout_manager.do_resize();
204 that.layout_manager.do_resize();
205 });
205 });
206 // Insert
206 // Insert
207 this.element.find('#insert_cell_above').click(function () {
207 this.element.find('#insert_cell_above').click(function () {
208 that.notebook.insert_cell_above('code');
208 that.notebook.insert_cell_above('code');
209 that.notebook.select_prev();
209 that.notebook.select_prev();
210 });
210 });
211 this.element.find('#insert_cell_below').click(function () {
211 this.element.find('#insert_cell_below').click(function () {
212 that.notebook.insert_cell_below('code');
212 that.notebook.insert_cell_below('code');
213 that.notebook.select_next();
213 that.notebook.select_next();
214 });
214 });
215 // Cell
215 // Cell
216 this.element.find('#run_cell').click(function () {
216 this.element.find('#run_cell').click(function () {
217 that.notebook.execute_cell();
217 that.notebook.execute_cell();
218 });
218 });
219 this.element.find('#run_cell_select_below').click(function () {
219 this.element.find('#run_cell_select_below').click(function () {
220 that.notebook.execute_cell_and_select_below();
220 that.notebook.execute_cell_and_select_below();
221 });
221 });
222 this.element.find('#run_cell_insert_below').click(function () {
222 this.element.find('#run_cell_insert_below').click(function () {
223 that.notebook.execute_cell_and_insert_below();
223 that.notebook.execute_cell_and_insert_below();
224 });
224 });
225 this.element.find('#run_all_cells').click(function () {
225 this.element.find('#run_all_cells').click(function () {
226 that.notebook.execute_all_cells();
226 that.notebook.execute_all_cells();
227 });
227 });
228 this.element.find('#run_all_cells_above').click(function () {
228 this.element.find('#run_all_cells_above').click(function () {
229 that.notebook.execute_cells_above();
229 that.notebook.execute_cells_above();
230 });
230 });
231 this.element.find('#run_all_cells_below').click(function () {
231 this.element.find('#run_all_cells_below').click(function () {
232 that.notebook.execute_cells_below();
232 that.notebook.execute_cells_below();
233 });
233 });
234 this.element.find('#to_code').click(function () {
234 this.element.find('#to_code').click(function () {
235 that.notebook.to_code();
235 that.notebook.to_code();
236 });
236 });
237 this.element.find('#to_markdown').click(function () {
237 this.element.find('#to_markdown').click(function () {
238 that.notebook.to_markdown();
238 that.notebook.to_markdown();
239 });
239 });
240 this.element.find('#to_raw').click(function () {
240 this.element.find('#to_raw').click(function () {
241 that.notebook.to_raw();
241 that.notebook.to_raw();
242 });
242 });
243 this.element.find('#to_heading1').click(function () {
243 this.element.find('#to_heading1').click(function () {
244 that.notebook.to_heading(undefined, 1);
244 that.notebook.to_heading(undefined, 1);
245 });
245 });
246 this.element.find('#to_heading2').click(function () {
246 this.element.find('#to_heading2').click(function () {
247 that.notebook.to_heading(undefined, 2);
247 that.notebook.to_heading(undefined, 2);
248 });
248 });
249 this.element.find('#to_heading3').click(function () {
249 this.element.find('#to_heading3').click(function () {
250 that.notebook.to_heading(undefined, 3);
250 that.notebook.to_heading(undefined, 3);
251 });
251 });
252 this.element.find('#to_heading4').click(function () {
252 this.element.find('#to_heading4').click(function () {
253 that.notebook.to_heading(undefined, 4);
253 that.notebook.to_heading(undefined, 4);
254 });
254 });
255 this.element.find('#to_heading5').click(function () {
255 this.element.find('#to_heading5').click(function () {
256 that.notebook.to_heading(undefined, 5);
256 that.notebook.to_heading(undefined, 5);
257 });
257 });
258 this.element.find('#to_heading6').click(function () {
258 this.element.find('#to_heading6').click(function () {
259 that.notebook.to_heading(undefined, 6);
259 that.notebook.to_heading(undefined, 6);
260 });
260 });
261
261
262 this.element.find('#toggle_current_output').click(function () {
262 this.element.find('#toggle_current_output').click(function () {
263 that.notebook.toggle_output();
263 that.notebook.toggle_output();
264 });
264 });
265 this.element.find('#toggle_current_output_scroll').click(function () {
265 this.element.find('#toggle_current_output_scroll').click(function () {
266 that.notebook.toggle_output_scroll();
266 that.notebook.toggle_output_scroll();
267 });
267 });
268 this.element.find('#clear_current_output').click(function () {
268 this.element.find('#clear_current_output').click(function () {
269 that.notebook.clear_output();
269 that.notebook.clear_output();
270 });
270 });
271
271
272 this.element.find('#toggle_all_output').click(function () {
272 this.element.find('#toggle_all_output').click(function () {
273 that.notebook.toggle_all_output();
273 that.notebook.toggle_all_output();
274 });
274 });
275 this.element.find('#toggle_all_output_scroll').click(function () {
275 this.element.find('#toggle_all_output_scroll').click(function () {
276 that.notebook.toggle_all_output_scroll();
276 that.notebook.toggle_all_output_scroll();
277 });
277 });
278 this.element.find('#clear_all_output').click(function () {
278 this.element.find('#clear_all_output').click(function () {
279 that.notebook.clear_all_output();
279 that.notebook.clear_all_output();
280 });
280 });
281
281
282 // Kernel
282 // Kernel
283 this.element.find('#int_kernel').click(function () {
283 this.element.find('#int_kernel').click(function () {
284 that.notebook.session.interrupt_kernel();
284 that.notebook.session.interrupt_kernel();
285 });
285 });
286 this.element.find('#restart_kernel').click(function () {
286 this.element.find('#restart_kernel').click(function () {
287 that.notebook.restart_kernel();
287 that.notebook.restart_kernel();
288 });
288 });
289 // Help
289 // Help
290 if (this.tour) {
290 if (this.tour) {
291 this.element.find('#notebook_tour').click(function () {
291 this.element.find('#notebook_tour').click(function () {
292 that.tour.start();
292 that.tour.start();
293 });
293 });
294 } else {
294 } else {
295 this.element.find('#notebook_tour').addClass("disabled");
295 this.element.find('#notebook_tour').addClass("disabled");
296 }
296 }
297 this.element.find('#keyboard_shortcuts').click(function () {
297 this.element.find('#keyboard_shortcuts').click(function () {
298 that.quick_help.show_keyboard_shortcuts();
298 that.quick_help.show_keyboard_shortcuts();
299 });
299 });
300
300
301 this.update_restore_checkpoint(null);
301 this.update_restore_checkpoint(null);
302
302
303 this.events.on('checkpoints_listed.Notebook', function (event, data) {
303 this.events.on('checkpoints_listed.Notebook', function (event, data) {
304 that.update_restore_checkpoint(that.notebook.checkpoints);
304 that.update_restore_checkpoint(that.notebook.checkpoints);
305 });
305 });
306
306
307 this.events.on('checkpoint_created.Notebook', function (event, data) {
307 this.events.on('checkpoint_created.Notebook', function (event, data) {
308 that.update_restore_checkpoint(that.notebook.checkpoints);
308 that.update_restore_checkpoint(that.notebook.checkpoints);
309 });
309 });
310 };
310 };
311
311
312 MenuBar.prototype.update_restore_checkpoint = function(checkpoints) {
312 MenuBar.prototype.update_restore_checkpoint = function(checkpoints) {
313 var ul = this.element.find("#restore_checkpoint").find("ul");
313 var ul = this.element.find("#restore_checkpoint").find("ul");
314 ul.empty();
314 ul.empty();
315 if (!checkpoints || checkpoints.length === 0) {
315 if (!checkpoints || checkpoints.length === 0) {
316 ul.append(
316 ul.append(
317 $("<li/>")
317 $("<li/>")
318 .addClass("disabled")
318 .addClass("disabled")
319 .append(
319 .append(
320 $("<a/>")
320 $("<a/>")
321 .text("No checkpoints")
321 .text("No checkpoints")
322 )
322 )
323 );
323 );
324 return;
324 return;
325 }
325 }
326
326
327 var that = this;
327 var that = this;
328 checkpoints.map(function (checkpoint) {
328 checkpoints.map(function (checkpoint) {
329 var d = new Date(checkpoint.last_modified);
329 var d = new Date(checkpoint.last_modified);
330 ul.append(
330 ul.append(
331 $("<li/>").append(
331 $("<li/>").append(
332 $("<a/>")
332 $("<a/>")
333 .attr("href", "#")
333 .attr("href", "#")
334 .text(d.format("mmm dd HH:MM:ss"))
334 .text(d.format("mmm dd HH:MM:ss"))
335 .click(function () {
335 .click(function () {
336 that.notebook.restore_checkpoint_dialog(checkpoint);
336 that.notebook.restore_checkpoint_dialog(checkpoint);
337 })
337 })
338 )
338 )
339 );
339 );
340 });
340 });
341 };
341 };
342
342
343 // Backwards compatability.
343 // Backwards compatability.
344 IPython.MenuBar = MenuBar;
344 IPython.MenuBar = MenuBar;
345
345
346 return {'MenuBar': MenuBar};
346 return {'MenuBar': MenuBar};
347 });
347 });
@@ -1,2481 +1,2493 b''
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 'notebook/js/textcell',
9 'notebook/js/textcell',
10 'notebook/js/codecell',
10 'notebook/js/codecell',
11 'services/sessions/js/session',
11 'services/sessions/js/session',
12 'notebook/js/celltoolbar',
12 'notebook/js/celltoolbar',
13 'components/marked/lib/marked',
13 'components/marked/lib/marked',
14 'notebook/js/mathjaxutils',
14 'notebook/js/mathjaxutils',
15 'base/js/keyboard',
15 'base/js/keyboard',
16 'components/jquery-ui/ui/minified/jquery-ui.min',
16 'components/jquery-ui/ui/minified/jquery-ui.min',
17 'components/bootstrap/js/bootstrap.min',
17 'components/bootstrap/js/bootstrap.min',
18 ], function (
18 ], function (
19 IPython,
19 IPython,
20 $,
20 $,
21 utils,
21 utils,
22 dialog,
22 dialog,
23 cells,
23 cells,
24 codecell,
24 codecell,
25 session,
25 session,
26 celltoolbar,
26 celltoolbar,
27 marked,
27 marked,
28 mathjaxutils,
28 mathjaxutils,
29 keyboard
29 keyboard
30 ) {
30 ) {
31
31
32 /**
32 /**
33 * A notebook contains and manages cells.
33 * A notebook contains and manages cells.
34 *
34 *
35 * @class Notebook
35 * @class Notebook
36 * @constructor
36 * @constructor
37 * @param {String} selector A jQuery selector for the notebook's DOM element
37 * @param {String} selector A jQuery selector for the notebook's DOM element
38 * @param {Object} [options] A config object
38 * @param {Object} [options] A config object
39 * @param {Object} [events] An events object
39 * @param {Object} [events] An events object
40 */
40 */
41 var Notebook = function (selector, options, events, keyboard_manager, save_widget, config) {
41 var Notebook = function (selector, options) {
42 this.config = config;
42 this.config = options.config || {};
43 this.events = events;
43 this.base_url = options.base_url;
44 this.keyboard_manager = keyboard_manager;
44 this.notebook_path = options.notebook_path;
45 this.notebook_name = options.notebook_name;
46 this.events = options.events;
47 this.keyboard_manager = options.keyboard_manager;
48 this.save_widget = options.save_widget;
45 // TODO: This code smells (and the other `= this` line a couple lines down)
49 // TODO: This code smells (and the other `= this` line a couple lines down)
46 // We need a better way to deal with circular instance references.
50 // We need a better way to deal with circular instance references.
47 keyboard_manager.notebook = this;
51 this.keyboard_manager.notebook = this;
48 this.save_widget = save_widget;
52 this.save_widget.notebook = this;
49 save_widget.notebook = this;
50
53
51 mathjaxutils.init();
54 mathjaxutils.init();
52
55
53 if (marked) {
56 if (marked) {
54 marked.setOptions({
57 marked.setOptions({
55 gfm : true,
58 gfm : true,
56 tables: true,
59 tables: true,
57 langPrefix: "language-",
60 langPrefix: "language-",
58 highlight: function(code, lang) {
61 highlight: function(code, lang) {
59 if (!lang) {
62 if (!lang) {
60 // no language, no highlight
63 // no language, no highlight
61 return code;
64 return code;
62 }
65 }
63 var highlighted;
66 var highlighted;
64 try {
67 try {
65 highlighted = hljs.highlight(lang, code, false);
68 highlighted = hljs.highlight(lang, code, false);
66 } catch(err) {
69 } catch(err) {
67 highlighted = hljs.highlightAuto(code);
70 highlighted = hljs.highlightAuto(code);
68 }
71 }
69 return highlighted.value;
72 return highlighted.value;
70 }
73 }
71 });
74 });
72 }
75 }
73
76
74 // Backwards compatability.
77 // Backwards compatability.
75 IPython.keyboard_manager = this.keyboard_manager;
78 IPython.keyboard_manager = this.keyboard_manager;
76 IPython.save_widget = this.save_widget;
79 IPython.save_widget = this.save_widget;
77 IPython.keyboard = this.keyboard;
80 IPython.keyboard = this.keyboard;
78
81
79 this.options = options = options || {};
80 this.base_url = options.base_url;
81 this.notebook_path = options.notebook_path;
82 this.notebook_name = options.notebook_name;
83 this.element = $(selector);
82 this.element = $(selector);
84 this.element.scroll();
83 this.element.scroll();
85 this.element.data("notebook", this);
84 this.element.data("notebook", this);
86 this.next_prompt_number = 1;
85 this.next_prompt_number = 1;
87 this.session = null;
86 this.session = null;
88 this.kernel = null;
87 this.kernel = null;
89 this.clipboard = null;
88 this.clipboard = null;
90 this.undelete_backup = null;
89 this.undelete_backup = null;
91 this.undelete_index = null;
90 this.undelete_index = null;
92 this.undelete_below = false;
91 this.undelete_below = false;
93 this.paste_enabled = false;
92 this.paste_enabled = false;
94 // It is important to start out in command mode to match the intial mode
93 // It is important to start out in command mode to match the intial mode
95 // of the KeyboardManager.
94 // of the KeyboardManager.
96 this.mode = 'command';
95 this.mode = 'command';
97 this.set_dirty(false);
96 this.set_dirty(false);
98 this.metadata = {};
97 this.metadata = {};
99 this._checkpoint_after_save = false;
98 this._checkpoint_after_save = false;
100 this.last_checkpoint = null;
99 this.last_checkpoint = null;
101 this.checkpoints = [];
100 this.checkpoints = [];
102 this.autosave_interval = 0;
101 this.autosave_interval = 0;
103 this.autosave_timer = null;
102 this.autosave_timer = null;
104 // autosave *at most* every two minutes
103 // autosave *at most* every two minutes
105 this.minimum_autosave_interval = 120000;
104 this.minimum_autosave_interval = 120000;
106 // single worksheet for now
105 // single worksheet for now
107 this.worksheet_metadata = {};
106 this.worksheet_metadata = {};
108 this.notebook_name_blacklist_re = /[\/\\:]/;
107 this.notebook_name_blacklist_re = /[\/\\:]/;
109 this.nbformat = 3; // Increment this when changing the nbformat
108 this.nbformat = 3; // Increment this when changing the nbformat
110 this.nbformat_minor = 0; // Increment this when changing the nbformat
109 this.nbformat_minor = 0; // Increment this when changing the nbformat
111 this.style();
110 this.style();
112 this.create_elements();
111 this.create_elements();
113 this.bind_events();
112 this.bind_events();
114 this.save_notebook = function() { // don't allow save until notebook_loaded
113 this.save_notebook = function() { // don't allow save until notebook_loaded
115 this.save_notebook_error(null, null, "Load failed, save is disabled");
114 this.save_notebook_error(null, null, "Load failed, save is disabled");
116 };
115 };
117 };
116 };
118
117
119 /**
118 /**
120 * Tweak the notebook's CSS style.
119 * Tweak the notebook's CSS style.
121 *
120 *
122 * @method style
121 * @method style
123 */
122 */
124 Notebook.prototype.style = function () {
123 Notebook.prototype.style = function () {
125 $('div#notebook').addClass('border-box-sizing');
124 $('div#notebook').addClass('border-box-sizing');
126 };
125 };
127
126
128 /**
127 /**
129 * Create an HTML and CSS representation of the notebook.
128 * Create an HTML and CSS representation of the notebook.
130 *
129 *
131 * @method create_elements
130 * @method create_elements
132 */
131 */
133 Notebook.prototype.create_elements = function () {
132 Notebook.prototype.create_elements = function () {
134 var that = this;
133 var that = this;
135 this.element.attr('tabindex','-1');
134 this.element.attr('tabindex','-1');
136 this.container = $("<div/>").addClass("container").attr("id", "notebook-container");
135 this.container = $("<div/>").addClass("container").attr("id", "notebook-container");
137 // We add this end_space div to the end of the notebook div to:
136 // We add this end_space div to the end of the notebook div to:
138 // i) provide a margin between the last cell and the end of the notebook
137 // i) provide a margin between the last cell and the end of the notebook
139 // ii) to prevent the div from scrolling up when the last cell is being
138 // ii) to prevent the div from scrolling up when the last cell is being
140 // edited, but is too low on the page, which browsers will do automatically.
139 // edited, but is too low on the page, which browsers will do automatically.
141 var end_space = $('<div/>').addClass('end_space');
140 var end_space = $('<div/>').addClass('end_space');
142 end_space.dblclick(function (e) {
141 end_space.dblclick(function (e) {
143 var ncells = that.ncells();
142 var ncells = that.ncells();
144 that.insert_cell_below('code',ncells-1);
143 that.insert_cell_below('code',ncells-1);
145 });
144 });
146 this.element.append(this.container);
145 this.element.append(this.container);
147 this.container.append(end_space);
146 this.container.append(end_space);
148 };
147 };
149
148
150 /**
149 /**
151 * Bind JavaScript events: key presses and custom IPython events.
150 * Bind JavaScript events: key presses and custom IPython events.
152 *
151 *
153 * @method bind_events
152 * @method bind_events
154 */
153 */
155 Notebook.prototype.bind_events = function () {
154 Notebook.prototype.bind_events = function () {
156 var that = this;
155 var that = this;
157
156
158 this.events.on('set_next_input.Notebook', function (event, data) {
157 this.events.on('set_next_input.Notebook', function (event, data) {
159 var index = that.find_cell_index(data.cell);
158 var index = that.find_cell_index(data.cell);
160 var new_cell = that.insert_cell_below('code',index);
159 var new_cell = that.insert_cell_below('code',index);
161 new_cell.set_text(data.text);
160 new_cell.set_text(data.text);
162 that.dirty = true;
161 that.dirty = true;
163 });
162 });
164
163
165 this.events.on('set_dirty.Notebook', function (event, data) {
164 this.events.on('set_dirty.Notebook', function (event, data) {
166 that.dirty = data.value;
165 that.dirty = data.value;
167 });
166 });
168
167
169 this.events.on('trust_changed.Notebook', function (event, data) {
168 this.events.on('trust_changed.Notebook', function (event, data) {
170 that.trusted = data.value;
169 that.trusted = data.value;
171 });
170 });
172
171
173 this.events.on('select.Cell', function (event, data) {
172 this.events.on('select.Cell', function (event, data) {
174 var index = that.find_cell_index(data.cell);
173 var index = that.find_cell_index(data.cell);
175 that.select(index);
174 that.select(index);
176 });
175 });
177
176
178 this.events.on('edit_mode.Cell', function (event, data) {
177 this.events.on('edit_mode.Cell', function (event, data) {
179 that.handle_edit_mode(data.cell);
178 that.handle_edit_mode(data.cell);
180 });
179 });
181
180
182 this.events.on('command_mode.Cell', function (event, data) {
181 this.events.on('command_mode.Cell', function (event, data) {
183 that.handle_command_mode(data.cell);
182 that.handle_command_mode(data.cell);
184 });
183 });
185
184
186 this.events.on('status_autorestarting.Kernel', function () {
185 this.events.on('status_autorestarting.Kernel', function () {
187 dialog.modal({
186 dialog.modal({
188 title: "Kernel Restarting",
187 title: "Kernel Restarting",
189 body: "The kernel appears to have died. It will restart automatically.",
188 body: "The kernel appears to have died. It will restart automatically.",
190 buttons: {
189 buttons: {
191 OK : {
190 OK : {
192 class : "btn-primary"
191 class : "btn-primary"
193 }
192 }
194 }
193 }
195 });
194 });
196 });
195 });
197
196
198 var collapse_time = function (time) {
197 var collapse_time = function (time) {
199 var app_height = $('#ipython-main-app').height(); // content height
198 var app_height = $('#ipython-main-app').height(); // content height
200 var splitter_height = $('div#pager_splitter').outerHeight(true);
199 var splitter_height = $('div#pager_splitter').outerHeight(true);
201 var new_height = app_height - splitter_height;
200 var new_height = app_height - splitter_height;
202 that.element.animate({height : new_height + 'px'}, time);
201 that.element.animate({height : new_height + 'px'}, time);
203 };
202 };
204
203
205 this.element.bind('collapse_pager', function (event, extrap) {
204 this.element.bind('collapse_pager', function (event, extrap) {
206 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
205 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
207 collapse_time(time);
206 collapse_time(time);
208 });
207 });
209
208
210 var expand_time = function (time) {
209 var expand_time = function (time) {
211 var app_height = $('#ipython-main-app').height(); // content height
210 var app_height = $('#ipython-main-app').height(); // content height
212 var splitter_height = $('div#pager_splitter').outerHeight(true);
211 var splitter_height = $('div#pager_splitter').outerHeight(true);
213 var pager_height = $('div#pager').outerHeight(true);
212 var pager_height = $('div#pager').outerHeight(true);
214 var new_height = app_height - pager_height - splitter_height;
213 var new_height = app_height - pager_height - splitter_height;
215 that.element.animate({height : new_height + 'px'}, time);
214 that.element.animate({height : new_height + 'px'}, time);
216 };
215 };
217
216
218 this.element.bind('expand_pager', function (event, extrap) {
217 this.element.bind('expand_pager', function (event, extrap) {
219 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
218 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
220 expand_time(time);
219 expand_time(time);
221 });
220 });
222
221
223 // Firefox 22 broke $(window).on("beforeunload")
222 // Firefox 22 broke $(window).on("beforeunload")
224 // I'm not sure why or how.
223 // I'm not sure why or how.
225 window.onbeforeunload = function (e) {
224 window.onbeforeunload = function (e) {
226 // TODO: Make killing the kernel configurable.
225 // TODO: Make killing the kernel configurable.
227 var kill_kernel = false;
226 var kill_kernel = false;
228 if (kill_kernel) {
227 if (kill_kernel) {
229 that.session.kill_kernel();
228 that.session.kill_kernel();
230 }
229 }
231 // if we are autosaving, trigger an autosave on nav-away.
230 // if we are autosaving, trigger an autosave on nav-away.
232 // still warn, because if we don't the autosave may fail.
231 // still warn, because if we don't the autosave may fail.
233 if (that.dirty) {
232 if (that.dirty) {
234 if ( that.autosave_interval ) {
233 if ( that.autosave_interval ) {
235 // schedule autosave in a timeout
234 // schedule autosave in a timeout
236 // this gives you a chance to forcefully discard changes
235 // this gives you a chance to forcefully discard changes
237 // by reloading the page if you *really* want to.
236 // by reloading the page if you *really* want to.
238 // the timer doesn't start until you *dismiss* the dialog.
237 // the timer doesn't start until you *dismiss* the dialog.
239 setTimeout(function () {
238 setTimeout(function () {
240 if (that.dirty) {
239 if (that.dirty) {
241 that.save_notebook();
240 that.save_notebook();
242 }
241 }
243 }, 1000);
242 }, 1000);
244 return "Autosave in progress, latest changes may be lost.";
243 return "Autosave in progress, latest changes may be lost.";
245 } else {
244 } else {
246 return "Unsaved changes will be lost.";
245 return "Unsaved changes will be lost.";
247 }
246 }
248 }
247 }
249 // Null is the *only* return value that will make the browser not
248 // Null is the *only* return value that will make the browser not
250 // pop up the "don't leave" dialog.
249 // pop up the "don't leave" dialog.
251 return null;
250 return null;
252 };
251 };
253 };
252 };
254
253
255 /**
254 /**
256 * Set the dirty flag, and trigger the set_dirty.Notebook event
255 * Set the dirty flag, and trigger the set_dirty.Notebook event
257 *
256 *
258 * @method set_dirty
257 * @method set_dirty
259 */
258 */
260 Notebook.prototype.set_dirty = function (value) {
259 Notebook.prototype.set_dirty = function (value) {
261 if (value === undefined) {
260 if (value === undefined) {
262 value = true;
261 value = true;
263 }
262 }
264 if (this.dirty == value) {
263 if (this.dirty == value) {
265 return;
264 return;
266 }
265 }
267 this.events.trigger('set_dirty.Notebook', {value: value});
266 this.events.trigger('set_dirty.Notebook', {value: value});
268 };
267 };
269
268
270 /**
269 /**
271 * Scroll the top of the page to a given cell.
270 * Scroll the top of the page to a given cell.
272 *
271 *
273 * @method scroll_to_cell
272 * @method scroll_to_cell
274 * @param {Number} cell_number An index of the cell to view
273 * @param {Number} cell_number An index of the cell to view
275 * @param {Number} time Animation time in milliseconds
274 * @param {Number} time Animation time in milliseconds
276 * @return {Number} Pixel offset from the top of the container
275 * @return {Number} Pixel offset from the top of the container
277 */
276 */
278 Notebook.prototype.scroll_to_cell = function (cell_number, time) {
277 Notebook.prototype.scroll_to_cell = function (cell_number, time) {
279 var cells = this.get_cells();
278 var cells = this.get_cells();
280 time = time || 0;
279 time = time || 0;
281 cell_number = Math.min(cells.length-1,cell_number);
280 cell_number = Math.min(cells.length-1,cell_number);
282 cell_number = Math.max(0 ,cell_number);
281 cell_number = Math.max(0 ,cell_number);
283 var scroll_value = cells[cell_number].element.position().top-cells[0].element.position().top ;
282 var scroll_value = cells[cell_number].element.position().top-cells[0].element.position().top ;
284 this.element.animate({scrollTop:scroll_value}, time);
283 this.element.animate({scrollTop:scroll_value}, time);
285 return scroll_value;
284 return scroll_value;
286 };
285 };
287
286
288 /**
287 /**
289 * Scroll to the bottom of the page.
288 * Scroll to the bottom of the page.
290 *
289 *
291 * @method scroll_to_bottom
290 * @method scroll_to_bottom
292 */
291 */
293 Notebook.prototype.scroll_to_bottom = function () {
292 Notebook.prototype.scroll_to_bottom = function () {
294 this.element.animate({scrollTop:this.element.get(0).scrollHeight}, 0);
293 this.element.animate({scrollTop:this.element.get(0).scrollHeight}, 0);
295 };
294 };
296
295
297 /**
296 /**
298 * Scroll to the top of the page.
297 * Scroll to the top of the page.
299 *
298 *
300 * @method scroll_to_top
299 * @method scroll_to_top
301 */
300 */
302 Notebook.prototype.scroll_to_top = function () {
301 Notebook.prototype.scroll_to_top = function () {
303 this.element.animate({scrollTop:0}, 0);
302 this.element.animate({scrollTop:0}, 0);
304 };
303 };
305
304
306 // Edit Notebook metadata
305 // Edit Notebook metadata
307
306
308 Notebook.prototype.edit_metadata = function () {
307 Notebook.prototype.edit_metadata = function () {
309 var that = this;
308 var that = this;
310 dialog.edit_metadata(this.metadata, function (md) {
309 dialog.edit_metadata(this.metadata, function (md) {
311 that.metadata = md;
310 that.metadata = md;
312 }, 'Notebook');
311 }, 'Notebook');
313 };
312 };
314
313
315 // Cell indexing, retrieval, etc.
314 // Cell indexing, retrieval, etc.
316
315
317 /**
316 /**
318 * Get all cell elements in the notebook.
317 * Get all cell elements in the notebook.
319 *
318 *
320 * @method get_cell_elements
319 * @method get_cell_elements
321 * @return {jQuery} A selector of all cell elements
320 * @return {jQuery} A selector of all cell elements
322 */
321 */
323 Notebook.prototype.get_cell_elements = function () {
322 Notebook.prototype.get_cell_elements = function () {
324 return this.container.children("div.cell");
323 return this.container.children("div.cell");
325 };
324 };
326
325
327 /**
326 /**
328 * Get a particular cell element.
327 * Get a particular cell element.
329 *
328 *
330 * @method get_cell_element
329 * @method get_cell_element
331 * @param {Number} index An index of a cell to select
330 * @param {Number} index An index of a cell to select
332 * @return {jQuery} A selector of the given cell.
331 * @return {jQuery} A selector of the given cell.
333 */
332 */
334 Notebook.prototype.get_cell_element = function (index) {
333 Notebook.prototype.get_cell_element = function (index) {
335 var result = null;
334 var result = null;
336 var e = this.get_cell_elements().eq(index);
335 var e = this.get_cell_elements().eq(index);
337 if (e.length !== 0) {
336 if (e.length !== 0) {
338 result = e;
337 result = e;
339 }
338 }
340 return result;
339 return result;
341 };
340 };
342
341
343 /**
342 /**
344 * Try to get a particular cell by msg_id.
343 * Try to get a particular cell by msg_id.
345 *
344 *
346 * @method get_msg_cell
345 * @method get_msg_cell
347 * @param {String} msg_id A message UUID
346 * @param {String} msg_id A message UUID
348 * @return {Cell} Cell or null if no cell was found.
347 * @return {Cell} Cell or null if no cell was found.
349 */
348 */
350 Notebook.prototype.get_msg_cell = function (msg_id) {
349 Notebook.prototype.get_msg_cell = function (msg_id) {
351 return codecell.CodeCell.msg_cells[msg_id] || null;
350 return codecell.CodeCell.msg_cells[msg_id] || null;
352 };
351 };
353
352
354 /**
353 /**
355 * Count the cells in this notebook.
354 * Count the cells in this notebook.
356 *
355 *
357 * @method ncells
356 * @method ncells
358 * @return {Number} The number of cells in this notebook
357 * @return {Number} The number of cells in this notebook
359 */
358 */
360 Notebook.prototype.ncells = function () {
359 Notebook.prototype.ncells = function () {
361 return this.get_cell_elements().length;
360 return this.get_cell_elements().length;
362 };
361 };
363
362
364 /**
363 /**
365 * Get all Cell objects in this notebook.
364 * Get all Cell objects in this notebook.
366 *
365 *
367 * @method get_cells
366 * @method get_cells
368 * @return {Array} This notebook's Cell objects
367 * @return {Array} This notebook's Cell objects
369 */
368 */
370 // TODO: we are often calling cells as cells()[i], which we should optimize
369 // TODO: we are often calling cells as cells()[i], which we should optimize
371 // to cells(i) or a new method.
370 // to cells(i) or a new method.
372 Notebook.prototype.get_cells = function () {
371 Notebook.prototype.get_cells = function () {
373 return this.get_cell_elements().toArray().map(function (e) {
372 return this.get_cell_elements().toArray().map(function (e) {
374 return $(e).data("cell");
373 return $(e).data("cell");
375 });
374 });
376 };
375 };
377
376
378 /**
377 /**
379 * Get a Cell object from this notebook.
378 * Get a Cell object from this notebook.
380 *
379 *
381 * @method get_cell
380 * @method get_cell
382 * @param {Number} index An index of a cell to retrieve
381 * @param {Number} index An index of a cell to retrieve
383 * @return {Cell} A particular cell
382 * @return {Cell} A particular cell
384 */
383 */
385 Notebook.prototype.get_cell = function (index) {
384 Notebook.prototype.get_cell = function (index) {
386 var result = null;
385 var result = null;
387 var ce = this.get_cell_element(index);
386 var ce = this.get_cell_element(index);
388 if (ce !== null) {
387 if (ce !== null) {
389 result = ce.data('cell');
388 result = ce.data('cell');
390 }
389 }
391 return result;
390 return result;
392 };
391 };
393
392
394 /**
393 /**
395 * Get the cell below a given cell.
394 * Get the cell below a given cell.
396 *
395 *
397 * @method get_next_cell
396 * @method get_next_cell
398 * @param {Cell} cell The provided cell
397 * @param {Cell} cell The provided cell
399 * @return {Cell} The next cell
398 * @return {Cell} The next cell
400 */
399 */
401 Notebook.prototype.get_next_cell = function (cell) {
400 Notebook.prototype.get_next_cell = function (cell) {
402 var result = null;
401 var result = null;
403 var index = this.find_cell_index(cell);
402 var index = this.find_cell_index(cell);
404 if (this.is_valid_cell_index(index+1)) {
403 if (this.is_valid_cell_index(index+1)) {
405 result = this.get_cell(index+1);
404 result = this.get_cell(index+1);
406 }
405 }
407 return result;
406 return result;
408 };
407 };
409
408
410 /**
409 /**
411 * Get the cell above a given cell.
410 * Get the cell above a given cell.
412 *
411 *
413 * @method get_prev_cell
412 * @method get_prev_cell
414 * @param {Cell} cell The provided cell
413 * @param {Cell} cell The provided cell
415 * @return {Cell} The previous cell
414 * @return {Cell} The previous cell
416 */
415 */
417 Notebook.prototype.get_prev_cell = function (cell) {
416 Notebook.prototype.get_prev_cell = function (cell) {
418 // TODO: off-by-one
417 // TODO: off-by-one
419 // nb.get_prev_cell(nb.get_cell(1)) is null
418 // nb.get_prev_cell(nb.get_cell(1)) is null
420 var result = null;
419 var result = null;
421 var index = this.find_cell_index(cell);
420 var index = this.find_cell_index(cell);
422 if (index !== null && index > 1) {
421 if (index !== null && index > 1) {
423 result = this.get_cell(index-1);
422 result = this.get_cell(index-1);
424 }
423 }
425 return result;
424 return result;
426 };
425 };
427
426
428 /**
427 /**
429 * Get the numeric index of a given cell.
428 * Get the numeric index of a given cell.
430 *
429 *
431 * @method find_cell_index
430 * @method find_cell_index
432 * @param {Cell} cell The provided cell
431 * @param {Cell} cell The provided cell
433 * @return {Number} The cell's numeric index
432 * @return {Number} The cell's numeric index
434 */
433 */
435 Notebook.prototype.find_cell_index = function (cell) {
434 Notebook.prototype.find_cell_index = function (cell) {
436 var result = null;
435 var result = null;
437 this.get_cell_elements().filter(function (index) {
436 this.get_cell_elements().filter(function (index) {
438 if ($(this).data("cell") === cell) {
437 if ($(this).data("cell") === cell) {
439 result = index;
438 result = index;
440 }
439 }
441 });
440 });
442 return result;
441 return result;
443 };
442 };
444
443
445 /**
444 /**
446 * Get a given index , or the selected index if none is provided.
445 * Get a given index , or the selected index if none is provided.
447 *
446 *
448 * @method index_or_selected
447 * @method index_or_selected
449 * @param {Number} index A cell's index
448 * @param {Number} index A cell's index
450 * @return {Number} The given index, or selected index if none is provided.
449 * @return {Number} The given index, or selected index if none is provided.
451 */
450 */
452 Notebook.prototype.index_or_selected = function (index) {
451 Notebook.prototype.index_or_selected = function (index) {
453 var i;
452 var i;
454 if (index === undefined || index === null) {
453 if (index === undefined || index === null) {
455 i = this.get_selected_index();
454 i = this.get_selected_index();
456 if (i === null) {
455 if (i === null) {
457 i = 0;
456 i = 0;
458 }
457 }
459 } else {
458 } else {
460 i = index;
459 i = index;
461 }
460 }
462 return i;
461 return i;
463 };
462 };
464
463
465 /**
464 /**
466 * Get the currently selected cell.
465 * Get the currently selected cell.
467 * @method get_selected_cell
466 * @method get_selected_cell
468 * @return {Cell} The selected cell
467 * @return {Cell} The selected cell
469 */
468 */
470 Notebook.prototype.get_selected_cell = function () {
469 Notebook.prototype.get_selected_cell = function () {
471 var index = this.get_selected_index();
470 var index = this.get_selected_index();
472 return this.get_cell(index);
471 return this.get_cell(index);
473 };
472 };
474
473
475 /**
474 /**
476 * Check whether a cell index is valid.
475 * Check whether a cell index is valid.
477 *
476 *
478 * @method is_valid_cell_index
477 * @method is_valid_cell_index
479 * @param {Number} index A cell index
478 * @param {Number} index A cell index
480 * @return True if the index is valid, false otherwise
479 * @return True if the index is valid, false otherwise
481 */
480 */
482 Notebook.prototype.is_valid_cell_index = function (index) {
481 Notebook.prototype.is_valid_cell_index = function (index) {
483 if (index !== null && index >= 0 && index < this.ncells()) {
482 if (index !== null && index >= 0 && index < this.ncells()) {
484 return true;
483 return true;
485 } else {
484 } else {
486 return false;
485 return false;
487 }
486 }
488 };
487 };
489
488
490 /**
489 /**
491 * Get the index of the currently selected cell.
490 * Get the index of the currently selected cell.
492
491
493 * @method get_selected_index
492 * @method get_selected_index
494 * @return {Number} The selected cell's numeric index
493 * @return {Number} The selected cell's numeric index
495 */
494 */
496 Notebook.prototype.get_selected_index = function () {
495 Notebook.prototype.get_selected_index = function () {
497 var result = null;
496 var result = null;
498 this.get_cell_elements().filter(function (index) {
497 this.get_cell_elements().filter(function (index) {
499 if ($(this).data("cell").selected === true) {
498 if ($(this).data("cell").selected === true) {
500 result = index;
499 result = index;
501 }
500 }
502 });
501 });
503 return result;
502 return result;
504 };
503 };
505
504
506
505
507 // Cell selection.
506 // Cell selection.
508
507
509 /**
508 /**
510 * Programmatically select a cell.
509 * Programmatically select a cell.
511 *
510 *
512 * @method select
511 * @method select
513 * @param {Number} index A cell's index
512 * @param {Number} index A cell's index
514 * @return {Notebook} This notebook
513 * @return {Notebook} This notebook
515 */
514 */
516 Notebook.prototype.select = function (index) {
515 Notebook.prototype.select = function (index) {
517 if (this.is_valid_cell_index(index)) {
516 if (this.is_valid_cell_index(index)) {
518 var sindex = this.get_selected_index();
517 var sindex = this.get_selected_index();
519 if (sindex !== null && index !== sindex) {
518 if (sindex !== null && index !== sindex) {
520 // If we are about to select a different cell, make sure we are
519 // If we are about to select a different cell, make sure we are
521 // first in command mode.
520 // first in command mode.
522 if (this.mode !== 'command') {
521 if (this.mode !== 'command') {
523 this.command_mode();
522 this.command_mode();
524 }
523 }
525 this.get_cell(sindex).unselect();
524 this.get_cell(sindex).unselect();
526 }
525 }
527 var cell = this.get_cell(index);
526 var cell = this.get_cell(index);
528 cell.select();
527 cell.select();
529 if (cell.cell_type === 'heading') {
528 if (cell.cell_type === 'heading') {
530 this.events.trigger('selected_cell_type_changed.Notebook',
529 this.events.trigger('selected_cell_type_changed.Notebook',
531 {'cell_type':cell.cell_type,level:cell.level}
530 {'cell_type':cell.cell_type,level:cell.level}
532 );
531 );
533 } else {
532 } else {
534 this.events.trigger('selected_cell_type_changed.Notebook',
533 this.events.trigger('selected_cell_type_changed.Notebook',
535 {'cell_type':cell.cell_type}
534 {'cell_type':cell.cell_type}
536 );
535 );
537 }
536 }
538 }
537 }
539 return this;
538 return this;
540 };
539 };
541
540
542 /**
541 /**
543 * Programmatically select the next cell.
542 * Programmatically select the next cell.
544 *
543 *
545 * @method select_next
544 * @method select_next
546 * @return {Notebook} This notebook
545 * @return {Notebook} This notebook
547 */
546 */
548 Notebook.prototype.select_next = function () {
547 Notebook.prototype.select_next = function () {
549 var index = this.get_selected_index();
548 var index = this.get_selected_index();
550 this.select(index+1);
549 this.select(index+1);
551 return this;
550 return this;
552 };
551 };
553
552
554 /**
553 /**
555 * Programmatically select the previous cell.
554 * Programmatically select the previous cell.
556 *
555 *
557 * @method select_prev
556 * @method select_prev
558 * @return {Notebook} This notebook
557 * @return {Notebook} This notebook
559 */
558 */
560 Notebook.prototype.select_prev = function () {
559 Notebook.prototype.select_prev = function () {
561 var index = this.get_selected_index();
560 var index = this.get_selected_index();
562 this.select(index-1);
561 this.select(index-1);
563 return this;
562 return this;
564 };
563 };
565
564
566
565
567 // Edit/Command mode
566 // Edit/Command mode
568
567
569 /**
568 /**
570 * Gets the index of the cell that is in edit mode.
569 * Gets the index of the cell that is in edit mode.
571 *
570 *
572 * @method get_edit_index
571 * @method get_edit_index
573 *
572 *
574 * @return index {int}
573 * @return index {int}
575 **/
574 **/
576 Notebook.prototype.get_edit_index = function () {
575 Notebook.prototype.get_edit_index = function () {
577 var result = null;
576 var result = null;
578 this.get_cell_elements().filter(function (index) {
577 this.get_cell_elements().filter(function (index) {
579 if ($(this).data("cell").mode === 'edit') {
578 if ($(this).data("cell").mode === 'edit') {
580 result = index;
579 result = index;
581 }
580 }
582 });
581 });
583 return result;
582 return result;
584 };
583 };
585
584
586 /**
585 /**
587 * Handle when a a cell blurs and the notebook should enter command mode.
586 * Handle when a a cell blurs and the notebook should enter command mode.
588 *
587 *
589 * @method handle_command_mode
588 * @method handle_command_mode
590 * @param [cell] {Cell} Cell to enter command mode on.
589 * @param [cell] {Cell} Cell to enter command mode on.
591 **/
590 **/
592 Notebook.prototype.handle_command_mode = function (cell) {
591 Notebook.prototype.handle_command_mode = function (cell) {
593 if (this.mode !== 'command') {
592 if (this.mode !== 'command') {
594 cell.command_mode();
593 cell.command_mode();
595 this.mode = 'command';
594 this.mode = 'command';
596 this.events.trigger('command_mode.Notebook');
595 this.events.trigger('command_mode.Notebook');
597 this.keyboard_manager.command_mode();
596 this.keyboard_manager.command_mode();
598 }
597 }
599 };
598 };
600
599
601 /**
600 /**
602 * Make the notebook enter command mode.
601 * Make the notebook enter command mode.
603 *
602 *
604 * @method command_mode
603 * @method command_mode
605 **/
604 **/
606 Notebook.prototype.command_mode = function () {
605 Notebook.prototype.command_mode = function () {
607 var cell = this.get_cell(this.get_edit_index());
606 var cell = this.get_cell(this.get_edit_index());
608 if (cell && this.mode !== 'command') {
607 if (cell && this.mode !== 'command') {
609 // We don't call cell.command_mode, but rather call cell.focus_cell()
608 // We don't call cell.command_mode, but rather call cell.focus_cell()
610 // which will blur and CM editor and trigger the call to
609 // which will blur and CM editor and trigger the call to
611 // handle_command_mode.
610 // handle_command_mode.
612 cell.focus_cell();
611 cell.focus_cell();
613 }
612 }
614 };
613 };
615
614
616 /**
615 /**
617 * Handle when a cell fires it's edit_mode event.
616 * Handle when a cell fires it's edit_mode event.
618 *
617 *
619 * @method handle_edit_mode
618 * @method handle_edit_mode
620 * @param [cell] {Cell} Cell to enter edit mode on.
619 * @param [cell] {Cell} Cell to enter edit mode on.
621 **/
620 **/
622 Notebook.prototype.handle_edit_mode = function (cell) {
621 Notebook.prototype.handle_edit_mode = function (cell) {
623 if (cell && this.mode !== 'edit') {
622 if (cell && this.mode !== 'edit') {
624 cell.edit_mode();
623 cell.edit_mode();
625 this.mode = 'edit';
624 this.mode = 'edit';
626 this.events.trigger('edit_mode.Notebook');
625 this.events.trigger('edit_mode.Notebook');
627 this.keyboard_manager.edit_mode();
626 this.keyboard_manager.edit_mode();
628 }
627 }
629 };
628 };
630
629
631 /**
630 /**
632 * Make a cell enter edit mode.
631 * Make a cell enter edit mode.
633 *
632 *
634 * @method edit_mode
633 * @method edit_mode
635 **/
634 **/
636 Notebook.prototype.edit_mode = function () {
635 Notebook.prototype.edit_mode = function () {
637 var cell = this.get_selected_cell();
636 var cell = this.get_selected_cell();
638 if (cell && this.mode !== 'edit') {
637 if (cell && this.mode !== 'edit') {
639 cell.unrender();
638 cell.unrender();
640 cell.focus_editor();
639 cell.focus_editor();
641 }
640 }
642 };
641 };
643
642
644 /**
643 /**
645 * Focus the currently selected cell.
644 * Focus the currently selected cell.
646 *
645 *
647 * @method focus_cell
646 * @method focus_cell
648 **/
647 **/
649 Notebook.prototype.focus_cell = function () {
648 Notebook.prototype.focus_cell = function () {
650 var cell = this.get_selected_cell();
649 var cell = this.get_selected_cell();
651 if (cell === null) {return;} // No cell is selected
650 if (cell === null) {return;} // No cell is selected
652 cell.focus_cell();
651 cell.focus_cell();
653 };
652 };
654
653
655 // Cell movement
654 // Cell movement
656
655
657 /**
656 /**
658 * Move given (or selected) cell up and select it.
657 * Move given (or selected) cell up and select it.
659 *
658 *
660 * @method move_cell_up
659 * @method move_cell_up
661 * @param [index] {integer} cell index
660 * @param [index] {integer} cell index
662 * @return {Notebook} This notebook
661 * @return {Notebook} This notebook
663 **/
662 **/
664 Notebook.prototype.move_cell_up = function (index) {
663 Notebook.prototype.move_cell_up = function (index) {
665 var i = this.index_or_selected(index);
664 var i = this.index_or_selected(index);
666 if (this.is_valid_cell_index(i) && i > 0) {
665 if (this.is_valid_cell_index(i) && i > 0) {
667 var pivot = this.get_cell_element(i-1);
666 var pivot = this.get_cell_element(i-1);
668 var tomove = this.get_cell_element(i);
667 var tomove = this.get_cell_element(i);
669 if (pivot !== null && tomove !== null) {
668 if (pivot !== null && tomove !== null) {
670 tomove.detach();
669 tomove.detach();
671 pivot.before(tomove);
670 pivot.before(tomove);
672 this.select(i-1);
671 this.select(i-1);
673 var cell = this.get_selected_cell();
672 var cell = this.get_selected_cell();
674 cell.focus_cell();
673 cell.focus_cell();
675 }
674 }
676 this.set_dirty(true);
675 this.set_dirty(true);
677 }
676 }
678 return this;
677 return this;
679 };
678 };
680
679
681
680
682 /**
681 /**
683 * Move given (or selected) cell down and select it
682 * Move given (or selected) cell down and select it
684 *
683 *
685 * @method move_cell_down
684 * @method move_cell_down
686 * @param [index] {integer} cell index
685 * @param [index] {integer} cell index
687 * @return {Notebook} This notebook
686 * @return {Notebook} This notebook
688 **/
687 **/
689 Notebook.prototype.move_cell_down = function (index) {
688 Notebook.prototype.move_cell_down = function (index) {
690 var i = this.index_or_selected(index);
689 var i = this.index_or_selected(index);
691 if (this.is_valid_cell_index(i) && this.is_valid_cell_index(i+1)) {
690 if (this.is_valid_cell_index(i) && this.is_valid_cell_index(i+1)) {
692 var pivot = this.get_cell_element(i+1);
691 var pivot = this.get_cell_element(i+1);
693 var tomove = this.get_cell_element(i);
692 var tomove = this.get_cell_element(i);
694 if (pivot !== null && tomove !== null) {
693 if (pivot !== null && tomove !== null) {
695 tomove.detach();
694 tomove.detach();
696 pivot.after(tomove);
695 pivot.after(tomove);
697 this.select(i+1);
696 this.select(i+1);
698 var cell = this.get_selected_cell();
697 var cell = this.get_selected_cell();
699 cell.focus_cell();
698 cell.focus_cell();
700 }
699 }
701 }
700 }
702 this.set_dirty();
701 this.set_dirty();
703 return this;
702 return this;
704 };
703 };
705
704
706
705
707 // Insertion, deletion.
706 // Insertion, deletion.
708
707
709 /**
708 /**
710 * Delete a cell from the notebook.
709 * Delete a cell from the notebook.
711 *
710 *
712 * @method delete_cell
711 * @method delete_cell
713 * @param [index] A cell's numeric index
712 * @param [index] A cell's numeric index
714 * @return {Notebook} This notebook
713 * @return {Notebook} This notebook
715 */
714 */
716 Notebook.prototype.delete_cell = function (index) {
715 Notebook.prototype.delete_cell = function (index) {
717 var i = this.index_or_selected(index);
716 var i = this.index_or_selected(index);
718 var cell = this.get_selected_cell();
717 var cell = this.get_selected_cell();
719 this.undelete_backup = cell.toJSON();
718 this.undelete_backup = cell.toJSON();
720 $('#undelete_cell').removeClass('disabled');
719 $('#undelete_cell').removeClass('disabled');
721 if (this.is_valid_cell_index(i)) {
720 if (this.is_valid_cell_index(i)) {
722 var old_ncells = this.ncells();
721 var old_ncells = this.ncells();
723 var ce = this.get_cell_element(i);
722 var ce = this.get_cell_element(i);
724 ce.remove();
723 ce.remove();
725 if (i === 0) {
724 if (i === 0) {
726 // Always make sure we have at least one cell.
725 // Always make sure we have at least one cell.
727 if (old_ncells === 1) {
726 if (old_ncells === 1) {
728 this.insert_cell_below('code');
727 this.insert_cell_below('code');
729 }
728 }
730 this.select(0);
729 this.select(0);
731 this.undelete_index = 0;
730 this.undelete_index = 0;
732 this.undelete_below = false;
731 this.undelete_below = false;
733 } else if (i === old_ncells-1 && i !== 0) {
732 } else if (i === old_ncells-1 && i !== 0) {
734 this.select(i-1);
733 this.select(i-1);
735 this.undelete_index = i - 1;
734 this.undelete_index = i - 1;
736 this.undelete_below = true;
735 this.undelete_below = true;
737 } else {
736 } else {
738 this.select(i);
737 this.select(i);
739 this.undelete_index = i;
738 this.undelete_index = i;
740 this.undelete_below = false;
739 this.undelete_below = false;
741 }
740 }
742 this.events.trigger('delete.Cell', {'cell': cell, 'index': i});
741 this.events.trigger('delete.Cell', {'cell': cell, 'index': i});
743 this.set_dirty(true);
742 this.set_dirty(true);
744 }
743 }
745 return this;
744 return this;
746 };
745 };
747
746
748 /**
747 /**
749 * Restore the most recently deleted cell.
748 * Restore the most recently deleted cell.
750 *
749 *
751 * @method undelete
750 * @method undelete
752 */
751 */
753 Notebook.prototype.undelete_cell = function() {
752 Notebook.prototype.undelete_cell = function() {
754 if (this.undelete_backup !== null && this.undelete_index !== null) {
753 if (this.undelete_backup !== null && this.undelete_index !== null) {
755 var current_index = this.get_selected_index();
754 var current_index = this.get_selected_index();
756 if (this.undelete_index < current_index) {
755 if (this.undelete_index < current_index) {
757 current_index = current_index + 1;
756 current_index = current_index + 1;
758 }
757 }
759 if (this.undelete_index >= this.ncells()) {
758 if (this.undelete_index >= this.ncells()) {
760 this.select(this.ncells() - 1);
759 this.select(this.ncells() - 1);
761 }
760 }
762 else {
761 else {
763 this.select(this.undelete_index);
762 this.select(this.undelete_index);
764 }
763 }
765 var cell_data = this.undelete_backup;
764 var cell_data = this.undelete_backup;
766 var new_cell = null;
765 var new_cell = null;
767 if (this.undelete_below) {
766 if (this.undelete_below) {
768 new_cell = this.insert_cell_below(cell_data.cell_type);
767 new_cell = this.insert_cell_below(cell_data.cell_type);
769 } else {
768 } else {
770 new_cell = this.insert_cell_above(cell_data.cell_type);
769 new_cell = this.insert_cell_above(cell_data.cell_type);
771 }
770 }
772 new_cell.fromJSON(cell_data);
771 new_cell.fromJSON(cell_data);
773 if (this.undelete_below) {
772 if (this.undelete_below) {
774 this.select(current_index+1);
773 this.select(current_index+1);
775 } else {
774 } else {
776 this.select(current_index);
775 this.select(current_index);
777 }
776 }
778 this.undelete_backup = null;
777 this.undelete_backup = null;
779 this.undelete_index = null;
778 this.undelete_index = null;
780 }
779 }
781 $('#undelete_cell').addClass('disabled');
780 $('#undelete_cell').addClass('disabled');
782 };
781 };
783
782
784 /**
783 /**
785 * Insert a cell so that after insertion the cell is at given index.
784 * Insert a cell so that after insertion the cell is at given index.
786 *
785 *
787 * If cell type is not provided, it will default to the type of the
786 * If cell type is not provided, it will default to the type of the
788 * currently active cell.
787 * currently active cell.
789 *
788 *
790 * Similar to insert_above, but index parameter is mandatory
789 * Similar to insert_above, but index parameter is mandatory
791 *
790 *
792 * Index will be brought back into the accessible range [0,n]
791 * Index will be brought back into the accessible range [0,n]
793 *
792 *
794 * @method insert_cell_at_index
793 * @method insert_cell_at_index
795 * @param [type] {string} in ['code','markdown','heading'], defaults to 'code'
794 * @param [type] {string} in ['code','markdown','heading'], defaults to 'code'
796 * @param [index] {int} a valid index where to insert cell
795 * @param [index] {int} a valid index where to insert cell
797 *
796 *
798 * @return cell {cell|null} created cell or null
797 * @return cell {cell|null} created cell or null
799 **/
798 **/
800 Notebook.prototype.insert_cell_at_index = function(type, index){
799 Notebook.prototype.insert_cell_at_index = function(type, index){
801
800
802 var ncells = this.ncells();
801 var ncells = this.ncells();
803 index = Math.min(index,ncells);
802 index = Math.min(index,ncells);
804 index = Math.max(index,0);
803 index = Math.max(index,0);
805 var cell = null;
804 var cell = null;
806 type = type || this.get_selected_cell().cell_type;
805 type = type || this.get_selected_cell().cell_type;
807
806
808 if (ncells === 0 || this.is_valid_cell_index(index) || index === ncells) {
807 if (ncells === 0 || this.is_valid_cell_index(index) || index === ncells) {
808 var cell_options = {
809 base_url: base_url,
810 notebook_path: notebook_path,
811 notebook_name: notebook_name,
812 events: this.events,
813 config: this.config,
814 keyboard_manager: this.keyboard_manager,
815 notebook: this
816 };
809 if (type === 'code') {
817 if (type === 'code') {
810 cell = new codecell.CodeCell(this.kernel, this.options, this.events, this.config, this.keyboard_manager, this);
818 cell = new codecell.CodeCell(this.kernel, cell_options);
811 cell.set_input_prompt();
819 cell.set_input_prompt();
812 } else if (type === 'markdown') {
820 } else if (type === 'markdown') {
813 cell = new cells.MarkdownCell(this.options, this.events, this.config, this.keyboard_manager, this);
821 cell = new cells.MarkdownCell(cell_options);
814 } else if (type === 'raw') {
822 } else if (type === 'raw') {
815 cell = new cells.RawCell(this.options, this.events, this.config, this.keyboard_manager, this);
823 cell = new cells.RawCell(cell_options);
816 } else if (type === 'heading') {
824 } else if (type === 'heading') {
817 cell = new cells.HeadingCell(this.options, this.events, this.config, this.keyboard_manager, this);
825 cell = new cells.HeadingCell(cell_options);
818 }
826 }
819
827
820 if(this._insert_element_at_index(cell.element,index)) {
828 if(this._insert_element_at_index(cell.element,index)) {
821 cell.render();
829 cell.render();
822 this.events.trigger('create.Cell', {'cell': cell, 'index': index});
830 this.events.trigger('create.Cell', {'cell': cell, 'index': index});
823 cell.refresh();
831 cell.refresh();
824 // We used to select the cell after we refresh it, but there
832 // We used to select the cell after we refresh it, but there
825 // are now cases were this method is called where select is
833 // are now cases were this method is called where select is
826 // not appropriate. The selection logic should be handled by the
834 // not appropriate. The selection logic should be handled by the
827 // caller of the the top level insert_cell methods.
835 // caller of the the top level insert_cell methods.
828 this.set_dirty(true);
836 this.set_dirty(true);
829 }
837 }
830 }
838 }
831 return cell;
839 return cell;
832
840
833 };
841 };
834
842
835 /**
843 /**
836 * Insert an element at given cell index.
844 * Insert an element at given cell index.
837 *
845 *
838 * @method _insert_element_at_index
846 * @method _insert_element_at_index
839 * @param element {dom element} a cell element
847 * @param element {dom element} a cell element
840 * @param [index] {int} a valid index where to inser cell
848 * @param [index] {int} a valid index where to inser cell
841 * @private
849 * @private
842 *
850 *
843 * return true if everything whent fine.
851 * return true if everything whent fine.
844 **/
852 **/
845 Notebook.prototype._insert_element_at_index = function(element, index){
853 Notebook.prototype._insert_element_at_index = function(element, index){
846 if (element === undefined){
854 if (element === undefined){
847 return false;
855 return false;
848 }
856 }
849
857
850 var ncells = this.ncells();
858 var ncells = this.ncells();
851
859
852 if (ncells === 0) {
860 if (ncells === 0) {
853 // special case append if empty
861 // special case append if empty
854 this.element.find('div.end_space').before(element);
862 this.element.find('div.end_space').before(element);
855 } else if ( ncells === index ) {
863 } else if ( ncells === index ) {
856 // special case append it the end, but not empty
864 // special case append it the end, but not empty
857 this.get_cell_element(index-1).after(element);
865 this.get_cell_element(index-1).after(element);
858 } else if (this.is_valid_cell_index(index)) {
866 } else if (this.is_valid_cell_index(index)) {
859 // otherwise always somewhere to append to
867 // otherwise always somewhere to append to
860 this.get_cell_element(index).before(element);
868 this.get_cell_element(index).before(element);
861 } else {
869 } else {
862 return false;
870 return false;
863 }
871 }
864
872
865 if (this.undelete_index !== null && index <= this.undelete_index) {
873 if (this.undelete_index !== null && index <= this.undelete_index) {
866 this.undelete_index = this.undelete_index + 1;
874 this.undelete_index = this.undelete_index + 1;
867 this.set_dirty(true);
875 this.set_dirty(true);
868 }
876 }
869 return true;
877 return true;
870 };
878 };
871
879
872 /**
880 /**
873 * Insert a cell of given type above given index, or at top
881 * Insert a cell of given type above given index, or at top
874 * of notebook if index smaller than 0.
882 * of notebook if index smaller than 0.
875 *
883 *
876 * default index value is the one of currently selected cell
884 * default index value is the one of currently selected cell
877 *
885 *
878 * @method insert_cell_above
886 * @method insert_cell_above
879 * @param [type] {string} cell type
887 * @param [type] {string} cell type
880 * @param [index] {integer}
888 * @param [index] {integer}
881 *
889 *
882 * @return handle to created cell or null
890 * @return handle to created cell or null
883 **/
891 **/
884 Notebook.prototype.insert_cell_above = function (type, index) {
892 Notebook.prototype.insert_cell_above = function (type, index) {
885 index = this.index_or_selected(index);
893 index = this.index_or_selected(index);
886 return this.insert_cell_at_index(type, index);
894 return this.insert_cell_at_index(type, index);
887 };
895 };
888
896
889 /**
897 /**
890 * Insert a cell of given type below given index, or at bottom
898 * Insert a cell of given type below given index, or at bottom
891 * of notebook if index greater than number of cells
899 * of notebook if index greater than number of cells
892 *
900 *
893 * default index value is the one of currently selected cell
901 * default index value is the one of currently selected cell
894 *
902 *
895 * @method insert_cell_below
903 * @method insert_cell_below
896 * @param [type] {string} cell type
904 * @param [type] {string} cell type
897 * @param [index] {integer}
905 * @param [index] {integer}
898 *
906 *
899 * @return handle to created cell or null
907 * @return handle to created cell or null
900 *
908 *
901 **/
909 **/
902 Notebook.prototype.insert_cell_below = function (type, index) {
910 Notebook.prototype.insert_cell_below = function (type, index) {
903 index = this.index_or_selected(index);
911 index = this.index_or_selected(index);
904 return this.insert_cell_at_index(type, index+1);
912 return this.insert_cell_at_index(type, index+1);
905 };
913 };
906
914
907
915
908 /**
916 /**
909 * Insert cell at end of notebook
917 * Insert cell at end of notebook
910 *
918 *
911 * @method insert_cell_at_bottom
919 * @method insert_cell_at_bottom
912 * @param {String} type cell type
920 * @param {String} type cell type
913 *
921 *
914 * @return the added cell; or null
922 * @return the added cell; or null
915 **/
923 **/
916 Notebook.prototype.insert_cell_at_bottom = function (type){
924 Notebook.prototype.insert_cell_at_bottom = function (type){
917 var len = this.ncells();
925 var len = this.ncells();
918 return this.insert_cell_below(type,len-1);
926 return this.insert_cell_below(type,len-1);
919 };
927 };
920
928
921 /**
929 /**
922 * Turn a cell into a code cell.
930 * Turn a cell into a code cell.
923 *
931 *
924 * @method to_code
932 * @method to_code
925 * @param {Number} [index] A cell's index
933 * @param {Number} [index] A cell's index
926 */
934 */
927 Notebook.prototype.to_code = function (index) {
935 Notebook.prototype.to_code = function (index) {
928 var i = this.index_or_selected(index);
936 var i = this.index_or_selected(index);
929 if (this.is_valid_cell_index(i)) {
937 if (this.is_valid_cell_index(i)) {
930 var source_element = this.get_cell_element(i);
938 var source_element = this.get_cell_element(i);
931 var source_cell = source_element.data("cell");
939 var source_cell = source_element.data("cell");
932 if (!(source_cell instanceof codecell.CodeCell)) {
940 if (!(source_cell instanceof codecell.CodeCell)) {
933 var target_cell = this.insert_cell_below('code',i);
941 var target_cell = this.insert_cell_below('code',i);
934 var text = source_cell.get_text();
942 var text = source_cell.get_text();
935 if (text === source_cell.placeholder) {
943 if (text === source_cell.placeholder) {
936 text = '';
944 text = '';
937 }
945 }
938 target_cell.set_text(text);
946 target_cell.set_text(text);
939 // make this value the starting point, so that we can only undo
947 // make this value the starting point, so that we can only undo
940 // to this state, instead of a blank cell
948 // to this state, instead of a blank cell
941 target_cell.code_mirror.clearHistory();
949 target_cell.code_mirror.clearHistory();
942 source_element.remove();
950 source_element.remove();
943 this.select(i);
951 this.select(i);
944 var cursor = source_cell.code_mirror.getCursor();
952 var cursor = source_cell.code_mirror.getCursor();
945 target_cell.code_mirror.setCursor(cursor);
953 target_cell.code_mirror.setCursor(cursor);
946 this.set_dirty(true);
954 this.set_dirty(true);
947 }
955 }
948 }
956 }
949 };
957 };
950
958
951 /**
959 /**
952 * Turn a cell into a Markdown cell.
960 * Turn a cell into a Markdown cell.
953 *
961 *
954 * @method to_markdown
962 * @method to_markdown
955 * @param {Number} [index] A cell's index
963 * @param {Number} [index] A cell's index
956 */
964 */
957 Notebook.prototype.to_markdown = function (index) {
965 Notebook.prototype.to_markdown = function (index) {
958 var i = this.index_or_selected(index);
966 var i = this.index_or_selected(index);
959 if (this.is_valid_cell_index(i)) {
967 if (this.is_valid_cell_index(i)) {
960 var source_element = this.get_cell_element(i);
968 var source_element = this.get_cell_element(i);
961 var source_cell = source_element.data("cell");
969 var source_cell = source_element.data("cell");
962 if (!(source_cell instanceof cells.MarkdownCell)) {
970 if (!(source_cell instanceof cells.MarkdownCell)) {
963 var target_cell = this.insert_cell_below('markdown',i);
971 var target_cell = this.insert_cell_below('markdown',i);
964 var text = source_cell.get_text();
972 var text = source_cell.get_text();
965 if (text === source_cell.placeholder) {
973 if (text === source_cell.placeholder) {
966 text = '';
974 text = '';
967 }
975 }
968 // We must show the editor before setting its contents
976 // We must show the editor before setting its contents
969 target_cell.unrender();
977 target_cell.unrender();
970 target_cell.set_text(text);
978 target_cell.set_text(text);
971 // make this value the starting point, so that we can only undo
979 // make this value the starting point, so that we can only undo
972 // to this state, instead of a blank cell
980 // to this state, instead of a blank cell
973 target_cell.code_mirror.clearHistory();
981 target_cell.code_mirror.clearHistory();
974 source_element.remove();
982 source_element.remove();
975 this.select(i);
983 this.select(i);
976 if ((source_cell instanceof cells.TextCell) && source_cell.rendered) {
984 if ((source_cell instanceof cells.TextCell) && source_cell.rendered) {
977 target_cell.render();
985 target_cell.render();
978 }
986 }
979 var cursor = source_cell.code_mirror.getCursor();
987 var cursor = source_cell.code_mirror.getCursor();
980 target_cell.code_mirror.setCursor(cursor);
988 target_cell.code_mirror.setCursor(cursor);
981 this.set_dirty(true);
989 this.set_dirty(true);
982 }
990 }
983 }
991 }
984 };
992 };
985
993
986 /**
994 /**
987 * Turn a cell into a raw text cell.
995 * Turn a cell into a raw text cell.
988 *
996 *
989 * @method to_raw
997 * @method to_raw
990 * @param {Number} [index] A cell's index
998 * @param {Number} [index] A cell's index
991 */
999 */
992 Notebook.prototype.to_raw = function (index) {
1000 Notebook.prototype.to_raw = function (index) {
993 var i = this.index_or_selected(index);
1001 var i = this.index_or_selected(index);
994 if (this.is_valid_cell_index(i)) {
1002 if (this.is_valid_cell_index(i)) {
995 var source_element = this.get_cell_element(i);
1003 var source_element = this.get_cell_element(i);
996 var source_cell = source_element.data("cell");
1004 var source_cell = source_element.data("cell");
997 var target_cell = null;
1005 var target_cell = null;
998 if (!(source_cell instanceof cells.RawCell)) {
1006 if (!(source_cell instanceof cells.RawCell)) {
999 target_cell = this.insert_cell_below('raw',i);
1007 target_cell = this.insert_cell_below('raw',i);
1000 var text = source_cell.get_text();
1008 var text = source_cell.get_text();
1001 if (text === source_cell.placeholder) {
1009 if (text === source_cell.placeholder) {
1002 text = '';
1010 text = '';
1003 }
1011 }
1004 // We must show the editor before setting its contents
1012 // We must show the editor before setting its contents
1005 target_cell.unrender();
1013 target_cell.unrender();
1006 target_cell.set_text(text);
1014 target_cell.set_text(text);
1007 // make this value the starting point, so that we can only undo
1015 // make this value the starting point, so that we can only undo
1008 // to this state, instead of a blank cell
1016 // to this state, instead of a blank cell
1009 target_cell.code_mirror.clearHistory();
1017 target_cell.code_mirror.clearHistory();
1010 source_element.remove();
1018 source_element.remove();
1011 this.select(i);
1019 this.select(i);
1012 var cursor = source_cell.code_mirror.getCursor();
1020 var cursor = source_cell.code_mirror.getCursor();
1013 target_cell.code_mirror.setCursor(cursor);
1021 target_cell.code_mirror.setCursor(cursor);
1014 this.set_dirty(true);
1022 this.set_dirty(true);
1015 }
1023 }
1016 }
1024 }
1017 };
1025 };
1018
1026
1019 /**
1027 /**
1020 * Turn a cell into a heading cell.
1028 * Turn a cell into a heading cell.
1021 *
1029 *
1022 * @method to_heading
1030 * @method to_heading
1023 * @param {Number} [index] A cell's index
1031 * @param {Number} [index] A cell's index
1024 * @param {Number} [level] A heading level (e.g., 1 becomes &lt;h1&gt;)
1032 * @param {Number} [level] A heading level (e.g., 1 becomes &lt;h1&gt;)
1025 */
1033 */
1026 Notebook.prototype.to_heading = function (index, level) {
1034 Notebook.prototype.to_heading = function (index, level) {
1027 level = level || 1;
1035 level = level || 1;
1028 var i = this.index_or_selected(index);
1036 var i = this.index_or_selected(index);
1029 if (this.is_valid_cell_index(i)) {
1037 if (this.is_valid_cell_index(i)) {
1030 var source_element = this.get_cell_element(i);
1038 var source_element = this.get_cell_element(i);
1031 var source_cell = source_element.data("cell");
1039 var source_cell = source_element.data("cell");
1032 var target_cell = null;
1040 var target_cell = null;
1033 if (source_cell instanceof cells.HeadingCell) {
1041 if (source_cell instanceof cells.HeadingCell) {
1034 source_cell.set_level(level);
1042 source_cell.set_level(level);
1035 } else {
1043 } else {
1036 target_cell = this.insert_cell_below('heading',i);
1044 target_cell = this.insert_cell_below('heading',i);
1037 var text = source_cell.get_text();
1045 var text = source_cell.get_text();
1038 if (text === source_cell.placeholder) {
1046 if (text === source_cell.placeholder) {
1039 text = '';
1047 text = '';
1040 }
1048 }
1041 // We must show the editor before setting its contents
1049 // We must show the editor before setting its contents
1042 target_cell.set_level(level);
1050 target_cell.set_level(level);
1043 target_cell.unrender();
1051 target_cell.unrender();
1044 target_cell.set_text(text);
1052 target_cell.set_text(text);
1045 // make this value the starting point, so that we can only undo
1053 // make this value the starting point, so that we can only undo
1046 // to this state, instead of a blank cell
1054 // to this state, instead of a blank cell
1047 target_cell.code_mirror.clearHistory();
1055 target_cell.code_mirror.clearHistory();
1048 source_element.remove();
1056 source_element.remove();
1049 this.select(i);
1057 this.select(i);
1050 var cursor = source_cell.code_mirror.getCursor();
1058 var cursor = source_cell.code_mirror.getCursor();
1051 target_cell.code_mirror.setCursor(cursor);
1059 target_cell.code_mirror.setCursor(cursor);
1052 if ((source_cell instanceof cells.TextCell) && source_cell.rendered) {
1060 if ((source_cell instanceof cells.TextCell) && source_cell.rendered) {
1053 target_cell.render();
1061 target_cell.render();
1054 }
1062 }
1055 }
1063 }
1056 this.set_dirty(true);
1064 this.set_dirty(true);
1057 this.events.trigger('selected_cell_type_changed.Notebook',
1065 this.events.trigger('selected_cell_type_changed.Notebook',
1058 {'cell_type':'heading',level:level}
1066 {'cell_type':'heading',level:level}
1059 );
1067 );
1060 }
1068 }
1061 };
1069 };
1062
1070
1063
1071
1064 // Cut/Copy/Paste
1072 // Cut/Copy/Paste
1065
1073
1066 /**
1074 /**
1067 * Enable UI elements for pasting cells.
1075 * Enable UI elements for pasting cells.
1068 *
1076 *
1069 * @method enable_paste
1077 * @method enable_paste
1070 */
1078 */
1071 Notebook.prototype.enable_paste = function () {
1079 Notebook.prototype.enable_paste = function () {
1072 var that = this;
1080 var that = this;
1073 if (!this.paste_enabled) {
1081 if (!this.paste_enabled) {
1074 $('#paste_cell_replace').removeClass('disabled')
1082 $('#paste_cell_replace').removeClass('disabled')
1075 .on('click', function () {that.paste_cell_replace();});
1083 .on('click', function () {that.paste_cell_replace();});
1076 $('#paste_cell_above').removeClass('disabled')
1084 $('#paste_cell_above').removeClass('disabled')
1077 .on('click', function () {that.paste_cell_above();});
1085 .on('click', function () {that.paste_cell_above();});
1078 $('#paste_cell_below').removeClass('disabled')
1086 $('#paste_cell_below').removeClass('disabled')
1079 .on('click', function () {that.paste_cell_below();});
1087 .on('click', function () {that.paste_cell_below();});
1080 this.paste_enabled = true;
1088 this.paste_enabled = true;
1081 }
1089 }
1082 };
1090 };
1083
1091
1084 /**
1092 /**
1085 * Disable UI elements for pasting cells.
1093 * Disable UI elements for pasting cells.
1086 *
1094 *
1087 * @method disable_paste
1095 * @method disable_paste
1088 */
1096 */
1089 Notebook.prototype.disable_paste = function () {
1097 Notebook.prototype.disable_paste = function () {
1090 if (this.paste_enabled) {
1098 if (this.paste_enabled) {
1091 $('#paste_cell_replace').addClass('disabled').off('click');
1099 $('#paste_cell_replace').addClass('disabled').off('click');
1092 $('#paste_cell_above').addClass('disabled').off('click');
1100 $('#paste_cell_above').addClass('disabled').off('click');
1093 $('#paste_cell_below').addClass('disabled').off('click');
1101 $('#paste_cell_below').addClass('disabled').off('click');
1094 this.paste_enabled = false;
1102 this.paste_enabled = false;
1095 }
1103 }
1096 };
1104 };
1097
1105
1098 /**
1106 /**
1099 * Cut a cell.
1107 * Cut a cell.
1100 *
1108 *
1101 * @method cut_cell
1109 * @method cut_cell
1102 */
1110 */
1103 Notebook.prototype.cut_cell = function () {
1111 Notebook.prototype.cut_cell = function () {
1104 this.copy_cell();
1112 this.copy_cell();
1105 this.delete_cell();
1113 this.delete_cell();
1106 };
1114 };
1107
1115
1108 /**
1116 /**
1109 * Copy a cell.
1117 * Copy a cell.
1110 *
1118 *
1111 * @method copy_cell
1119 * @method copy_cell
1112 */
1120 */
1113 Notebook.prototype.copy_cell = function () {
1121 Notebook.prototype.copy_cell = function () {
1114 var cell = this.get_selected_cell();
1122 var cell = this.get_selected_cell();
1115 this.clipboard = cell.toJSON();
1123 this.clipboard = cell.toJSON();
1116 this.enable_paste();
1124 this.enable_paste();
1117 };
1125 };
1118
1126
1119 /**
1127 /**
1120 * Replace the selected cell with a cell in the clipboard.
1128 * Replace the selected cell with a cell in the clipboard.
1121 *
1129 *
1122 * @method paste_cell_replace
1130 * @method paste_cell_replace
1123 */
1131 */
1124 Notebook.prototype.paste_cell_replace = function () {
1132 Notebook.prototype.paste_cell_replace = function () {
1125 if (this.clipboard !== null && this.paste_enabled) {
1133 if (this.clipboard !== null && this.paste_enabled) {
1126 var cell_data = this.clipboard;
1134 var cell_data = this.clipboard;
1127 var new_cell = this.insert_cell_above(cell_data.cell_type);
1135 var new_cell = this.insert_cell_above(cell_data.cell_type);
1128 new_cell.fromJSON(cell_data);
1136 new_cell.fromJSON(cell_data);
1129 var old_cell = this.get_next_cell(new_cell);
1137 var old_cell = this.get_next_cell(new_cell);
1130 this.delete_cell(this.find_cell_index(old_cell));
1138 this.delete_cell(this.find_cell_index(old_cell));
1131 this.select(this.find_cell_index(new_cell));
1139 this.select(this.find_cell_index(new_cell));
1132 }
1140 }
1133 };
1141 };
1134
1142
1135 /**
1143 /**
1136 * Paste a cell from the clipboard above the selected cell.
1144 * Paste a cell from the clipboard above the selected cell.
1137 *
1145 *
1138 * @method paste_cell_above
1146 * @method paste_cell_above
1139 */
1147 */
1140 Notebook.prototype.paste_cell_above = function () {
1148 Notebook.prototype.paste_cell_above = function () {
1141 if (this.clipboard !== null && this.paste_enabled) {
1149 if (this.clipboard !== null && this.paste_enabled) {
1142 var cell_data = this.clipboard;
1150 var cell_data = this.clipboard;
1143 var new_cell = this.insert_cell_above(cell_data.cell_type);
1151 var new_cell = this.insert_cell_above(cell_data.cell_type);
1144 new_cell.fromJSON(cell_data);
1152 new_cell.fromJSON(cell_data);
1145 new_cell.focus_cell();
1153 new_cell.focus_cell();
1146 }
1154 }
1147 };
1155 };
1148
1156
1149 /**
1157 /**
1150 * Paste a cell from the clipboard below the selected cell.
1158 * Paste a cell from the clipboard below the selected cell.
1151 *
1159 *
1152 * @method paste_cell_below
1160 * @method paste_cell_below
1153 */
1161 */
1154 Notebook.prototype.paste_cell_below = function () {
1162 Notebook.prototype.paste_cell_below = function () {
1155 if (this.clipboard !== null && this.paste_enabled) {
1163 if (this.clipboard !== null && this.paste_enabled) {
1156 var cell_data = this.clipboard;
1164 var cell_data = this.clipboard;
1157 var new_cell = this.insert_cell_below(cell_data.cell_type);
1165 var new_cell = this.insert_cell_below(cell_data.cell_type);
1158 new_cell.fromJSON(cell_data);
1166 new_cell.fromJSON(cell_data);
1159 new_cell.focus_cell();
1167 new_cell.focus_cell();
1160 }
1168 }
1161 };
1169 };
1162
1170
1163 // Split/merge
1171 // Split/merge
1164
1172
1165 /**
1173 /**
1166 * Split the selected cell into two, at the cursor.
1174 * Split the selected cell into two, at the cursor.
1167 *
1175 *
1168 * @method split_cell
1176 * @method split_cell
1169 */
1177 */
1170 Notebook.prototype.split_cell = function () {
1178 Notebook.prototype.split_cell = function () {
1171 var mdc = cells.MarkdownCell;
1179 var mdc = cells.MarkdownCell;
1172 var rc = cells.RawCell;
1180 var rc = cells.RawCell;
1173 var cell = this.get_selected_cell();
1181 var cell = this.get_selected_cell();
1174 if (cell.is_splittable()) {
1182 if (cell.is_splittable()) {
1175 var texta = cell.get_pre_cursor();
1183 var texta = cell.get_pre_cursor();
1176 var textb = cell.get_post_cursor();
1184 var textb = cell.get_post_cursor();
1177 if (cell instanceof codecell.CodeCell) {
1185 if (cell instanceof codecell.CodeCell) {
1178 // In this case the operations keep the notebook in its existing mode
1186 // In this case the operations keep the notebook in its existing mode
1179 // so we don't need to do any post-op mode changes.
1187 // so we don't need to do any post-op mode changes.
1180 cell.set_text(textb);
1188 cell.set_text(textb);
1181 var new_cell = this.insert_cell_above('code');
1189 var new_cell = this.insert_cell_above('code');
1182 new_cell.set_text(texta);
1190 new_cell.set_text(texta);
1183 } else if ((cell instanceof mdc && !cell.rendered) || (cell instanceof rc)) {
1191 } else if ((cell instanceof mdc && !cell.rendered) || (cell instanceof rc)) {
1184 // We know cell is !rendered so we can use set_text.
1192 // We know cell is !rendered so we can use set_text.
1185 cell.set_text(textb);
1193 cell.set_text(textb);
1186 var new_cell = this.insert_cell_above(cell.cell_type);
1194 var new_cell = this.insert_cell_above(cell.cell_type);
1187 // Unrender the new cell so we can call set_text.
1195 // Unrender the new cell so we can call set_text.
1188 new_cell.unrender();
1196 new_cell.unrender();
1189 new_cell.set_text(texta);
1197 new_cell.set_text(texta);
1190 }
1198 }
1191 }
1199 }
1192 };
1200 };
1193
1201
1194 /**
1202 /**
1195 * Combine the selected cell into the cell above it.
1203 * Combine the selected cell into the cell above it.
1196 *
1204 *
1197 * @method merge_cell_above
1205 * @method merge_cell_above
1198 */
1206 */
1199 Notebook.prototype.merge_cell_above = function () {
1207 Notebook.prototype.merge_cell_above = function () {
1200 var mdc = cells.MarkdownCell;
1208 var mdc = cells.MarkdownCell;
1201 var rc = cells.RawCell;
1209 var rc = cells.RawCell;
1202 var index = this.get_selected_index();
1210 var index = this.get_selected_index();
1203 var cell = this.get_cell(index);
1211 var cell = this.get_cell(index);
1204 var render = cell.rendered;
1212 var render = cell.rendered;
1205 if (!cell.is_mergeable()) {
1213 if (!cell.is_mergeable()) {
1206 return;
1214 return;
1207 }
1215 }
1208 if (index > 0) {
1216 if (index > 0) {
1209 var upper_cell = this.get_cell(index-1);
1217 var upper_cell = this.get_cell(index-1);
1210 if (!upper_cell.is_mergeable()) {
1218 if (!upper_cell.is_mergeable()) {
1211 return;
1219 return;
1212 }
1220 }
1213 var upper_text = upper_cell.get_text();
1221 var upper_text = upper_cell.get_text();
1214 var text = cell.get_text();
1222 var text = cell.get_text();
1215 if (cell instanceof codecell.CodeCell) {
1223 if (cell instanceof codecell.CodeCell) {
1216 cell.set_text(upper_text+'\n'+text);
1224 cell.set_text(upper_text+'\n'+text);
1217 } else if ((cell instanceof mdc) || (cell instanceof rc)) {
1225 } else if ((cell instanceof mdc) || (cell instanceof rc)) {
1218 cell.unrender(); // Must unrender before we set_text.
1226 cell.unrender(); // Must unrender before we set_text.
1219 cell.set_text(upper_text+'\n\n'+text);
1227 cell.set_text(upper_text+'\n\n'+text);
1220 if (render) {
1228 if (render) {
1221 // The rendered state of the final cell should match
1229 // The rendered state of the final cell should match
1222 // that of the original selected cell;
1230 // that of the original selected cell;
1223 cell.render();
1231 cell.render();
1224 }
1232 }
1225 }
1233 }
1226 this.delete_cell(index-1);
1234 this.delete_cell(index-1);
1227 this.select(this.find_cell_index(cell));
1235 this.select(this.find_cell_index(cell));
1228 }
1236 }
1229 };
1237 };
1230
1238
1231 /**
1239 /**
1232 * Combine the selected cell into the cell below it.
1240 * Combine the selected cell into the cell below it.
1233 *
1241 *
1234 * @method merge_cell_below
1242 * @method merge_cell_below
1235 */
1243 */
1236 Notebook.prototype.merge_cell_below = function () {
1244 Notebook.prototype.merge_cell_below = function () {
1237 var mdc = cells.MarkdownCell;
1245 var mdc = cells.MarkdownCell;
1238 var rc = cells.RawCell;
1246 var rc = cells.RawCell;
1239 var index = this.get_selected_index();
1247 var index = this.get_selected_index();
1240 var cell = this.get_cell(index);
1248 var cell = this.get_cell(index);
1241 var render = cell.rendered;
1249 var render = cell.rendered;
1242 if (!cell.is_mergeable()) {
1250 if (!cell.is_mergeable()) {
1243 return;
1251 return;
1244 }
1252 }
1245 if (index < this.ncells()-1) {
1253 if (index < this.ncells()-1) {
1246 var lower_cell = this.get_cell(index+1);
1254 var lower_cell = this.get_cell(index+1);
1247 if (!lower_cell.is_mergeable()) {
1255 if (!lower_cell.is_mergeable()) {
1248 return;
1256 return;
1249 }
1257 }
1250 var lower_text = lower_cell.get_text();
1258 var lower_text = lower_cell.get_text();
1251 var text = cell.get_text();
1259 var text = cell.get_text();
1252 if (cell instanceof codecell.CodeCell) {
1260 if (cell instanceof codecell.CodeCell) {
1253 cell.set_text(text+'\n'+lower_text);
1261 cell.set_text(text+'\n'+lower_text);
1254 } else if ((cell instanceof mdc) || (cell instanceof rc)) {
1262 } else if ((cell instanceof mdc) || (cell instanceof rc)) {
1255 cell.unrender(); // Must unrender before we set_text.
1263 cell.unrender(); // Must unrender before we set_text.
1256 cell.set_text(text+'\n\n'+lower_text);
1264 cell.set_text(text+'\n\n'+lower_text);
1257 if (render) {
1265 if (render) {
1258 // The rendered state of the final cell should match
1266 // The rendered state of the final cell should match
1259 // that of the original selected cell;
1267 // that of the original selected cell;
1260 cell.render();
1268 cell.render();
1261 }
1269 }
1262 }
1270 }
1263 this.delete_cell(index+1);
1271 this.delete_cell(index+1);
1264 this.select(this.find_cell_index(cell));
1272 this.select(this.find_cell_index(cell));
1265 }
1273 }
1266 };
1274 };
1267
1275
1268
1276
1269 // Cell collapsing and output clearing
1277 // Cell collapsing and output clearing
1270
1278
1271 /**
1279 /**
1272 * Hide a cell's output.
1280 * Hide a cell's output.
1273 *
1281 *
1274 * @method collapse_output
1282 * @method collapse_output
1275 * @param {Number} index A cell's numeric index
1283 * @param {Number} index A cell's numeric index
1276 */
1284 */
1277 Notebook.prototype.collapse_output = function (index) {
1285 Notebook.prototype.collapse_output = function (index) {
1278 var i = this.index_or_selected(index);
1286 var i = this.index_or_selected(index);
1279 var cell = this.get_cell(i);
1287 var cell = this.get_cell(i);
1280 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1288 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1281 cell.collapse_output();
1289 cell.collapse_output();
1282 this.set_dirty(true);
1290 this.set_dirty(true);
1283 }
1291 }
1284 };
1292 };
1285
1293
1286 /**
1294 /**
1287 * Hide each code cell's output area.
1295 * Hide each code cell's output area.
1288 *
1296 *
1289 * @method collapse_all_output
1297 * @method collapse_all_output
1290 */
1298 */
1291 Notebook.prototype.collapse_all_output = function () {
1299 Notebook.prototype.collapse_all_output = function () {
1292 $.map(this.get_cells(), function (cell, i) {
1300 $.map(this.get_cells(), function (cell, i) {
1293 if (cell instanceof codecell.CodeCell) {
1301 if (cell instanceof codecell.CodeCell) {
1294 cell.collapse_output();
1302 cell.collapse_output();
1295 }
1303 }
1296 });
1304 });
1297 // this should not be set if the `collapse` key is removed from nbformat
1305 // this should not be set if the `collapse` key is removed from nbformat
1298 this.set_dirty(true);
1306 this.set_dirty(true);
1299 };
1307 };
1300
1308
1301 /**
1309 /**
1302 * Show a cell's output.
1310 * Show a cell's output.
1303 *
1311 *
1304 * @method expand_output
1312 * @method expand_output
1305 * @param {Number} index A cell's numeric index
1313 * @param {Number} index A cell's numeric index
1306 */
1314 */
1307 Notebook.prototype.expand_output = function (index) {
1315 Notebook.prototype.expand_output = function (index) {
1308 var i = this.index_or_selected(index);
1316 var i = this.index_or_selected(index);
1309 var cell = this.get_cell(i);
1317 var cell = this.get_cell(i);
1310 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1318 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1311 cell.expand_output();
1319 cell.expand_output();
1312 this.set_dirty(true);
1320 this.set_dirty(true);
1313 }
1321 }
1314 };
1322 };
1315
1323
1316 /**
1324 /**
1317 * Expand each code cell's output area, and remove scrollbars.
1325 * Expand each code cell's output area, and remove scrollbars.
1318 *
1326 *
1319 * @method expand_all_output
1327 * @method expand_all_output
1320 */
1328 */
1321 Notebook.prototype.expand_all_output = function () {
1329 Notebook.prototype.expand_all_output = function () {
1322 $.map(this.get_cells(), function (cell, i) {
1330 $.map(this.get_cells(), function (cell, i) {
1323 if (cell instanceof codecell.CodeCell) {
1331 if (cell instanceof codecell.CodeCell) {
1324 cell.expand_output();
1332 cell.expand_output();
1325 }
1333 }
1326 });
1334 });
1327 // this should not be set if the `collapse` key is removed from nbformat
1335 // this should not be set if the `collapse` key is removed from nbformat
1328 this.set_dirty(true);
1336 this.set_dirty(true);
1329 };
1337 };
1330
1338
1331 /**
1339 /**
1332 * Clear the selected CodeCell's output area.
1340 * Clear the selected CodeCell's output area.
1333 *
1341 *
1334 * @method clear_output
1342 * @method clear_output
1335 * @param {Number} index A cell's numeric index
1343 * @param {Number} index A cell's numeric index
1336 */
1344 */
1337 Notebook.prototype.clear_output = function (index) {
1345 Notebook.prototype.clear_output = function (index) {
1338 var i = this.index_or_selected(index);
1346 var i = this.index_or_selected(index);
1339 var cell = this.get_cell(i);
1347 var cell = this.get_cell(i);
1340 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1348 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1341 cell.clear_output();
1349 cell.clear_output();
1342 this.set_dirty(true);
1350 this.set_dirty(true);
1343 }
1351 }
1344 };
1352 };
1345
1353
1346 /**
1354 /**
1347 * Clear each code cell's output area.
1355 * Clear each code cell's output area.
1348 *
1356 *
1349 * @method clear_all_output
1357 * @method clear_all_output
1350 */
1358 */
1351 Notebook.prototype.clear_all_output = function () {
1359 Notebook.prototype.clear_all_output = function () {
1352 $.map(this.get_cells(), function (cell, i) {
1360 $.map(this.get_cells(), function (cell, i) {
1353 if (cell instanceof codecell.CodeCell) {
1361 if (cell instanceof codecell.CodeCell) {
1354 cell.clear_output();
1362 cell.clear_output();
1355 }
1363 }
1356 });
1364 });
1357 this.set_dirty(true);
1365 this.set_dirty(true);
1358 };
1366 };
1359
1367
1360 /**
1368 /**
1361 * Scroll the selected CodeCell's output area.
1369 * Scroll the selected CodeCell's output area.
1362 *
1370 *
1363 * @method scroll_output
1371 * @method scroll_output
1364 * @param {Number} index A cell's numeric index
1372 * @param {Number} index A cell's numeric index
1365 */
1373 */
1366 Notebook.prototype.scroll_output = function (index) {
1374 Notebook.prototype.scroll_output = function (index) {
1367 var i = this.index_or_selected(index);
1375 var i = this.index_or_selected(index);
1368 var cell = this.get_cell(i);
1376 var cell = this.get_cell(i);
1369 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1377 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1370 cell.scroll_output();
1378 cell.scroll_output();
1371 this.set_dirty(true);
1379 this.set_dirty(true);
1372 }
1380 }
1373 };
1381 };
1374
1382
1375 /**
1383 /**
1376 * Expand each code cell's output area, and add a scrollbar for long output.
1384 * Expand each code cell's output area, and add a scrollbar for long output.
1377 *
1385 *
1378 * @method scroll_all_output
1386 * @method scroll_all_output
1379 */
1387 */
1380 Notebook.prototype.scroll_all_output = function () {
1388 Notebook.prototype.scroll_all_output = function () {
1381 $.map(this.get_cells(), function (cell, i) {
1389 $.map(this.get_cells(), function (cell, i) {
1382 if (cell instanceof codecell.CodeCell) {
1390 if (cell instanceof codecell.CodeCell) {
1383 cell.scroll_output();
1391 cell.scroll_output();
1384 }
1392 }
1385 });
1393 });
1386 // this should not be set if the `collapse` key is removed from nbformat
1394 // this should not be set if the `collapse` key is removed from nbformat
1387 this.set_dirty(true);
1395 this.set_dirty(true);
1388 };
1396 };
1389
1397
1390 /** Toggle whether a cell's output is collapsed or expanded.
1398 /** Toggle whether a cell's output is collapsed or expanded.
1391 *
1399 *
1392 * @method toggle_output
1400 * @method toggle_output
1393 * @param {Number} index A cell's numeric index
1401 * @param {Number} index A cell's numeric index
1394 */
1402 */
1395 Notebook.prototype.toggle_output = function (index) {
1403 Notebook.prototype.toggle_output = function (index) {
1396 var i = this.index_or_selected(index);
1404 var i = this.index_or_selected(index);
1397 var cell = this.get_cell(i);
1405 var cell = this.get_cell(i);
1398 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1406 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1399 cell.toggle_output();
1407 cell.toggle_output();
1400 this.set_dirty(true);
1408 this.set_dirty(true);
1401 }
1409 }
1402 };
1410 };
1403
1411
1404 /**
1412 /**
1405 * Hide/show the output of all cells.
1413 * Hide/show the output of all cells.
1406 *
1414 *
1407 * @method toggle_all_output
1415 * @method toggle_all_output
1408 */
1416 */
1409 Notebook.prototype.toggle_all_output = function () {
1417 Notebook.prototype.toggle_all_output = function () {
1410 $.map(this.get_cells(), function (cell, i) {
1418 $.map(this.get_cells(), function (cell, i) {
1411 if (cell instanceof codecell.CodeCell) {
1419 if (cell instanceof codecell.CodeCell) {
1412 cell.toggle_output();
1420 cell.toggle_output();
1413 }
1421 }
1414 });
1422 });
1415 // this should not be set if the `collapse` key is removed from nbformat
1423 // this should not be set if the `collapse` key is removed from nbformat
1416 this.set_dirty(true);
1424 this.set_dirty(true);
1417 };
1425 };
1418
1426
1419 /**
1427 /**
1420 * Toggle a scrollbar for long cell outputs.
1428 * Toggle a scrollbar for long cell outputs.
1421 *
1429 *
1422 * @method toggle_output_scroll
1430 * @method toggle_output_scroll
1423 * @param {Number} index A cell's numeric index
1431 * @param {Number} index A cell's numeric index
1424 */
1432 */
1425 Notebook.prototype.toggle_output_scroll = function (index) {
1433 Notebook.prototype.toggle_output_scroll = function (index) {
1426 var i = this.index_or_selected(index);
1434 var i = this.index_or_selected(index);
1427 var cell = this.get_cell(i);
1435 var cell = this.get_cell(i);
1428 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1436 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1429 cell.toggle_output_scroll();
1437 cell.toggle_output_scroll();
1430 this.set_dirty(true);
1438 this.set_dirty(true);
1431 }
1439 }
1432 };
1440 };
1433
1441
1434 /**
1442 /**
1435 * Toggle the scrolling of long output on all cells.
1443 * Toggle the scrolling of long output on all cells.
1436 *
1444 *
1437 * @method toggle_all_output_scrolling
1445 * @method toggle_all_output_scrolling
1438 */
1446 */
1439 Notebook.prototype.toggle_all_output_scroll = function () {
1447 Notebook.prototype.toggle_all_output_scroll = function () {
1440 $.map(this.get_cells(), function (cell, i) {
1448 $.map(this.get_cells(), function (cell, i) {
1441 if (cell instanceof codecell.CodeCell) {
1449 if (cell instanceof codecell.CodeCell) {
1442 cell.toggle_output_scroll();
1450 cell.toggle_output_scroll();
1443 }
1451 }
1444 });
1452 });
1445 // this should not be set if the `collapse` key is removed from nbformat
1453 // this should not be set if the `collapse` key is removed from nbformat
1446 this.set_dirty(true);
1454 this.set_dirty(true);
1447 };
1455 };
1448
1456
1449 // Other cell functions: line numbers, ...
1457 // Other cell functions: line numbers, ...
1450
1458
1451 /**
1459 /**
1452 * Toggle line numbers in the selected cell's input area.
1460 * Toggle line numbers in the selected cell's input area.
1453 *
1461 *
1454 * @method cell_toggle_line_numbers
1462 * @method cell_toggle_line_numbers
1455 */
1463 */
1456 Notebook.prototype.cell_toggle_line_numbers = function() {
1464 Notebook.prototype.cell_toggle_line_numbers = function() {
1457 this.get_selected_cell().toggle_line_numbers();
1465 this.get_selected_cell().toggle_line_numbers();
1458 };
1466 };
1459
1467
1460 // Session related things
1468 // Session related things
1461
1469
1462 /**
1470 /**
1463 * Start a new session and set it on each code cell.
1471 * Start a new session and set it on each code cell.
1464 *
1472 *
1465 * @method start_session
1473 * @method start_session
1466 */
1474 */
1467 Notebook.prototype.start_session = function () {
1475 Notebook.prototype.start_session = function () {
1468 this.session = new session.Session(this, this.options);
1476 this.session = new session.Session(this, {
1477 base_url: base_url,
1478 notebook_path: notebook_path,
1479 notebook_name: notebook_name,
1480 notebook: this});
1469 this.session.start($.proxy(this._session_started, this));
1481 this.session.start($.proxy(this._session_started, this));
1470 };
1482 };
1471
1483
1472
1484
1473 /**
1485 /**
1474 * Once a session is started, link the code cells to the kernel and pass the
1486 * Once a session is started, link the code cells to the kernel and pass the
1475 * comm manager to the widget manager
1487 * comm manager to the widget manager
1476 *
1488 *
1477 */
1489 */
1478 Notebook.prototype._session_started = function(){
1490 Notebook.prototype._session_started = function(){
1479 this.kernel = this.session.kernel;
1491 this.kernel = this.session.kernel;
1480 var ncells = this.ncells();
1492 var ncells = this.ncells();
1481 for (var i=0; i<ncells; i++) {
1493 for (var i=0; i<ncells; i++) {
1482 var cell = this.get_cell(i);
1494 var cell = this.get_cell(i);
1483 if (cell instanceof codecell.CodeCell) {
1495 if (cell instanceof codecell.CodeCell) {
1484 cell.set_kernel(this.session.kernel);
1496 cell.set_kernel(this.session.kernel);
1485 }
1497 }
1486 }
1498 }
1487 };
1499 };
1488
1500
1489 /**
1501 /**
1490 * Prompt the user to restart the IPython kernel.
1502 * Prompt the user to restart the IPython kernel.
1491 *
1503 *
1492 * @method restart_kernel
1504 * @method restart_kernel
1493 */
1505 */
1494 Notebook.prototype.restart_kernel = function () {
1506 Notebook.prototype.restart_kernel = function () {
1495 var that = this;
1507 var that = this;
1496 dialog.modal({
1508 dialog.modal({
1497 title : "Restart kernel or continue running?",
1509 title : "Restart kernel or continue running?",
1498 body : $("<p/>").text(
1510 body : $("<p/>").text(
1499 'Do you want to restart the current kernel? You will lose all variables defined in it.'
1511 'Do you want to restart the current kernel? You will lose all variables defined in it.'
1500 ),
1512 ),
1501 buttons : {
1513 buttons : {
1502 "Continue running" : {},
1514 "Continue running" : {},
1503 "Restart" : {
1515 "Restart" : {
1504 "class" : "btn-danger",
1516 "class" : "btn-danger",
1505 "click" : function() {
1517 "click" : function() {
1506 that.session.restart_kernel();
1518 that.session.restart_kernel();
1507 }
1519 }
1508 }
1520 }
1509 }
1521 }
1510 });
1522 });
1511 };
1523 };
1512
1524
1513 /**
1525 /**
1514 * Execute or render cell outputs and go into command mode.
1526 * Execute or render cell outputs and go into command mode.
1515 *
1527 *
1516 * @method execute_cell
1528 * @method execute_cell
1517 */
1529 */
1518 Notebook.prototype.execute_cell = function () {
1530 Notebook.prototype.execute_cell = function () {
1519 // mode = shift, ctrl, alt
1531 // mode = shift, ctrl, alt
1520 var cell = this.get_selected_cell();
1532 var cell = this.get_selected_cell();
1521 var cell_index = this.find_cell_index(cell);
1533 var cell_index = this.find_cell_index(cell);
1522
1534
1523 cell.execute();
1535 cell.execute();
1524 this.command_mode();
1536 this.command_mode();
1525 this.set_dirty(true);
1537 this.set_dirty(true);
1526 };
1538 };
1527
1539
1528 /**
1540 /**
1529 * Execute or render cell outputs and insert a new cell below.
1541 * Execute or render cell outputs and insert a new cell below.
1530 *
1542 *
1531 * @method execute_cell_and_insert_below
1543 * @method execute_cell_and_insert_below
1532 */
1544 */
1533 Notebook.prototype.execute_cell_and_insert_below = function () {
1545 Notebook.prototype.execute_cell_and_insert_below = function () {
1534 var cell = this.get_selected_cell();
1546 var cell = this.get_selected_cell();
1535 var cell_index = this.find_cell_index(cell);
1547 var cell_index = this.find_cell_index(cell);
1536
1548
1537 cell.execute();
1549 cell.execute();
1538
1550
1539 // If we are at the end always insert a new cell and return
1551 // If we are at the end always insert a new cell and return
1540 if (cell_index === (this.ncells()-1)) {
1552 if (cell_index === (this.ncells()-1)) {
1541 this.command_mode();
1553 this.command_mode();
1542 this.insert_cell_below();
1554 this.insert_cell_below();
1543 this.select(cell_index+1);
1555 this.select(cell_index+1);
1544 this.edit_mode();
1556 this.edit_mode();
1545 this.scroll_to_bottom();
1557 this.scroll_to_bottom();
1546 this.set_dirty(true);
1558 this.set_dirty(true);
1547 return;
1559 return;
1548 }
1560 }
1549
1561
1550 this.command_mode();
1562 this.command_mode();
1551 this.insert_cell_below();
1563 this.insert_cell_below();
1552 this.select(cell_index+1);
1564 this.select(cell_index+1);
1553 this.edit_mode();
1565 this.edit_mode();
1554 this.set_dirty(true);
1566 this.set_dirty(true);
1555 };
1567 };
1556
1568
1557 /**
1569 /**
1558 * Execute or render cell outputs and select the next cell.
1570 * Execute or render cell outputs and select the next cell.
1559 *
1571 *
1560 * @method execute_cell_and_select_below
1572 * @method execute_cell_and_select_below
1561 */
1573 */
1562 Notebook.prototype.execute_cell_and_select_below = function () {
1574 Notebook.prototype.execute_cell_and_select_below = function () {
1563
1575
1564 var cell = this.get_selected_cell();
1576 var cell = this.get_selected_cell();
1565 var cell_index = this.find_cell_index(cell);
1577 var cell_index = this.find_cell_index(cell);
1566
1578
1567 cell.execute();
1579 cell.execute();
1568
1580
1569 // If we are at the end always insert a new cell and return
1581 // If we are at the end always insert a new cell and return
1570 if (cell_index === (this.ncells()-1)) {
1582 if (cell_index === (this.ncells()-1)) {
1571 this.command_mode();
1583 this.command_mode();
1572 this.insert_cell_below();
1584 this.insert_cell_below();
1573 this.select(cell_index+1);
1585 this.select(cell_index+1);
1574 this.edit_mode();
1586 this.edit_mode();
1575 this.scroll_to_bottom();
1587 this.scroll_to_bottom();
1576 this.set_dirty(true);
1588 this.set_dirty(true);
1577 return;
1589 return;
1578 }
1590 }
1579
1591
1580 this.command_mode();
1592 this.command_mode();
1581 this.select(cell_index+1);
1593 this.select(cell_index+1);
1582 this.focus_cell();
1594 this.focus_cell();
1583 this.set_dirty(true);
1595 this.set_dirty(true);
1584 };
1596 };
1585
1597
1586 /**
1598 /**
1587 * Execute all cells below the selected cell.
1599 * Execute all cells below the selected cell.
1588 *
1600 *
1589 * @method execute_cells_below
1601 * @method execute_cells_below
1590 */
1602 */
1591 Notebook.prototype.execute_cells_below = function () {
1603 Notebook.prototype.execute_cells_below = function () {
1592 this.execute_cell_range(this.get_selected_index(), this.ncells());
1604 this.execute_cell_range(this.get_selected_index(), this.ncells());
1593 this.scroll_to_bottom();
1605 this.scroll_to_bottom();
1594 };
1606 };
1595
1607
1596 /**
1608 /**
1597 * Execute all cells above the selected cell.
1609 * Execute all cells above the selected cell.
1598 *
1610 *
1599 * @method execute_cells_above
1611 * @method execute_cells_above
1600 */
1612 */
1601 Notebook.prototype.execute_cells_above = function () {
1613 Notebook.prototype.execute_cells_above = function () {
1602 this.execute_cell_range(0, this.get_selected_index());
1614 this.execute_cell_range(0, this.get_selected_index());
1603 };
1615 };
1604
1616
1605 /**
1617 /**
1606 * Execute all cells.
1618 * Execute all cells.
1607 *
1619 *
1608 * @method execute_all_cells
1620 * @method execute_all_cells
1609 */
1621 */
1610 Notebook.prototype.execute_all_cells = function () {
1622 Notebook.prototype.execute_all_cells = function () {
1611 this.execute_cell_range(0, this.ncells());
1623 this.execute_cell_range(0, this.ncells());
1612 this.scroll_to_bottom();
1624 this.scroll_to_bottom();
1613 };
1625 };
1614
1626
1615 /**
1627 /**
1616 * Execute a contiguous range of cells.
1628 * Execute a contiguous range of cells.
1617 *
1629 *
1618 * @method execute_cell_range
1630 * @method execute_cell_range
1619 * @param {Number} start Index of the first cell to execute (inclusive)
1631 * @param {Number} start Index of the first cell to execute (inclusive)
1620 * @param {Number} end Index of the last cell to execute (exclusive)
1632 * @param {Number} end Index of the last cell to execute (exclusive)
1621 */
1633 */
1622 Notebook.prototype.execute_cell_range = function (start, end) {
1634 Notebook.prototype.execute_cell_range = function (start, end) {
1623 this.command_mode();
1635 this.command_mode();
1624 for (var i=start; i<end; i++) {
1636 for (var i=start; i<end; i++) {
1625 this.select(i);
1637 this.select(i);
1626 this.execute_cell();
1638 this.execute_cell();
1627 }
1639 }
1628 };
1640 };
1629
1641
1630 // Persistance and loading
1642 // Persistance and loading
1631
1643
1632 /**
1644 /**
1633 * Getter method for this notebook's name.
1645 * Getter method for this notebook's name.
1634 *
1646 *
1635 * @method get_notebook_name
1647 * @method get_notebook_name
1636 * @return {String} This notebook's name (excluding file extension)
1648 * @return {String} This notebook's name (excluding file extension)
1637 */
1649 */
1638 Notebook.prototype.get_notebook_name = function () {
1650 Notebook.prototype.get_notebook_name = function () {
1639 var nbname = this.notebook_name.substring(0,this.notebook_name.length-6);
1651 var nbname = this.notebook_name.substring(0,this.notebook_name.length-6);
1640 return nbname;
1652 return nbname;
1641 };
1653 };
1642
1654
1643 /**
1655 /**
1644 * Setter method for this notebook's name.
1656 * Setter method for this notebook's name.
1645 *
1657 *
1646 * @method set_notebook_name
1658 * @method set_notebook_name
1647 * @param {String} name A new name for this notebook
1659 * @param {String} name A new name for this notebook
1648 */
1660 */
1649 Notebook.prototype.set_notebook_name = function (name) {
1661 Notebook.prototype.set_notebook_name = function (name) {
1650 this.notebook_name = name;
1662 this.notebook_name = name;
1651 };
1663 };
1652
1664
1653 /**
1665 /**
1654 * Check that a notebook's name is valid.
1666 * Check that a notebook's name is valid.
1655 *
1667 *
1656 * @method test_notebook_name
1668 * @method test_notebook_name
1657 * @param {String} nbname A name for this notebook
1669 * @param {String} nbname A name for this notebook
1658 * @return {Boolean} True if the name is valid, false if invalid
1670 * @return {Boolean} True if the name is valid, false if invalid
1659 */
1671 */
1660 Notebook.prototype.test_notebook_name = function (nbname) {
1672 Notebook.prototype.test_notebook_name = function (nbname) {
1661 nbname = nbname || '';
1673 nbname = nbname || '';
1662 if (nbname.length>0 && !this.notebook_name_blacklist_re.test(nbname)) {
1674 if (nbname.length>0 && !this.notebook_name_blacklist_re.test(nbname)) {
1663 return true;
1675 return true;
1664 } else {
1676 } else {
1665 return false;
1677 return false;
1666 }
1678 }
1667 };
1679 };
1668
1680
1669 /**
1681 /**
1670 * Load a notebook from JSON (.ipynb).
1682 * Load a notebook from JSON (.ipynb).
1671 *
1683 *
1672 * This currently handles one worksheet: others are deleted.
1684 * This currently handles one worksheet: others are deleted.
1673 *
1685 *
1674 * @method fromJSON
1686 * @method fromJSON
1675 * @param {Object} data JSON representation of a notebook
1687 * @param {Object} data JSON representation of a notebook
1676 */
1688 */
1677 Notebook.prototype.fromJSON = function (data) {
1689 Notebook.prototype.fromJSON = function (data) {
1678 var content = data.content;
1690 var content = data.content;
1679 var ncells = this.ncells();
1691 var ncells = this.ncells();
1680 var i;
1692 var i;
1681 for (i=0; i<ncells; i++) {
1693 for (i=0; i<ncells; i++) {
1682 // Always delete cell 0 as they get renumbered as they are deleted.
1694 // Always delete cell 0 as they get renumbered as they are deleted.
1683 this.delete_cell(0);
1695 this.delete_cell(0);
1684 }
1696 }
1685 // Save the metadata and name.
1697 // Save the metadata and name.
1686 this.metadata = content.metadata;
1698 this.metadata = content.metadata;
1687 this.notebook_name = data.name;
1699 this.notebook_name = data.name;
1688 var trusted = true;
1700 var trusted = true;
1689 // Only handle 1 worksheet for now.
1701 // Only handle 1 worksheet for now.
1690 var worksheet = content.worksheets[0];
1702 var worksheet = content.worksheets[0];
1691 if (worksheet !== undefined) {
1703 if (worksheet !== undefined) {
1692 if (worksheet.metadata) {
1704 if (worksheet.metadata) {
1693 this.worksheet_metadata = worksheet.metadata;
1705 this.worksheet_metadata = worksheet.metadata;
1694 }
1706 }
1695 var new_cells = worksheet.cells;
1707 var new_cells = worksheet.cells;
1696 ncells = new_cells.length;
1708 ncells = new_cells.length;
1697 var cell_data = null;
1709 var cell_data = null;
1698 var new_cell = null;
1710 var new_cell = null;
1699 for (i=0; i<ncells; i++) {
1711 for (i=0; i<ncells; i++) {
1700 cell_data = new_cells[i];
1712 cell_data = new_cells[i];
1701 // VERSIONHACK: plaintext -> raw
1713 // VERSIONHACK: plaintext -> raw
1702 // handle never-released plaintext name for raw cells
1714 // handle never-released plaintext name for raw cells
1703 if (cell_data.cell_type === 'plaintext'){
1715 if (cell_data.cell_type === 'plaintext'){
1704 cell_data.cell_type = 'raw';
1716 cell_data.cell_type = 'raw';
1705 }
1717 }
1706
1718
1707 new_cell = this.insert_cell_at_index(cell_data.cell_type, i);
1719 new_cell = this.insert_cell_at_index(cell_data.cell_type, i);
1708 new_cell.fromJSON(cell_data);
1720 new_cell.fromJSON(cell_data);
1709 if (new_cell.cell_type == 'code' && !new_cell.output_area.trusted) {
1721 if (new_cell.cell_type == 'code' && !new_cell.output_area.trusted) {
1710 trusted = false;
1722 trusted = false;
1711 }
1723 }
1712 }
1724 }
1713 }
1725 }
1714 if (trusted != this.trusted) {
1726 if (trusted != this.trusted) {
1715 this.trusted = trusted;
1727 this.trusted = trusted;
1716 this.events.trigger("trust_changed.Notebook", trusted);
1728 this.events.trigger("trust_changed.Notebook", trusted);
1717 }
1729 }
1718 if (content.worksheets.length > 1) {
1730 if (content.worksheets.length > 1) {
1719 dialog.modal({
1731 dialog.modal({
1720 title : "Multiple worksheets",
1732 title : "Multiple worksheets",
1721 body : "This notebook has " + data.worksheets.length + " worksheets, " +
1733 body : "This notebook has " + data.worksheets.length + " worksheets, " +
1722 "but this version of IPython can only handle the first. " +
1734 "but this version of IPython can only handle the first. " +
1723 "If you save this notebook, worksheets after the first will be lost.",
1735 "If you save this notebook, worksheets after the first will be lost.",
1724 buttons : {
1736 buttons : {
1725 OK : {
1737 OK : {
1726 class : "btn-danger"
1738 class : "btn-danger"
1727 }
1739 }
1728 }
1740 }
1729 });
1741 });
1730 }
1742 }
1731 };
1743 };
1732
1744
1733 /**
1745 /**
1734 * Dump this notebook into a JSON-friendly object.
1746 * Dump this notebook into a JSON-friendly object.
1735 *
1747 *
1736 * @method toJSON
1748 * @method toJSON
1737 * @return {Object} A JSON-friendly representation of this notebook.
1749 * @return {Object} A JSON-friendly representation of this notebook.
1738 */
1750 */
1739 Notebook.prototype.toJSON = function () {
1751 Notebook.prototype.toJSON = function () {
1740 var cells = this.get_cells();
1752 var cells = this.get_cells();
1741 var ncells = cells.length;
1753 var ncells = cells.length;
1742 var cell_array = new Array(ncells);
1754 var cell_array = new Array(ncells);
1743 var trusted = true;
1755 var trusted = true;
1744 for (var i=0; i<ncells; i++) {
1756 for (var i=0; i<ncells; i++) {
1745 var cell = cells[i];
1757 var cell = cells[i];
1746 if (cell.cell_type == 'code' && !cell.output_area.trusted) {
1758 if (cell.cell_type == 'code' && !cell.output_area.trusted) {
1747 trusted = false;
1759 trusted = false;
1748 }
1760 }
1749 cell_array[i] = cell.toJSON();
1761 cell_array[i] = cell.toJSON();
1750 }
1762 }
1751 var data = {
1763 var data = {
1752 // Only handle 1 worksheet for now.
1764 // Only handle 1 worksheet for now.
1753 worksheets : [{
1765 worksheets : [{
1754 cells: cell_array,
1766 cells: cell_array,
1755 metadata: this.worksheet_metadata
1767 metadata: this.worksheet_metadata
1756 }],
1768 }],
1757 metadata : this.metadata
1769 metadata : this.metadata
1758 };
1770 };
1759 if (trusted != this.trusted) {
1771 if (trusted != this.trusted) {
1760 this.trusted = trusted;
1772 this.trusted = trusted;
1761 this.events.trigger("trust_changed.Notebook", trusted);
1773 this.events.trigger("trust_changed.Notebook", trusted);
1762 }
1774 }
1763 return data;
1775 return data;
1764 };
1776 };
1765
1777
1766 /**
1778 /**
1767 * Start an autosave timer, for periodically saving the notebook.
1779 * Start an autosave timer, for periodically saving the notebook.
1768 *
1780 *
1769 * @method set_autosave_interval
1781 * @method set_autosave_interval
1770 * @param {Integer} interval the autosave interval in milliseconds
1782 * @param {Integer} interval the autosave interval in milliseconds
1771 */
1783 */
1772 Notebook.prototype.set_autosave_interval = function (interval) {
1784 Notebook.prototype.set_autosave_interval = function (interval) {
1773 var that = this;
1785 var that = this;
1774 // clear previous interval, so we don't get simultaneous timers
1786 // clear previous interval, so we don't get simultaneous timers
1775 if (this.autosave_timer) {
1787 if (this.autosave_timer) {
1776 clearInterval(this.autosave_timer);
1788 clearInterval(this.autosave_timer);
1777 }
1789 }
1778
1790
1779 this.autosave_interval = this.minimum_autosave_interval = interval;
1791 this.autosave_interval = this.minimum_autosave_interval = interval;
1780 if (interval) {
1792 if (interval) {
1781 this.autosave_timer = setInterval(function() {
1793 this.autosave_timer = setInterval(function() {
1782 if (that.dirty) {
1794 if (that.dirty) {
1783 that.save_notebook();
1795 that.save_notebook();
1784 }
1796 }
1785 }, interval);
1797 }, interval);
1786 this.events.trigger("autosave_enabled.Notebook", interval);
1798 this.events.trigger("autosave_enabled.Notebook", interval);
1787 } else {
1799 } else {
1788 this.autosave_timer = null;
1800 this.autosave_timer = null;
1789 this.events.trigger("autosave_disabled.Notebook");
1801 this.events.trigger("autosave_disabled.Notebook");
1790 }
1802 }
1791 };
1803 };
1792
1804
1793 /**
1805 /**
1794 * Save this notebook on the server. This becomes a notebook instance's
1806 * Save this notebook on the server. This becomes a notebook instance's
1795 * .save_notebook method *after* the entire notebook has been loaded.
1807 * .save_notebook method *after* the entire notebook has been loaded.
1796 *
1808 *
1797 * @method save_notebook
1809 * @method save_notebook
1798 */
1810 */
1799 Notebook.prototype.save_notebook = function (extra_settings) {
1811 Notebook.prototype.save_notebook = function (extra_settings) {
1800 // Create a JSON model to be sent to the server.
1812 // Create a JSON model to be sent to the server.
1801 var model = {};
1813 var model = {};
1802 model.name = this.notebook_name;
1814 model.name = this.notebook_name;
1803 model.path = this.notebook_path;
1815 model.path = this.notebook_path;
1804 model.content = this.toJSON();
1816 model.content = this.toJSON();
1805 model.content.nbformat = this.nbformat;
1817 model.content.nbformat = this.nbformat;
1806 model.content.nbformat_minor = this.nbformat_minor;
1818 model.content.nbformat_minor = this.nbformat_minor;
1807 // time the ajax call for autosave tuning purposes.
1819 // time the ajax call for autosave tuning purposes.
1808 var start = new Date().getTime();
1820 var start = new Date().getTime();
1809 // We do the call with settings so we can set cache to false.
1821 // We do the call with settings so we can set cache to false.
1810 var settings = {
1822 var settings = {
1811 processData : false,
1823 processData : false,
1812 cache : false,
1824 cache : false,
1813 type : "PUT",
1825 type : "PUT",
1814 data : JSON.stringify(model),
1826 data : JSON.stringify(model),
1815 headers : {'Content-Type': 'application/json'},
1827 headers : {'Content-Type': 'application/json'},
1816 success : $.proxy(this.save_notebook_success, this, start),
1828 success : $.proxy(this.save_notebook_success, this, start),
1817 error : $.proxy(this.save_notebook_error, this)
1829 error : $.proxy(this.save_notebook_error, this)
1818 };
1830 };
1819 if (extra_settings) {
1831 if (extra_settings) {
1820 for (var key in extra_settings) {
1832 for (var key in extra_settings) {
1821 settings[key] = extra_settings[key];
1833 settings[key] = extra_settings[key];
1822 }
1834 }
1823 }
1835 }
1824 this.events.trigger('notebook_saving.Notebook');
1836 this.events.trigger('notebook_saving.Notebook');
1825 var url = utils.url_join_encode(
1837 var url = utils.url_join_encode(
1826 this.base_url,
1838 this.base_url,
1827 'api/notebooks',
1839 'api/notebooks',
1828 this.notebook_path,
1840 this.notebook_path,
1829 this.notebook_name
1841 this.notebook_name
1830 );
1842 );
1831 $.ajax(url, settings);
1843 $.ajax(url, settings);
1832 };
1844 };
1833
1845
1834 /**
1846 /**
1835 * Success callback for saving a notebook.
1847 * Success callback for saving a notebook.
1836 *
1848 *
1837 * @method save_notebook_success
1849 * @method save_notebook_success
1838 * @param {Integer} start the time when the save request started
1850 * @param {Integer} start the time when the save request started
1839 * @param {Object} data JSON representation of a notebook
1851 * @param {Object} data JSON representation of a notebook
1840 * @param {String} status Description of response status
1852 * @param {String} status Description of response status
1841 * @param {jqXHR} xhr jQuery Ajax object
1853 * @param {jqXHR} xhr jQuery Ajax object
1842 */
1854 */
1843 Notebook.prototype.save_notebook_success = function (start, data, status, xhr) {
1855 Notebook.prototype.save_notebook_success = function (start, data, status, xhr) {
1844 this.set_dirty(false);
1856 this.set_dirty(false);
1845 this.events.trigger('notebook_saved.Notebook');
1857 this.events.trigger('notebook_saved.Notebook');
1846 this._update_autosave_interval(start);
1858 this._update_autosave_interval(start);
1847 if (this._checkpoint_after_save) {
1859 if (this._checkpoint_after_save) {
1848 this.create_checkpoint();
1860 this.create_checkpoint();
1849 this._checkpoint_after_save = false;
1861 this._checkpoint_after_save = false;
1850 }
1862 }
1851 };
1863 };
1852
1864
1853 /**
1865 /**
1854 * update the autosave interval based on how long the last save took
1866 * update the autosave interval based on how long the last save took
1855 *
1867 *
1856 * @method _update_autosave_interval
1868 * @method _update_autosave_interval
1857 * @param {Integer} timestamp when the save request started
1869 * @param {Integer} timestamp when the save request started
1858 */
1870 */
1859 Notebook.prototype._update_autosave_interval = function (start) {
1871 Notebook.prototype._update_autosave_interval = function (start) {
1860 var duration = (new Date().getTime() - start);
1872 var duration = (new Date().getTime() - start);
1861 if (this.autosave_interval) {
1873 if (this.autosave_interval) {
1862 // new save interval: higher of 10x save duration or parameter (default 30 seconds)
1874 // new save interval: higher of 10x save duration or parameter (default 30 seconds)
1863 var interval = Math.max(10 * duration, this.minimum_autosave_interval);
1875 var interval = Math.max(10 * duration, this.minimum_autosave_interval);
1864 // round to 10 seconds, otherwise we will be setting a new interval too often
1876 // round to 10 seconds, otherwise we will be setting a new interval too often
1865 interval = 10000 * Math.round(interval / 10000);
1877 interval = 10000 * Math.round(interval / 10000);
1866 // set new interval, if it's changed
1878 // set new interval, if it's changed
1867 if (interval != this.autosave_interval) {
1879 if (interval != this.autosave_interval) {
1868 this.set_autosave_interval(interval);
1880 this.set_autosave_interval(interval);
1869 }
1881 }
1870 }
1882 }
1871 };
1883 };
1872
1884
1873 /**
1885 /**
1874 * Failure callback for saving a notebook.
1886 * Failure callback for saving a notebook.
1875 *
1887 *
1876 * @method save_notebook_error
1888 * @method save_notebook_error
1877 * @param {jqXHR} xhr jQuery Ajax object
1889 * @param {jqXHR} xhr jQuery Ajax object
1878 * @param {String} status Description of response status
1890 * @param {String} status Description of response status
1879 * @param {String} error HTTP error message
1891 * @param {String} error HTTP error message
1880 */
1892 */
1881 Notebook.prototype.save_notebook_error = function (xhr, status, error) {
1893 Notebook.prototype.save_notebook_error = function (xhr, status, error) {
1882 this.events.trigger('notebook_save_failed.Notebook', [xhr, status, error]);
1894 this.events.trigger('notebook_save_failed.Notebook', [xhr, status, error]);
1883 };
1895 };
1884
1896
1885 /**
1897 /**
1886 * Explicitly trust the output of this notebook.
1898 * Explicitly trust the output of this notebook.
1887 *
1899 *
1888 * @method trust_notebook
1900 * @method trust_notebook
1889 */
1901 */
1890 Notebook.prototype.trust_notebook = function (extra_settings) {
1902 Notebook.prototype.trust_notebook = function (extra_settings) {
1891 var body = $("<div>").append($("<p>")
1903 var body = $("<div>").append($("<p>")
1892 .text("A trusted IPython notebook may execute hidden malicious code ")
1904 .text("A trusted IPython notebook may execute hidden malicious code ")
1893 .append($("<strong>")
1905 .append($("<strong>")
1894 .append(
1906 .append(
1895 $("<em>").text("when you open it")
1907 $("<em>").text("when you open it")
1896 )
1908 )
1897 ).append(".").append(
1909 ).append(".").append(
1898 " Selecting trust will immediately reload this notebook in a trusted state."
1910 " Selecting trust will immediately reload this notebook in a trusted state."
1899 ).append(
1911 ).append(
1900 " For more information, see the "
1912 " For more information, see the "
1901 ).append($("<a>").attr("href", "http://ipython.org/ipython-doc/2/notebook/security.html")
1913 ).append($("<a>").attr("href", "http://ipython.org/ipython-doc/2/notebook/security.html")
1902 .text("IPython security documentation")
1914 .text("IPython security documentation")
1903 ).append(".")
1915 ).append(".")
1904 );
1916 );
1905
1917
1906 var nb = this;
1918 var nb = this;
1907 dialog.modal({
1919 dialog.modal({
1908 title: "Trust this notebook?",
1920 title: "Trust this notebook?",
1909 body: body,
1921 body: body,
1910
1922
1911 buttons: {
1923 buttons: {
1912 Cancel : {},
1924 Cancel : {},
1913 Trust : {
1925 Trust : {
1914 class : "btn-danger",
1926 class : "btn-danger",
1915 click : function () {
1927 click : function () {
1916 var cells = nb.get_cells();
1928 var cells = nb.get_cells();
1917 for (var i = 0; i < cells.length; i++) {
1929 for (var i = 0; i < cells.length; i++) {
1918 var cell = cells[i];
1930 var cell = cells[i];
1919 if (cell.cell_type == 'code') {
1931 if (cell.cell_type == 'code') {
1920 cell.output_area.trusted = true;
1932 cell.output_area.trusted = true;
1921 }
1933 }
1922 }
1934 }
1923 this.events.on('notebook_saved.Notebook', function () {
1935 this.events.on('notebook_saved.Notebook', function () {
1924 window.location.reload();
1936 window.location.reload();
1925 });
1937 });
1926 nb.save_notebook();
1938 nb.save_notebook();
1927 }
1939 }
1928 }
1940 }
1929 }
1941 }
1930 });
1942 });
1931 };
1943 };
1932
1944
1933 Notebook.prototype.new_notebook = function(){
1945 Notebook.prototype.new_notebook = function(){
1934 var path = this.notebook_path;
1946 var path = this.notebook_path;
1935 var base_url = this.base_url;
1947 var base_url = this.base_url;
1936 var settings = {
1948 var settings = {
1937 processData : false,
1949 processData : false,
1938 cache : false,
1950 cache : false,
1939 type : "POST",
1951 type : "POST",
1940 dataType : "json",
1952 dataType : "json",
1941 async : false,
1953 async : false,
1942 success : function (data, status, xhr){
1954 success : function (data, status, xhr){
1943 var notebook_name = data.name;
1955 var notebook_name = data.name;
1944 window.open(
1956 window.open(
1945 utils.url_join_encode(
1957 utils.url_join_encode(
1946 base_url,
1958 base_url,
1947 'notebooks',
1959 'notebooks',
1948 path,
1960 path,
1949 notebook_name
1961 notebook_name
1950 ),
1962 ),
1951 '_blank'
1963 '_blank'
1952 );
1964 );
1953 },
1965 },
1954 error : utils.log_ajax_error,
1966 error : utils.log_ajax_error,
1955 };
1967 };
1956 var url = utils.url_join_encode(
1968 var url = utils.url_join_encode(
1957 base_url,
1969 base_url,
1958 'api/notebooks',
1970 'api/notebooks',
1959 path
1971 path
1960 );
1972 );
1961 $.ajax(url,settings);
1973 $.ajax(url,settings);
1962 };
1974 };
1963
1975
1964
1976
1965 Notebook.prototype.copy_notebook = function(){
1977 Notebook.prototype.copy_notebook = function(){
1966 var path = this.notebook_path;
1978 var path = this.notebook_path;
1967 var base_url = this.base_url;
1979 var base_url = this.base_url;
1968 var settings = {
1980 var settings = {
1969 processData : false,
1981 processData : false,
1970 cache : false,
1982 cache : false,
1971 type : "POST",
1983 type : "POST",
1972 dataType : "json",
1984 dataType : "json",
1973 data : JSON.stringify({copy_from : this.notebook_name}),
1985 data : JSON.stringify({copy_from : this.notebook_name}),
1974 async : false,
1986 async : false,
1975 success : function (data, status, xhr) {
1987 success : function (data, status, xhr) {
1976 window.open(utils.url_join_encode(
1988 window.open(utils.url_join_encode(
1977 base_url,
1989 base_url,
1978 'notebooks',
1990 'notebooks',
1979 data.path,
1991 data.path,
1980 data.name
1992 data.name
1981 ), '_blank');
1993 ), '_blank');
1982 },
1994 },
1983 error : utils.log_ajax_error,
1995 error : utils.log_ajax_error,
1984 };
1996 };
1985 var url = utils.url_join_encode(
1997 var url = utils.url_join_encode(
1986 base_url,
1998 base_url,
1987 'api/notebooks',
1999 'api/notebooks',
1988 path
2000 path
1989 );
2001 );
1990 $.ajax(url,settings);
2002 $.ajax(url,settings);
1991 };
2003 };
1992
2004
1993 Notebook.prototype.rename = function (nbname) {
2005 Notebook.prototype.rename = function (nbname) {
1994 var that = this;
2006 var that = this;
1995 if (!nbname.match(/\.ipynb$/)) {
2007 if (!nbname.match(/\.ipynb$/)) {
1996 nbname = nbname + ".ipynb";
2008 nbname = nbname + ".ipynb";
1997 }
2009 }
1998 var data = {name: nbname};
2010 var data = {name: nbname};
1999 var settings = {
2011 var settings = {
2000 processData : false,
2012 processData : false,
2001 cache : false,
2013 cache : false,
2002 type : "PATCH",
2014 type : "PATCH",
2003 data : JSON.stringify(data),
2015 data : JSON.stringify(data),
2004 dataType: "json",
2016 dataType: "json",
2005 headers : {'Content-Type': 'application/json'},
2017 headers : {'Content-Type': 'application/json'},
2006 success : $.proxy(that.rename_success, this),
2018 success : $.proxy(that.rename_success, this),
2007 error : $.proxy(that.rename_error, this)
2019 error : $.proxy(that.rename_error, this)
2008 };
2020 };
2009 this.events.trigger('rename_notebook.Notebook', data);
2021 this.events.trigger('rename_notebook.Notebook', data);
2010 var url = utils.url_join_encode(
2022 var url = utils.url_join_encode(
2011 this.base_url,
2023 this.base_url,
2012 'api/notebooks',
2024 'api/notebooks',
2013 this.notebook_path,
2025 this.notebook_path,
2014 this.notebook_name
2026 this.notebook_name
2015 );
2027 );
2016 $.ajax(url, settings);
2028 $.ajax(url, settings);
2017 };
2029 };
2018
2030
2019 Notebook.prototype.delete = function () {
2031 Notebook.prototype.delete = function () {
2020 var that = this;
2032 var that = this;
2021 var settings = {
2033 var settings = {
2022 processData : false,
2034 processData : false,
2023 cache : false,
2035 cache : false,
2024 type : "DELETE",
2036 type : "DELETE",
2025 dataType: "json",
2037 dataType: "json",
2026 error : utils.log_ajax_error,
2038 error : utils.log_ajax_error,
2027 };
2039 };
2028 var url = utils.url_join_encode(
2040 var url = utils.url_join_encode(
2029 this.base_url,
2041 this.base_url,
2030 'api/notebooks',
2042 'api/notebooks',
2031 this.notebook_path,
2043 this.notebook_path,
2032 this.notebook_name
2044 this.notebook_name
2033 );
2045 );
2034 $.ajax(url, settings);
2046 $.ajax(url, settings);
2035 };
2047 };
2036
2048
2037
2049
2038 Notebook.prototype.rename_success = function (json, status, xhr) {
2050 Notebook.prototype.rename_success = function (json, status, xhr) {
2039 var name = this.notebook_name = json.name;
2051 var name = this.notebook_name = json.name;
2040 var path = json.path;
2052 var path = json.path;
2041 this.session.rename_notebook(name, path);
2053 this.session.rename_notebook(name, path);
2042 this.events.trigger('notebook_renamed.Notebook', json);
2054 this.events.trigger('notebook_renamed.Notebook', json);
2043 };
2055 };
2044
2056
2045 Notebook.prototype.rename_error = function (xhr, status, error) {
2057 Notebook.prototype.rename_error = function (xhr, status, error) {
2046 var that = this;
2058 var that = this;
2047 var dialog_body = $('<div/>').append(
2059 var dialog_body = $('<div/>').append(
2048 $("<p/>").addClass("rename-message")
2060 $("<p/>").addClass("rename-message")
2049 .text('This notebook name already exists.')
2061 .text('This notebook name already exists.')
2050 );
2062 );
2051 this.events.trigger('notebook_rename_failed.Notebook', [xhr, status, error]);
2063 this.events.trigger('notebook_rename_failed.Notebook', [xhr, status, error]);
2052 dialog.modal({
2064 dialog.modal({
2053 title: "Notebook Rename Error!",
2065 title: "Notebook Rename Error!",
2054 body: dialog_body,
2066 body: dialog_body,
2055 buttons : {
2067 buttons : {
2056 "Cancel": {},
2068 "Cancel": {},
2057 "OK": {
2069 "OK": {
2058 class: "btn-primary",
2070 class: "btn-primary",
2059 click: function () {
2071 click: function () {
2060 this.save_widget.rename_notebook();
2072 this.save_widget.rename_notebook();
2061 }}
2073 }}
2062 },
2074 },
2063 open : function (event, ui) {
2075 open : function (event, ui) {
2064 var that = $(this);
2076 var that = $(this);
2065 // Upon ENTER, click the OK button.
2077 // Upon ENTER, click the OK button.
2066 that.find('input[type="text"]').keydown(function (event, ui) {
2078 that.find('input[type="text"]').keydown(function (event, ui) {
2067 if (event.which === this.keyboard.keycodes.enter) {
2079 if (event.which === this.keyboard.keycodes.enter) {
2068 that.find('.btn-primary').first().click();
2080 that.find('.btn-primary').first().click();
2069 }
2081 }
2070 });
2082 });
2071 that.find('input[type="text"]').focus();
2083 that.find('input[type="text"]').focus();
2072 }
2084 }
2073 });
2085 });
2074 };
2086 };
2075
2087
2076 /**
2088 /**
2077 * Request a notebook's data from the server.
2089 * Request a notebook's data from the server.
2078 *
2090 *
2079 * @method load_notebook
2091 * @method load_notebook
2080 * @param {String} notebook_name and path A notebook to load
2092 * @param {String} notebook_name and path A notebook to load
2081 */
2093 */
2082 Notebook.prototype.load_notebook = function (notebook_name, notebook_path) {
2094 Notebook.prototype.load_notebook = function (notebook_name, notebook_path) {
2083 var that = this;
2095 var that = this;
2084 this.notebook_name = notebook_name;
2096 this.notebook_name = notebook_name;
2085 this.notebook_path = notebook_path;
2097 this.notebook_path = notebook_path;
2086 // We do the call with settings so we can set cache to false.
2098 // We do the call with settings so we can set cache to false.
2087 var settings = {
2099 var settings = {
2088 processData : false,
2100 processData : false,
2089 cache : false,
2101 cache : false,
2090 type : "GET",
2102 type : "GET",
2091 dataType : "json",
2103 dataType : "json",
2092 success : $.proxy(this.load_notebook_success,this),
2104 success : $.proxy(this.load_notebook_success,this),
2093 error : $.proxy(this.load_notebook_error,this),
2105 error : $.proxy(this.load_notebook_error,this),
2094 };
2106 };
2095 this.events.trigger('notebook_loading.Notebook');
2107 this.events.trigger('notebook_loading.Notebook');
2096 var url = utils.url_join_encode(
2108 var url = utils.url_join_encode(
2097 this.base_url,
2109 this.base_url,
2098 'api/notebooks',
2110 'api/notebooks',
2099 this.notebook_path,
2111 this.notebook_path,
2100 this.notebook_name
2112 this.notebook_name
2101 );
2113 );
2102 $.ajax(url, settings);
2114 $.ajax(url, settings);
2103 };
2115 };
2104
2116
2105 /**
2117 /**
2106 * Success callback for loading a notebook from the server.
2118 * Success callback for loading a notebook from the server.
2107 *
2119 *
2108 * Load notebook data from the JSON response.
2120 * Load notebook data from the JSON response.
2109 *
2121 *
2110 * @method load_notebook_success
2122 * @method load_notebook_success
2111 * @param {Object} data JSON representation of a notebook
2123 * @param {Object} data JSON representation of a notebook
2112 * @param {String} status Description of response status
2124 * @param {String} status Description of response status
2113 * @param {jqXHR} xhr jQuery Ajax object
2125 * @param {jqXHR} xhr jQuery Ajax object
2114 */
2126 */
2115 Notebook.prototype.load_notebook_success = function (data, status, xhr) {
2127 Notebook.prototype.load_notebook_success = function (data, status, xhr) {
2116 this.fromJSON(data);
2128 this.fromJSON(data);
2117 if (this.ncells() === 0) {
2129 if (this.ncells() === 0) {
2118 this.insert_cell_below('code');
2130 this.insert_cell_below('code');
2119 this.edit_mode(0);
2131 this.edit_mode(0);
2120 } else {
2132 } else {
2121 this.select(0);
2133 this.select(0);
2122 this.handle_command_mode(this.get_cell(0));
2134 this.handle_command_mode(this.get_cell(0));
2123 }
2135 }
2124 this.set_dirty(false);
2136 this.set_dirty(false);
2125 this.scroll_to_top();
2137 this.scroll_to_top();
2126 if (data.orig_nbformat !== undefined && data.nbformat !== data.orig_nbformat) {
2138 if (data.orig_nbformat !== undefined && data.nbformat !== data.orig_nbformat) {
2127 var msg = "This notebook has been converted from an older " +
2139 var msg = "This notebook has been converted from an older " +
2128 "notebook format (v"+data.orig_nbformat+") to the current notebook " +
2140 "notebook format (v"+data.orig_nbformat+") to the current notebook " +
2129 "format (v"+data.nbformat+"). The next time you save this notebook, the " +
2141 "format (v"+data.nbformat+"). The next time you save this notebook, the " +
2130 "newer notebook format will be used and older versions of IPython " +
2142 "newer notebook format will be used and older versions of IPython " +
2131 "may not be able to read it. To keep the older version, close the " +
2143 "may not be able to read it. To keep the older version, close the " +
2132 "notebook without saving it.";
2144 "notebook without saving it.";
2133 dialog.modal({
2145 dialog.modal({
2134 title : "Notebook converted",
2146 title : "Notebook converted",
2135 body : msg,
2147 body : msg,
2136 buttons : {
2148 buttons : {
2137 OK : {
2149 OK : {
2138 class : "btn-primary"
2150 class : "btn-primary"
2139 }
2151 }
2140 }
2152 }
2141 });
2153 });
2142 } else if (data.orig_nbformat_minor !== undefined && data.nbformat_minor !== data.orig_nbformat_minor) {
2154 } else if (data.orig_nbformat_minor !== undefined && data.nbformat_minor !== data.orig_nbformat_minor) {
2143 var that = this;
2155 var that = this;
2144 var orig_vs = 'v' + data.nbformat + '.' + data.orig_nbformat_minor;
2156 var orig_vs = 'v' + data.nbformat + '.' + data.orig_nbformat_minor;
2145 var this_vs = 'v' + data.nbformat + '.' + this.nbformat_minor;
2157 var this_vs = 'v' + data.nbformat + '.' + this.nbformat_minor;
2146 var msg = "This notebook is version " + orig_vs + ", but we only fully support up to " +
2158 var msg = "This notebook is version " + orig_vs + ", but we only fully support up to " +
2147 this_vs + ". You can still work with this notebook, but some features " +
2159 this_vs + ". You can still work with this notebook, but some features " +
2148 "introduced in later notebook versions may not be available.";
2160 "introduced in later notebook versions may not be available.";
2149
2161
2150 dialog.modal({
2162 dialog.modal({
2151 title : "Newer Notebook",
2163 title : "Newer Notebook",
2152 body : msg,
2164 body : msg,
2153 buttons : {
2165 buttons : {
2154 OK : {
2166 OK : {
2155 class : "btn-danger"
2167 class : "btn-danger"
2156 }
2168 }
2157 }
2169 }
2158 });
2170 });
2159
2171
2160 }
2172 }
2161
2173
2162 // Create the session after the notebook is completely loaded to prevent
2174 // Create the session after the notebook is completely loaded to prevent
2163 // code execution upon loading, which is a security risk.
2175 // code execution upon loading, which is a security risk.
2164 if (this.session === null) {
2176 if (this.session === null) {
2165 this.start_session();
2177 this.start_session();
2166 }
2178 }
2167 // load our checkpoint list
2179 // load our checkpoint list
2168 this.list_checkpoints();
2180 this.list_checkpoints();
2169
2181
2170 // load toolbar state
2182 // load toolbar state
2171 if (this.metadata.celltoolbar) {
2183 if (this.metadata.celltoolbar) {
2172 celltoolbar.CellToolbar.global_show();
2184 celltoolbar.CellToolbar.global_show();
2173 celltoolbar.CellToolbar.activate_preset(this.metadata.celltoolbar);
2185 celltoolbar.CellToolbar.activate_preset(this.metadata.celltoolbar);
2174 } else {
2186 } else {
2175 celltoolbar.CellToolbar.global_hide();
2187 celltoolbar.CellToolbar.global_hide();
2176 }
2188 }
2177
2189
2178 // now that we're fully loaded, it is safe to restore save functionality
2190 // now that we're fully loaded, it is safe to restore save functionality
2179 delete(this.save_notebook);
2191 delete(this.save_notebook);
2180 this.events.trigger('notebook_loaded.Notebook');
2192 this.events.trigger('notebook_loaded.Notebook');
2181 };
2193 };
2182
2194
2183 /**
2195 /**
2184 * Failure callback for loading a notebook from the server.
2196 * Failure callback for loading a notebook from the server.
2185 *
2197 *
2186 * @method load_notebook_error
2198 * @method load_notebook_error
2187 * @param {jqXHR} xhr jQuery Ajax object
2199 * @param {jqXHR} xhr jQuery Ajax object
2188 * @param {String} status Description of response status
2200 * @param {String} status Description of response status
2189 * @param {String} error HTTP error message
2201 * @param {String} error HTTP error message
2190 */
2202 */
2191 Notebook.prototype.load_notebook_error = function (xhr, status, error) {
2203 Notebook.prototype.load_notebook_error = function (xhr, status, error) {
2192 this.events.trigger('notebook_load_failed.Notebook', [xhr, status, error]);
2204 this.events.trigger('notebook_load_failed.Notebook', [xhr, status, error]);
2193 var msg;
2205 var msg;
2194 if (xhr.status === 400) {
2206 if (xhr.status === 400) {
2195 msg = error;
2207 msg = error;
2196 } else if (xhr.status === 500) {
2208 } else if (xhr.status === 500) {
2197 msg = "An unknown error occurred while loading this notebook. " +
2209 msg = "An unknown error occurred while loading this notebook. " +
2198 "This version can load notebook formats " +
2210 "This version can load notebook formats " +
2199 "v" + this.nbformat + " or earlier.";
2211 "v" + this.nbformat + " or earlier.";
2200 }
2212 }
2201 dialog.modal({
2213 dialog.modal({
2202 title: "Error loading notebook",
2214 title: "Error loading notebook",
2203 body : msg,
2215 body : msg,
2204 buttons : {
2216 buttons : {
2205 "OK": {}
2217 "OK": {}
2206 }
2218 }
2207 });
2219 });
2208 };
2220 };
2209
2221
2210 /********************* checkpoint-related *********************/
2222 /********************* checkpoint-related *********************/
2211
2223
2212 /**
2224 /**
2213 * Save the notebook then immediately create a checkpoint.
2225 * Save the notebook then immediately create a checkpoint.
2214 *
2226 *
2215 * @method save_checkpoint
2227 * @method save_checkpoint
2216 */
2228 */
2217 Notebook.prototype.save_checkpoint = function () {
2229 Notebook.prototype.save_checkpoint = function () {
2218 this._checkpoint_after_save = true;
2230 this._checkpoint_after_save = true;
2219 this.save_notebook();
2231 this.save_notebook();
2220 };
2232 };
2221
2233
2222 /**
2234 /**
2223 * Add a checkpoint for this notebook.
2235 * Add a checkpoint for this notebook.
2224 * for use as a callback from checkpoint creation.
2236 * for use as a callback from checkpoint creation.
2225 *
2237 *
2226 * @method add_checkpoint
2238 * @method add_checkpoint
2227 */
2239 */
2228 Notebook.prototype.add_checkpoint = function (checkpoint) {
2240 Notebook.prototype.add_checkpoint = function (checkpoint) {
2229 var found = false;
2241 var found = false;
2230 for (var i = 0; i < this.checkpoints.length; i++) {
2242 for (var i = 0; i < this.checkpoints.length; i++) {
2231 var existing = this.checkpoints[i];
2243 var existing = this.checkpoints[i];
2232 if (existing.id == checkpoint.id) {
2244 if (existing.id == checkpoint.id) {
2233 found = true;
2245 found = true;
2234 this.checkpoints[i] = checkpoint;
2246 this.checkpoints[i] = checkpoint;
2235 break;
2247 break;
2236 }
2248 }
2237 }
2249 }
2238 if (!found) {
2250 if (!found) {
2239 this.checkpoints.push(checkpoint);
2251 this.checkpoints.push(checkpoint);
2240 }
2252 }
2241 this.last_checkpoint = this.checkpoints[this.checkpoints.length - 1];
2253 this.last_checkpoint = this.checkpoints[this.checkpoints.length - 1];
2242 };
2254 };
2243
2255
2244 /**
2256 /**
2245 * List checkpoints for this notebook.
2257 * List checkpoints for this notebook.
2246 *
2258 *
2247 * @method list_checkpoints
2259 * @method list_checkpoints
2248 */
2260 */
2249 Notebook.prototype.list_checkpoints = function () {
2261 Notebook.prototype.list_checkpoints = function () {
2250 var url = utils.url_join_encode(
2262 var url = utils.url_join_encode(
2251 this.base_url,
2263 this.base_url,
2252 'api/notebooks',
2264 'api/notebooks',
2253 this.notebook_path,
2265 this.notebook_path,
2254 this.notebook_name,
2266 this.notebook_name,
2255 'checkpoints'
2267 'checkpoints'
2256 );
2268 );
2257 $.get(url).done(
2269 $.get(url).done(
2258 $.proxy(this.list_checkpoints_success, this)
2270 $.proxy(this.list_checkpoints_success, this)
2259 ).fail(
2271 ).fail(
2260 $.proxy(this.list_checkpoints_error, this)
2272 $.proxy(this.list_checkpoints_error, this)
2261 );
2273 );
2262 };
2274 };
2263
2275
2264 /**
2276 /**
2265 * Success callback for listing checkpoints.
2277 * Success callback for listing checkpoints.
2266 *
2278 *
2267 * @method list_checkpoint_success
2279 * @method list_checkpoint_success
2268 * @param {Object} data JSON representation of a checkpoint
2280 * @param {Object} data JSON representation of a checkpoint
2269 * @param {String} status Description of response status
2281 * @param {String} status Description of response status
2270 * @param {jqXHR} xhr jQuery Ajax object
2282 * @param {jqXHR} xhr jQuery Ajax object
2271 */
2283 */
2272 Notebook.prototype.list_checkpoints_success = function (data, status, xhr) {
2284 Notebook.prototype.list_checkpoints_success = function (data, status, xhr) {
2273 data = $.parseJSON(data);
2285 data = $.parseJSON(data);
2274 this.checkpoints = data;
2286 this.checkpoints = data;
2275 if (data.length) {
2287 if (data.length) {
2276 this.last_checkpoint = data[data.length - 1];
2288 this.last_checkpoint = data[data.length - 1];
2277 } else {
2289 } else {
2278 this.last_checkpoint = null;
2290 this.last_checkpoint = null;
2279 }
2291 }
2280 this.events.trigger('checkpoints_listed.Notebook', [data]);
2292 this.events.trigger('checkpoints_listed.Notebook', [data]);
2281 };
2293 };
2282
2294
2283 /**
2295 /**
2284 * Failure callback for listing a checkpoint.
2296 * Failure callback for listing a checkpoint.
2285 *
2297 *
2286 * @method list_checkpoint_error
2298 * @method list_checkpoint_error
2287 * @param {jqXHR} xhr jQuery Ajax object
2299 * @param {jqXHR} xhr jQuery Ajax object
2288 * @param {String} status Description of response status
2300 * @param {String} status Description of response status
2289 * @param {String} error_msg HTTP error message
2301 * @param {String} error_msg HTTP error message
2290 */
2302 */
2291 Notebook.prototype.list_checkpoints_error = function (xhr, status, error_msg) {
2303 Notebook.prototype.list_checkpoints_error = function (xhr, status, error_msg) {
2292 this.events.trigger('list_checkpoints_failed.Notebook');
2304 this.events.trigger('list_checkpoints_failed.Notebook');
2293 };
2305 };
2294
2306
2295 /**
2307 /**
2296 * Create a checkpoint of this notebook on the server from the most recent save.
2308 * Create a checkpoint of this notebook on the server from the most recent save.
2297 *
2309 *
2298 * @method create_checkpoint
2310 * @method create_checkpoint
2299 */
2311 */
2300 Notebook.prototype.create_checkpoint = function () {
2312 Notebook.prototype.create_checkpoint = function () {
2301 var url = utils.url_join_encode(
2313 var url = utils.url_join_encode(
2302 this.base_url,
2314 this.base_url,
2303 'api/notebooks',
2315 'api/notebooks',
2304 this.notebook_path,
2316 this.notebook_path,
2305 this.notebook_name,
2317 this.notebook_name,
2306 'checkpoints'
2318 'checkpoints'
2307 );
2319 );
2308 $.post(url).done(
2320 $.post(url).done(
2309 $.proxy(this.create_checkpoint_success, this)
2321 $.proxy(this.create_checkpoint_success, this)
2310 ).fail(
2322 ).fail(
2311 $.proxy(this.create_checkpoint_error, this)
2323 $.proxy(this.create_checkpoint_error, this)
2312 );
2324 );
2313 };
2325 };
2314
2326
2315 /**
2327 /**
2316 * Success callback for creating a checkpoint.
2328 * Success callback for creating a checkpoint.
2317 *
2329 *
2318 * @method create_checkpoint_success
2330 * @method create_checkpoint_success
2319 * @param {Object} data JSON representation of a checkpoint
2331 * @param {Object} data JSON representation of a checkpoint
2320 * @param {String} status Description of response status
2332 * @param {String} status Description of response status
2321 * @param {jqXHR} xhr jQuery Ajax object
2333 * @param {jqXHR} xhr jQuery Ajax object
2322 */
2334 */
2323 Notebook.prototype.create_checkpoint_success = function (data, status, xhr) {
2335 Notebook.prototype.create_checkpoint_success = function (data, status, xhr) {
2324 data = $.parseJSON(data);
2336 data = $.parseJSON(data);
2325 this.add_checkpoint(data);
2337 this.add_checkpoint(data);
2326 this.events.trigger('checkpoint_created.Notebook', data);
2338 this.events.trigger('checkpoint_created.Notebook', data);
2327 };
2339 };
2328
2340
2329 /**
2341 /**
2330 * Failure callback for creating a checkpoint.
2342 * Failure callback for creating a checkpoint.
2331 *
2343 *
2332 * @method create_checkpoint_error
2344 * @method create_checkpoint_error
2333 * @param {jqXHR} xhr jQuery Ajax object
2345 * @param {jqXHR} xhr jQuery Ajax object
2334 * @param {String} status Description of response status
2346 * @param {String} status Description of response status
2335 * @param {String} error_msg HTTP error message
2347 * @param {String} error_msg HTTP error message
2336 */
2348 */
2337 Notebook.prototype.create_checkpoint_error = function (xhr, status, error_msg) {
2349 Notebook.prototype.create_checkpoint_error = function (xhr, status, error_msg) {
2338 this.events.trigger('checkpoint_failed.Notebook');
2350 this.events.trigger('checkpoint_failed.Notebook');
2339 };
2351 };
2340
2352
2341 Notebook.prototype.restore_checkpoint_dialog = function (checkpoint) {
2353 Notebook.prototype.restore_checkpoint_dialog = function (checkpoint) {
2342 var that = this;
2354 var that = this;
2343 checkpoint = checkpoint || this.last_checkpoint;
2355 checkpoint = checkpoint || this.last_checkpoint;
2344 if ( ! checkpoint ) {
2356 if ( ! checkpoint ) {
2345 console.log("restore dialog, but no checkpoint to restore to!");
2357 console.log("restore dialog, but no checkpoint to restore to!");
2346 return;
2358 return;
2347 }
2359 }
2348 var body = $('<div/>').append(
2360 var body = $('<div/>').append(
2349 $('<p/>').addClass("p-space").text(
2361 $('<p/>').addClass("p-space").text(
2350 "Are you sure you want to revert the notebook to " +
2362 "Are you sure you want to revert the notebook to " +
2351 "the latest checkpoint?"
2363 "the latest checkpoint?"
2352 ).append(
2364 ).append(
2353 $("<strong/>").text(
2365 $("<strong/>").text(
2354 " This cannot be undone."
2366 " This cannot be undone."
2355 )
2367 )
2356 )
2368 )
2357 ).append(
2369 ).append(
2358 $('<p/>').addClass("p-space").text("The checkpoint was last updated at:")
2370 $('<p/>').addClass("p-space").text("The checkpoint was last updated at:")
2359 ).append(
2371 ).append(
2360 $('<p/>').addClass("p-space").text(
2372 $('<p/>').addClass("p-space").text(
2361 Date(checkpoint.last_modified)
2373 Date(checkpoint.last_modified)
2362 ).css("text-align", "center")
2374 ).css("text-align", "center")
2363 );
2375 );
2364
2376
2365 dialog.modal({
2377 dialog.modal({
2366 title : "Revert notebook to checkpoint",
2378 title : "Revert notebook to checkpoint",
2367 body : body,
2379 body : body,
2368 buttons : {
2380 buttons : {
2369 Revert : {
2381 Revert : {
2370 class : "btn-danger",
2382 class : "btn-danger",
2371 click : function () {
2383 click : function () {
2372 that.restore_checkpoint(checkpoint.id);
2384 that.restore_checkpoint(checkpoint.id);
2373 }
2385 }
2374 },
2386 },
2375 Cancel : {}
2387 Cancel : {}
2376 }
2388 }
2377 });
2389 });
2378 };
2390 };
2379
2391
2380 /**
2392 /**
2381 * Restore the notebook to a checkpoint state.
2393 * Restore the notebook to a checkpoint state.
2382 *
2394 *
2383 * @method restore_checkpoint
2395 * @method restore_checkpoint
2384 * @param {String} checkpoint ID
2396 * @param {String} checkpoint ID
2385 */
2397 */
2386 Notebook.prototype.restore_checkpoint = function (checkpoint) {
2398 Notebook.prototype.restore_checkpoint = function (checkpoint) {
2387 this.events.trigger('notebook_restoring.Notebook', checkpoint);
2399 this.events.trigger('notebook_restoring.Notebook', checkpoint);
2388 var url = utils.url_join_encode(
2400 var url = utils.url_join_encode(
2389 this.base_url,
2401 this.base_url,
2390 'api/notebooks',
2402 'api/notebooks',
2391 this.notebook_path,
2403 this.notebook_path,
2392 this.notebook_name,
2404 this.notebook_name,
2393 'checkpoints',
2405 'checkpoints',
2394 checkpoint
2406 checkpoint
2395 );
2407 );
2396 $.post(url).done(
2408 $.post(url).done(
2397 $.proxy(this.restore_checkpoint_success, this)
2409 $.proxy(this.restore_checkpoint_success, this)
2398 ).fail(
2410 ).fail(
2399 $.proxy(this.restore_checkpoint_error, this)
2411 $.proxy(this.restore_checkpoint_error, this)
2400 );
2412 );
2401 };
2413 };
2402
2414
2403 /**
2415 /**
2404 * Success callback for restoring a notebook to a checkpoint.
2416 * Success callback for restoring a notebook to a checkpoint.
2405 *
2417 *
2406 * @method restore_checkpoint_success
2418 * @method restore_checkpoint_success
2407 * @param {Object} data (ignored, should be empty)
2419 * @param {Object} data (ignored, should be empty)
2408 * @param {String} status Description of response status
2420 * @param {String} status Description of response status
2409 * @param {jqXHR} xhr jQuery Ajax object
2421 * @param {jqXHR} xhr jQuery Ajax object
2410 */
2422 */
2411 Notebook.prototype.restore_checkpoint_success = function (data, status, xhr) {
2423 Notebook.prototype.restore_checkpoint_success = function (data, status, xhr) {
2412 this.events.trigger('checkpoint_restored.Notebook');
2424 this.events.trigger('checkpoint_restored.Notebook');
2413 this.load_notebook(this.notebook_name, this.notebook_path);
2425 this.load_notebook(this.notebook_name, this.notebook_path);
2414 };
2426 };
2415
2427
2416 /**
2428 /**
2417 * Failure callback for restoring a notebook to a checkpoint.
2429 * Failure callback for restoring a notebook to a checkpoint.
2418 *
2430 *
2419 * @method restore_checkpoint_error
2431 * @method restore_checkpoint_error
2420 * @param {jqXHR} xhr jQuery Ajax object
2432 * @param {jqXHR} xhr jQuery Ajax object
2421 * @param {String} status Description of response status
2433 * @param {String} status Description of response status
2422 * @param {String} error_msg HTTP error message
2434 * @param {String} error_msg HTTP error message
2423 */
2435 */
2424 Notebook.prototype.restore_checkpoint_error = function (xhr, status, error_msg) {
2436 Notebook.prototype.restore_checkpoint_error = function (xhr, status, error_msg) {
2425 this.events.trigger('checkpoint_restore_failed.Notebook');
2437 this.events.trigger('checkpoint_restore_failed.Notebook');
2426 };
2438 };
2427
2439
2428 /**
2440 /**
2429 * Delete a notebook checkpoint.
2441 * Delete a notebook checkpoint.
2430 *
2442 *
2431 * @method delete_checkpoint
2443 * @method delete_checkpoint
2432 * @param {String} checkpoint ID
2444 * @param {String} checkpoint ID
2433 */
2445 */
2434 Notebook.prototype.delete_checkpoint = function (checkpoint) {
2446 Notebook.prototype.delete_checkpoint = function (checkpoint) {
2435 this.events.trigger('notebook_restoring.Notebook', checkpoint);
2447 this.events.trigger('notebook_restoring.Notebook', checkpoint);
2436 var url = utils.url_join_encode(
2448 var url = utils.url_join_encode(
2437 this.base_url,
2449 this.base_url,
2438 'api/notebooks',
2450 'api/notebooks',
2439 this.notebook_path,
2451 this.notebook_path,
2440 this.notebook_name,
2452 this.notebook_name,
2441 'checkpoints',
2453 'checkpoints',
2442 checkpoint
2454 checkpoint
2443 );
2455 );
2444 $.ajax(url, {
2456 $.ajax(url, {
2445 type: 'DELETE',
2457 type: 'DELETE',
2446 success: $.proxy(this.delete_checkpoint_success, this),
2458 success: $.proxy(this.delete_checkpoint_success, this),
2447 error: $.proxy(this.delete_checkpoint_error, this)
2459 error: $.proxy(this.delete_checkpoint_error, this)
2448 });
2460 });
2449 };
2461 };
2450
2462
2451 /**
2463 /**
2452 * Success callback for deleting a notebook checkpoint
2464 * Success callback for deleting a notebook checkpoint
2453 *
2465 *
2454 * @method delete_checkpoint_success
2466 * @method delete_checkpoint_success
2455 * @param {Object} data (ignored, should be empty)
2467 * @param {Object} data (ignored, should be empty)
2456 * @param {String} status Description of response status
2468 * @param {String} status Description of response status
2457 * @param {jqXHR} xhr jQuery Ajax object
2469 * @param {jqXHR} xhr jQuery Ajax object
2458 */
2470 */
2459 Notebook.prototype.delete_checkpoint_success = function (data, status, xhr) {
2471 Notebook.prototype.delete_checkpoint_success = function (data, status, xhr) {
2460 this.events.trigger('checkpoint_deleted.Notebook', data);
2472 this.events.trigger('checkpoint_deleted.Notebook', data);
2461 this.load_notebook(this.notebook_name, this.notebook_path);
2473 this.load_notebook(this.notebook_name, this.notebook_path);
2462 };
2474 };
2463
2475
2464 /**
2476 /**
2465 * Failure callback for deleting a notebook checkpoint.
2477 * Failure callback for deleting a notebook checkpoint.
2466 *
2478 *
2467 * @method delete_checkpoint_error
2479 * @method delete_checkpoint_error
2468 * @param {jqXHR} xhr jQuery Ajax object
2480 * @param {jqXHR} xhr jQuery Ajax object
2469 * @param {String} status Description of response status
2481 * @param {String} status Description of response status
2470 * @param {String} error_msg HTTP error message
2482 * @param {String} error_msg HTTP error message
2471 */
2483 */
2472 Notebook.prototype.delete_checkpoint_error = function (xhr, status, error_msg) {
2484 Notebook.prototype.delete_checkpoint_error = function (xhr, status, error_msg) {
2473 this.events.trigger('checkpoint_delete_failed.Notebook');
2485 this.events.trigger('checkpoint_delete_failed.Notebook');
2474 };
2486 };
2475
2487
2476
2488
2477 // For backwards compatability.
2489 // For backwards compatability.
2478 IPython.Notebook = Notebook;
2490 IPython.Notebook = Notebook;
2479
2491
2480 return {'Notebook': Notebook};
2492 return {'Notebook': Notebook};
2481 });
2493 });
@@ -1,233 +1,233 b''
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 'notebook/js/notificationwidget',
9 'notebook/js/notificationwidget',
10 ], function(IPython, $, utils, dialog, notificationwidget) {
10 ], function(IPython, $, utils, dialog, notificationwidget) {
11 "use strict";
11 "use strict";
12
12
13 var NotificationArea = function (selector, events, save_widget, notebook) {
13 var NotificationArea = function (selector, options) {
14 this.selector = selector;
14 this.selector = selector;
15 this.events = events;
15 this.events = options.events;
16 this.save_widget = save_widget;
16 this.save_widget = options.save_widget;
17 this.notebook = notebook;
17 this.notebook = options.notebook;
18 if (this.selector !== undefined) {
18 if (this.selector !== undefined) {
19 this.element = $(selector);
19 this.element = $(selector);
20 }
20 }
21 this.widget_dict = {};
21 this.widget_dict = {};
22 };
22 };
23
23
24 NotificationArea.prototype.temp_message = function (msg, timeout, css_class) {
24 NotificationArea.prototype.temp_message = function (msg, timeout, css_class) {
25 var uuid = utils.uuid();
25 var uuid = utils.uuid();
26 if( css_class == 'danger') {css_class = 'ui-state-error';}
26 if( css_class == 'danger') {css_class = 'ui-state-error';}
27 if( css_class == 'warning') {css_class = 'ui-state-highlight';}
27 if( css_class == 'warning') {css_class = 'ui-state-highlight';}
28 var tdiv = $('<div>')
28 var tdiv = $('<div>')
29 .attr('id',uuid)
29 .attr('id',uuid)
30 .addClass('notification_widget ui-widget ui-widget-content ui-corner-all')
30 .addClass('notification_widget ui-widget ui-widget-content ui-corner-all')
31 .addClass('border-box-sizing')
31 .addClass('border-box-sizing')
32 .addClass(css_class)
32 .addClass(css_class)
33 .hide()
33 .hide()
34 .text(msg);
34 .text(msg);
35
35
36 $(this.selector).append(tdiv);
36 $(this.selector).append(tdiv);
37 var tmout = Math.max(1500,(timeout||1500));
37 var tmout = Math.max(1500,(timeout||1500));
38 tdiv.fadeIn(100);
38 tdiv.fadeIn(100);
39
39
40 setTimeout(function () {
40 setTimeout(function () {
41 tdiv.fadeOut(100, function () {tdiv.remove();});
41 tdiv.fadeOut(100, function () {tdiv.remove();});
42 }, tmout);
42 }, tmout);
43 };
43 };
44
44
45 NotificationArea.prototype.widget = function(name) {
45 NotificationArea.prototype.widget = function(name) {
46 if(this.widget_dict[name] === undefined) {
46 if(this.widget_dict[name] === undefined) {
47 return this.new_notification_widget(name);
47 return this.new_notification_widget(name);
48 }
48 }
49 return this.get_widget(name);
49 return this.get_widget(name);
50 };
50 };
51
51
52 NotificationArea.prototype.get_widget = function(name) {
52 NotificationArea.prototype.get_widget = function(name) {
53 if(this.widget_dict[name] === undefined) {
53 if(this.widget_dict[name] === undefined) {
54 throw('no widgets with this name');
54 throw('no widgets with this name');
55 }
55 }
56 return this.widget_dict[name];
56 return this.widget_dict[name];
57 };
57 };
58
58
59 NotificationArea.prototype.new_notification_widget = function(name) {
59 NotificationArea.prototype.new_notification_widget = function(name) {
60 if(this.widget_dict[name] !== undefined) {
60 if(this.widget_dict[name] !== undefined) {
61 throw('widget with that name already exists ! ');
61 throw('widget with that name already exists ! ');
62 }
62 }
63 var div = $('<div/>').attr('id','notification_'+name);
63 var div = $('<div/>').attr('id','notification_'+name);
64 $(this.selector).append(div);
64 $(this.selector).append(div);
65 this.widget_dict[name] = new notificationwidget.NotificationWidget('#notification_'+name);
65 this.widget_dict[name] = new notificationwidget.NotificationWidget('#notification_'+name);
66 return this.widget_dict[name];
66 return this.widget_dict[name];
67 };
67 };
68
68
69 NotificationArea.prototype.init_notification_widgets = function() {
69 NotificationArea.prototype.init_notification_widgets = function() {
70 var that = this;
70 var that = this;
71 var knw = this.new_notification_widget('kernel');
71 var knw = this.new_notification_widget('kernel');
72 var $kernel_ind_icon = $("#kernel_indicator_icon");
72 var $kernel_ind_icon = $("#kernel_indicator_icon");
73 var $modal_ind_icon = $("#modal_indicator_icon");
73 var $modal_ind_icon = $("#modal_indicator_icon");
74
74
75 // Command/Edit mode
75 // Command/Edit mode
76 this.events.on('edit_mode.Notebook',function () {
76 this.events.on('edit_mode.Notebook',function () {
77 that.save_widget.update_document_title();
77 that.save_widget.update_document_title();
78 $modal_ind_icon.attr('class','edit_mode_icon').attr('title','Edit Mode');
78 $modal_ind_icon.attr('class','edit_mode_icon').attr('title','Edit Mode');
79 });
79 });
80
80
81 this.events.on('command_mode.Notebook',function () {
81 this.events.on('command_mode.Notebook',function () {
82 that.save_widget.update_document_title();
82 that.save_widget.update_document_title();
83 $modal_ind_icon.attr('class','command_mode_icon').attr('title','Command Mode');
83 $modal_ind_icon.attr('class','command_mode_icon').attr('title','Command Mode');
84 });
84 });
85
85
86 // Implicitly start off in Command mode, switching to Edit mode will trigger event
86 // Implicitly start off in Command mode, switching to Edit mode will trigger event
87 $modal_ind_icon.attr('class','command-mode_icon').attr('title','Command Mode');
87 $modal_ind_icon.attr('class','command-mode_icon').attr('title','Command Mode');
88
88
89 // Kernel events
89 // Kernel events
90 this.events.on('status_idle.Kernel',function () {
90 this.events.on('status_idle.Kernel',function () {
91 that.save_widget.update_document_title();
91 that.save_widget.update_document_title();
92 $kernel_ind_icon.attr('class','kernel_idle_icon').attr('title','Kernel Idle');
92 $kernel_ind_icon.attr('class','kernel_idle_icon').attr('title','Kernel Idle');
93 });
93 });
94
94
95 this.events.on('status_busy.Kernel',function () {
95 this.events.on('status_busy.Kernel',function () {
96 window.document.title='(Busy) '+window.document.title;
96 window.document.title='(Busy) '+window.document.title;
97 $kernel_ind_icon.attr('class','kernel_busy_icon').attr('title','Kernel Busy');
97 $kernel_ind_icon.attr('class','kernel_busy_icon').attr('title','Kernel Busy');
98 });
98 });
99
99
100 this.events.on('status_restarting.Kernel',function () {
100 this.events.on('status_restarting.Kernel',function () {
101 that.save_widget.update_document_title();
101 that.save_widget.update_document_title();
102 knw.set_message("Restarting kernel", 2000);
102 knw.set_message("Restarting kernel", 2000);
103 });
103 });
104
104
105 this.events.on('status_interrupting.Kernel',function () {
105 this.events.on('status_interrupting.Kernel',function () {
106 knw.set_message("Interrupting kernel", 2000);
106 knw.set_message("Interrupting kernel", 2000);
107 });
107 });
108
108
109 // Start the kernel indicator in the busy state, and send a kernel_info request.
109 // Start the kernel indicator in the busy state, and send a kernel_info request.
110 // When the kernel_info reply arrives, the kernel is idle.
110 // When the kernel_info reply arrives, the kernel is idle.
111 $kernel_ind_icon.attr('class','kernel_busy_icon').attr('title','Kernel Busy');
111 $kernel_ind_icon.attr('class','kernel_busy_icon').attr('title','Kernel Busy');
112
112
113 this.events.on('status_started.Kernel', function (evt, data) {
113 this.events.on('status_started.Kernel', function (evt, data) {
114 data.kernel.kernel_info(function () {
114 data.kernel.kernel_info(function () {
115 that.events.trigger('status_idle.Kernel');
115 that.events.trigger('status_idle.Kernel');
116 });
116 });
117 });
117 });
118
118
119 this.events.on('status_dead.Kernel',function () {
119 this.events.on('status_dead.Kernel',function () {
120 var msg = 'The kernel has died, and the automatic restart has failed.' +
120 var msg = 'The kernel has died, and the automatic restart has failed.' +
121 ' It is possible the kernel cannot be restarted.' +
121 ' It is possible the kernel cannot be restarted.' +
122 ' If you are not able to restart the kernel, you will still be able to save' +
122 ' If you are not able to restart the kernel, you will still be able to save' +
123 ' the notebook, but running code will no longer work until the notebook' +
123 ' the notebook, but running code will no longer work until the notebook' +
124 ' is reopened.';
124 ' is reopened.';
125
125
126 dialog.modal({
126 dialog.modal({
127 title: "Dead kernel",
127 title: "Dead kernel",
128 body : msg,
128 body : msg,
129 buttons : {
129 buttons : {
130 "Manual Restart": {
130 "Manual Restart": {
131 class: "btn-danger",
131 class: "btn-danger",
132 click: function () {
132 click: function () {
133 that.events.trigger('status_restarting.Kernel');
133 that.events.trigger('status_restarting.Kernel');
134 that.notebook.start_kernel();
134 that.notebook.start_kernel();
135 }
135 }
136 },
136 },
137 "Don't restart": {}
137 "Don't restart": {}
138 }
138 }
139 });
139 });
140 });
140 });
141
141
142 this.events.on('websocket_closed.Kernel', function (event, data) {
142 this.events.on('websocket_closed.Kernel', function (event, data) {
143 var kernel = data.kernel;
143 var kernel = data.kernel;
144 var ws_url = data.ws_url;
144 var ws_url = data.ws_url;
145 var early = data.early;
145 var early = data.early;
146 var msg;
146 var msg;
147 if (!early) {
147 if (!early) {
148 knw.set_message('Reconnecting WebSockets', 1000);
148 knw.set_message('Reconnecting WebSockets', 1000);
149 setTimeout(function () {
149 setTimeout(function () {
150 kernel.start_channels();
150 kernel.start_channels();
151 }, 5000);
151 }, 5000);
152 return;
152 return;
153 }
153 }
154 console.log('WebSocket connection failed: ', ws_url);
154 console.log('WebSocket connection failed: ', ws_url);
155 msg = "A WebSocket connection could not be established." +
155 msg = "A WebSocket connection could not be established." +
156 " You will NOT be able to run code. Check your" +
156 " You will NOT be able to run code. Check your" +
157 " network connection or notebook server configuration.";
157 " network connection or notebook server configuration.";
158 dialog.modal({
158 dialog.modal({
159 title: "WebSocket connection failed",
159 title: "WebSocket connection failed",
160 body: msg,
160 body: msg,
161 buttons : {
161 buttons : {
162 "OK": {},
162 "OK": {},
163 "Reconnect": {
163 "Reconnect": {
164 click: function () {
164 click: function () {
165 knw.set_message('Reconnecting WebSockets', 1000);
165 knw.set_message('Reconnecting WebSockets', 1000);
166 setTimeout(function () {
166 setTimeout(function () {
167 kernel.start_channels();
167 kernel.start_channels();
168 }, 5000);
168 }, 5000);
169 }
169 }
170 }
170 }
171 }
171 }
172 });
172 });
173 });
173 });
174
174
175
175
176 var nnw = this.new_notification_widget('notebook');
176 var nnw = this.new_notification_widget('notebook');
177
177
178 // Notebook events
178 // Notebook events
179 this.events.on('notebook_loading.Notebook', function () {
179 this.events.on('notebook_loading.Notebook', function () {
180 nnw.set_message("Loading notebook",500);
180 nnw.set_message("Loading notebook",500);
181 });
181 });
182 this.events.on('notebook_loaded.Notebook', function () {
182 this.events.on('notebook_loaded.Notebook', function () {
183 nnw.set_message("Notebook loaded",500);
183 nnw.set_message("Notebook loaded",500);
184 });
184 });
185 this.events.on('notebook_saving.Notebook', function () {
185 this.events.on('notebook_saving.Notebook', function () {
186 nnw.set_message("Saving notebook",500);
186 nnw.set_message("Saving notebook",500);
187 });
187 });
188 this.events.on('notebook_saved.Notebook', function () {
188 this.events.on('notebook_saved.Notebook', function () {
189 nnw.set_message("Notebook saved",2000);
189 nnw.set_message("Notebook saved",2000);
190 });
190 });
191 this.events.on('notebook_save_failed.Notebook', function (evt, xhr, status, data) {
191 this.events.on('notebook_save_failed.Notebook', function (evt, xhr, status, data) {
192 nnw.set_message(data || "Notebook save failed");
192 nnw.set_message(data || "Notebook save failed");
193 });
193 });
194
194
195 // Checkpoint events
195 // Checkpoint events
196 this.events.on('checkpoint_created.Notebook', function (evt, data) {
196 this.events.on('checkpoint_created.Notebook', function (evt, data) {
197 var msg = "Checkpoint created";
197 var msg = "Checkpoint created";
198 if (data.last_modified) {
198 if (data.last_modified) {
199 var d = new Date(data.last_modified);
199 var d = new Date(data.last_modified);
200 msg = msg + ": " + d.format("HH:MM:ss");
200 msg = msg + ": " + d.format("HH:MM:ss");
201 }
201 }
202 nnw.set_message(msg, 2000);
202 nnw.set_message(msg, 2000);
203 });
203 });
204 this.events.on('checkpoint_failed.Notebook', function () {
204 this.events.on('checkpoint_failed.Notebook', function () {
205 nnw.set_message("Checkpoint failed");
205 nnw.set_message("Checkpoint failed");
206 });
206 });
207 this.events.on('checkpoint_deleted.Notebook', function () {
207 this.events.on('checkpoint_deleted.Notebook', function () {
208 nnw.set_message("Checkpoint deleted", 500);
208 nnw.set_message("Checkpoint deleted", 500);
209 });
209 });
210 this.events.on('checkpoint_delete_failed.Notebook', function () {
210 this.events.on('checkpoint_delete_failed.Notebook', function () {
211 nnw.set_message("Checkpoint delete failed");
211 nnw.set_message("Checkpoint delete failed");
212 });
212 });
213 this.events.on('checkpoint_restoring.Notebook', function () {
213 this.events.on('checkpoint_restoring.Notebook', function () {
214 nnw.set_message("Restoring to checkpoint...", 500);
214 nnw.set_message("Restoring to checkpoint...", 500);
215 });
215 });
216 this.events.on('checkpoint_restore_failed.Notebook', function () {
216 this.events.on('checkpoint_restore_failed.Notebook', function () {
217 nnw.set_message("Checkpoint restore failed");
217 nnw.set_message("Checkpoint restore failed");
218 });
218 });
219
219
220 // Autosave events
220 // Autosave events
221 this.events.on('autosave_disabled.Notebook', function () {
221 this.events.on('autosave_disabled.Notebook', function () {
222 nnw.set_message("Autosave disabled", 2000);
222 nnw.set_message("Autosave disabled", 2000);
223 });
223 });
224 this.events.on('autosave_enabled.Notebook', function (evt, interval) {
224 this.events.on('autosave_enabled.Notebook', function (evt, interval) {
225 nnw.set_message("Saving every " + interval / 1000 + "s", 1000);
225 nnw.set_message("Saving every " + interval / 1000 + "s", 1000);
226 });
226 });
227
227
228 };
228 };
229
229
230 IPython.NotificationArea = NotificationArea;
230 IPython.NotificationArea = NotificationArea;
231
231
232 return {'NotificationArea': NotificationArea};
232 return {'NotificationArea': NotificationArea};
233 });
233 });
@@ -1,178 +1,178 b''
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 ], function(IPython, $, utils) {
8 ], function(IPython, $, utils) {
9 "use strict";
9 "use strict";
10
10
11 var Pager = function (pager_selector, pager_splitter_selector, layout_manager, events) {
11 var Pager = function (pager_selector, pager_splitter_selector, options) {
12 this.events = events;
12 this.events = options.events;
13 this.pager_element = $(pager_selector);
13 this.pager_element = $(pager_selector);
14 this.pager_button_area = $('#pager_button_area');
14 this.pager_button_area = $('#pager_button_area');
15 var that = this;
15 var that = this;
16 this.percentage_height = 0.40;
16 this.percentage_height = 0.40;
17 layout_manager.pager = this;
17 options.layout_manager.pager = this;
18 this.pager_splitter_element = $(pager_splitter_selector)
18 this.pager_splitter_element = $(pager_splitter_selector)
19 .draggable({
19 .draggable({
20 containment: 'window',
20 containment: 'window',
21 axis:'y',
21 axis:'y',
22 helper: null ,
22 helper: null ,
23 drag: function(event, ui) {
23 drag: function(event, ui) {
24 // recalculate the amount of space the pager should take
24 // recalculate the amount of space the pager should take
25 var pheight = ($(document.body).height()-event.clientY-4);
25 var pheight = ($(document.body).height()-event.clientY-4);
26 var downprct = pheight/layout_manager.app_height();
26 var downprct = pheight/options.layout_manager.app_height();
27 downprct = Math.min(0.9, downprct);
27 downprct = Math.min(0.9, downprct);
28 if (downprct < 0.1) {
28 if (downprct < 0.1) {
29 that.percentage_height = 0.1;
29 that.percentage_height = 0.1;
30 that.collapse({'duration':0});
30 that.collapse({'duration':0});
31 } else if (downprct > 0.2) {
31 } else if (downprct > 0.2) {
32 that.percentage_height = downprct;
32 that.percentage_height = downprct;
33 that.expand({'duration':0});
33 that.expand({'duration':0});
34 }
34 }
35 layout_manager.do_resize();
35 options.layout_manager.do_resize();
36 }
36 }
37 });
37 });
38 this.expanded = false;
38 this.expanded = false;
39 this.style();
39 this.style();
40 this.create_button_area();
40 this.create_button_area();
41 this.bind_events();
41 this.bind_events();
42 };
42 };
43
43
44 Pager.prototype.create_button_area = function(){
44 Pager.prototype.create_button_area = function(){
45 var that = this;
45 var that = this;
46 this.pager_button_area.append(
46 this.pager_button_area.append(
47 $('<a>').attr('role', "button")
47 $('<a>').attr('role', "button")
48 .attr('title',"Open the pager in an external window")
48 .attr('title',"Open the pager in an external window")
49 .addClass('ui-button')
49 .addClass('ui-button')
50 .click(function(){that.detach();})
50 .click(function(){that.detach();})
51 .attr('style','position: absolute; right: 20px;')
51 .attr('style','position: absolute; right: 20px;')
52 .append(
52 .append(
53 $('<span>').addClass("ui-icon ui-icon-extlink")
53 $('<span>').addClass("ui-icon ui-icon-extlink")
54 )
54 )
55 );
55 );
56 this.pager_button_area.append(
56 this.pager_button_area.append(
57 $('<a>').attr('role', "button")
57 $('<a>').attr('role', "button")
58 .attr('title',"Close the pager")
58 .attr('title',"Close the pager")
59 .addClass('ui-button')
59 .addClass('ui-button')
60 .click(function(){that.collapse();})
60 .click(function(){that.collapse();})
61 .attr('style','position: absolute; right: 5px;')
61 .attr('style','position: absolute; right: 5px;')
62 .append(
62 .append(
63 $('<span>').addClass("ui-icon ui-icon-close")
63 $('<span>').addClass("ui-icon ui-icon-close")
64 )
64 )
65 );
65 );
66 };
66 };
67
67
68 Pager.prototype.style = function () {
68 Pager.prototype.style = function () {
69 this.pager_splitter_element.addClass('border-box-sizing ui-widget ui-state-default');
69 this.pager_splitter_element.addClass('border-box-sizing ui-widget ui-state-default');
70 this.pager_element.addClass('border-box-sizing');
70 this.pager_element.addClass('border-box-sizing');
71 this.pager_element.find(".container").addClass('border-box-sizing');
71 this.pager_element.find(".container").addClass('border-box-sizing');
72 this.pager_splitter_element.attr('title', 'Click to Show/Hide pager area, drag to Resize');
72 this.pager_splitter_element.attr('title', 'Click to Show/Hide pager area, drag to Resize');
73 };
73 };
74
74
75
75
76 Pager.prototype.bind_events = function () {
76 Pager.prototype.bind_events = function () {
77 var that = this;
77 var that = this;
78
78
79 this.pager_element.bind('collapse_pager', function (event, extrap) {
79 this.pager_element.bind('collapse_pager', function (event, extrap) {
80 var time = 'fast';
80 var time = 'fast';
81 if (extrap && extrap.duration) {
81 if (extrap && extrap.duration) {
82 time = extrap.duration;
82 time = extrap.duration;
83 }
83 }
84 that.pager_element.hide(time);
84 that.pager_element.hide(time);
85 });
85 });
86
86
87 this.pager_element.bind('expand_pager', function (event, extrap) {
87 this.pager_element.bind('expand_pager', function (event, extrap) {
88 var time = 'fast';
88 var time = 'fast';
89 if (extrap && extrap.duration) {
89 if (extrap && extrap.duration) {
90 time = extrap.duration;
90 time = extrap.duration;
91 }
91 }
92 that.pager_element.show(time);
92 that.pager_element.show(time);
93 });
93 });
94
94
95 this.pager_splitter_element.hover(
95 this.pager_splitter_element.hover(
96 function () {
96 function () {
97 that.pager_splitter_element.addClass('ui-state-hover');
97 that.pager_splitter_element.addClass('ui-state-hover');
98 },
98 },
99 function () {
99 function () {
100 that.pager_splitter_element.removeClass('ui-state-hover');
100 that.pager_splitter_element.removeClass('ui-state-hover');
101 }
101 }
102 );
102 );
103
103
104 this.pager_splitter_element.click(function () {
104 this.pager_splitter_element.click(function () {
105 that.toggle();
105 that.toggle();
106 });
106 });
107
107
108 this.events.on('open_with_text.Pager', function (event, payload) {
108 this.events.on('open_with_text.Pager', function (event, payload) {
109 // FIXME: support other mime types
109 // FIXME: support other mime types
110 if (payload.data['text/plain'] && payload.data['text/plain'] !== "") {
110 if (payload.data['text/plain'] && payload.data['text/plain'] !== "") {
111 that.clear();
111 that.clear();
112 that.expand();
112 that.expand();
113 that.append_text(payload.data['text/plain']);
113 that.append_text(payload.data['text/plain']);
114 }
114 }
115 });
115 });
116 };
116 };
117
117
118
118
119 Pager.prototype.collapse = function (extrap) {
119 Pager.prototype.collapse = function (extrap) {
120 if (this.expanded === true) {
120 if (this.expanded === true) {
121 this.expanded = false;
121 this.expanded = false;
122 this.pager_element.add($('div#notebook')).trigger('collapse_pager', extrap);
122 this.pager_element.add($('div#notebook')).trigger('collapse_pager', extrap);
123 }
123 }
124 };
124 };
125
125
126
126
127 Pager.prototype.expand = function (extrap) {
127 Pager.prototype.expand = function (extrap) {
128 if (this.expanded !== true) {
128 if (this.expanded !== true) {
129 this.expanded = true;
129 this.expanded = true;
130 this.pager_element.add($('div#notebook')).trigger('expand_pager', extrap);
130 this.pager_element.add($('div#notebook')).trigger('expand_pager', extrap);
131 }
131 }
132 };
132 };
133
133
134
134
135 Pager.prototype.toggle = function () {
135 Pager.prototype.toggle = function () {
136 if (this.expanded === true) {
136 if (this.expanded === true) {
137 this.collapse();
137 this.collapse();
138 } else {
138 } else {
139 this.expand();
139 this.expand();
140 }
140 }
141 };
141 };
142
142
143
143
144 Pager.prototype.clear = function (text) {
144 Pager.prototype.clear = function (text) {
145 this.pager_element.find(".container").empty();
145 this.pager_element.find(".container").empty();
146 };
146 };
147
147
148 Pager.prototype.detach = function(){
148 Pager.prototype.detach = function(){
149 var w = window.open("","_blank");
149 var w = window.open("","_blank");
150 $(w.document.head)
150 $(w.document.head)
151 .append(
151 .append(
152 $('<link>')
152 $('<link>')
153 .attr('rel',"stylesheet")
153 .attr('rel',"stylesheet")
154 .attr('href',"/static/css/notebook.css")
154 .attr('href',"/static/css/notebook.css")
155 .attr('type',"text/css")
155 .attr('type',"text/css")
156 )
156 )
157 .append(
157 .append(
158 $('<title>').text("IPython Pager")
158 $('<title>').text("IPython Pager")
159 );
159 );
160 var pager_body = $(w.document.body);
160 var pager_body = $(w.document.body);
161 pager_body.css('overflow','scroll');
161 pager_body.css('overflow','scroll');
162
162
163 pager_body.append(this.pager_element.clone().children());
163 pager_body.append(this.pager_element.clone().children());
164 w.document.close();
164 w.document.close();
165 this.collapse();
165 this.collapse();
166 };
166 };
167
167
168 Pager.prototype.append_text = function (text) {
168 Pager.prototype.append_text = function (text) {
169 // The only user content injected with this HTML call is escaped by
169 // The only user content injected with this HTML call is escaped by
170 // the fixConsole() method.
170 // the fixConsole() method.
171 this.pager_element.find(".container").append($('<pre/>').html(utils.fixCarriageReturn(utils.fixConsole(text))));
171 this.pager_element.find(".container").append($('<pre/>').html(utils.fixCarriageReturn(utils.fixConsole(text))));
172 };
172 };
173
173
174 // Backwards compatability.
174 // Backwards compatability.
175 IPython.Pager = Pager;
175 IPython.Pager = Pager;
176
176
177 return {'Pager': Pager};
177 return {'Pager': Pager};
178 });
178 });
@@ -1,173 +1,173 b''
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 ], function(IPython, $, utils, dialog) {
10 "use strict";
10 "use strict";
11 var platform = utils.platform;
11 var platform = utils.platform;
12
12
13 var QuickHelp = function (selector, keyboard_manager, events) {
13 var QuickHelp = function (options) {
14 this.keyboard_manager = keyboard_manager;
14 this.keyboard_manager = options.keyboard_manager;
15 keyboard_manager.quick_help = this;
15 this.keyboard_manager.quick_help = this;
16 this.events = events;
16 this.events = options.events;
17 };
17 };
18
18
19 var cmd_ctrl = 'Ctrl-';
19 var cmd_ctrl = 'Ctrl-';
20 var platform_specific;
20 var platform_specific;
21
21
22 if (platform === 'MacOS') {
22 if (platform === 'MacOS') {
23 // Mac OS X specific
23 // Mac OS X specific
24 cmd_ctrl = 'Cmd-';
24 cmd_ctrl = 'Cmd-';
25 platform_specific = [
25 platform_specific = [
26 { shortcut: "Cmd-Up", help:"go to cell start" },
26 { shortcut: "Cmd-Up", help:"go to cell start" },
27 { shortcut: "Cmd-Down", help:"go to cell end" },
27 { shortcut: "Cmd-Down", help:"go to cell end" },
28 { shortcut: "Opt-Left", help:"go one word left" },
28 { shortcut: "Opt-Left", help:"go one word left" },
29 { shortcut: "Opt-Right", help:"go one word right" },
29 { shortcut: "Opt-Right", help:"go one word right" },
30 { shortcut: "Opt-Backspace", help:"del word before" },
30 { shortcut: "Opt-Backspace", help:"del word before" },
31 { shortcut: "Opt-Delete", help:"del word after" },
31 { shortcut: "Opt-Delete", help:"del word after" },
32 ];
32 ];
33 } else {
33 } else {
34 // PC specific
34 // PC specific
35 platform_specific = [
35 platform_specific = [
36 { shortcut: "Ctrl-Home", help:"go to cell start" },
36 { shortcut: "Ctrl-Home", help:"go to cell start" },
37 { shortcut: "Ctrl-Up", help:"go to cell start" },
37 { shortcut: "Ctrl-Up", help:"go to cell start" },
38 { shortcut: "Ctrl-End", help:"go to cell end" },
38 { shortcut: "Ctrl-End", help:"go to cell end" },
39 { shortcut: "Ctrl-Down", help:"go to cell end" },
39 { shortcut: "Ctrl-Down", help:"go to cell end" },
40 { shortcut: "Ctrl-Left", help:"go one word left" },
40 { shortcut: "Ctrl-Left", help:"go one word left" },
41 { shortcut: "Ctrl-Right", help:"go one word right" },
41 { shortcut: "Ctrl-Right", help:"go one word right" },
42 { shortcut: "Ctrl-Backspace", help:"del word before" },
42 { shortcut: "Ctrl-Backspace", help:"del word before" },
43 { shortcut: "Ctrl-Delete", help:"del word after" },
43 { shortcut: "Ctrl-Delete", help:"del word after" },
44 ];
44 ];
45 }
45 }
46
46
47 var cm_shortcuts = [
47 var cm_shortcuts = [
48 { shortcut:"Tab", help:"code completion or indent" },
48 { shortcut:"Tab", help:"code completion or indent" },
49 { shortcut:"Shift-Tab", help:"tooltip" },
49 { shortcut:"Shift-Tab", help:"tooltip" },
50 { shortcut: cmd_ctrl + "]", help:"indent" },
50 { shortcut: cmd_ctrl + "]", help:"indent" },
51 { shortcut: cmd_ctrl + "[", help:"dedent" },
51 { shortcut: cmd_ctrl + "[", help:"dedent" },
52 { shortcut: cmd_ctrl + "a", help:"select all" },
52 { shortcut: cmd_ctrl + "a", help:"select all" },
53 { shortcut: cmd_ctrl + "z", help:"undo" },
53 { shortcut: cmd_ctrl + "z", help:"undo" },
54 { shortcut: cmd_ctrl + "Shift-z", help:"redo" },
54 { shortcut: cmd_ctrl + "Shift-z", help:"redo" },
55 { shortcut: cmd_ctrl + "y", help:"redo" },
55 { shortcut: cmd_ctrl + "y", help:"redo" },
56 ].concat( platform_specific );
56 ].concat( platform_specific );
57
57
58
58
59
59
60
60
61
61
62
62
63 QuickHelp.prototype.show_keyboard_shortcuts = function () {
63 QuickHelp.prototype.show_keyboard_shortcuts = function () {
64 // toggles display of keyboard shortcut dialog
64 // toggles display of keyboard shortcut dialog
65 var that = this;
65 var that = this;
66 if ( this.force_rebuild ) {
66 if ( this.force_rebuild ) {
67 this.shortcut_dialog.remove();
67 this.shortcut_dialog.remove();
68 delete(this.shortcut_dialog);
68 delete(this.shortcut_dialog);
69 this.force_rebuild = false;
69 this.force_rebuild = false;
70 }
70 }
71 if ( this.shortcut_dialog ){
71 if ( this.shortcut_dialog ){
72 // if dialog is already shown, close it
72 // if dialog is already shown, close it
73 $(this.shortcut_dialog).modal("toggle");
73 $(this.shortcut_dialog).modal("toggle");
74 return;
74 return;
75 }
75 }
76 var command_shortcuts = this.keyboard_manager.command_shortcuts.help();
76 var command_shortcuts = this.keyboard_manager.command_shortcuts.help();
77 var edit_shortcuts = this.keyboard_manager.edit_shortcuts.help();
77 var edit_shortcuts = this.keyboard_manager.edit_shortcuts.help();
78 var help, shortcut;
78 var help, shortcut;
79 var i, half, n;
79 var i, half, n;
80 var element = $('<div/>');
80 var element = $('<div/>');
81
81
82 // The documentation
82 // The documentation
83 var doc = $('<div/>').addClass('alert alert-warning');
83 var doc = $('<div/>').addClass('alert alert-warning');
84 doc.append(
84 doc.append(
85 $('<button/>').addClass('close').attr('data-dismiss','alert').html('&times;')
85 $('<button/>').addClass('close').attr('data-dismiss','alert').html('&times;')
86 ).append(
86 ).append(
87 'The IPython Notebook has two different keyboard input modes. <b>Edit mode</b> '+
87 'The IPython Notebook has two different keyboard input modes. <b>Edit mode</b> '+
88 'allows you to type code/text into a cell and is indicated by a green cell '+
88 'allows you to type code/text into a cell and is indicated by a green cell '+
89 'border. <b>Command mode</b> binds the keyboard to notebook level actions '+
89 'border. <b>Command mode</b> binds the keyboard to notebook level actions '+
90 'and is indicated by a grey cell border.'
90 'and is indicated by a grey cell border.'
91 );
91 );
92 element.append(doc);
92 element.append(doc);
93
93
94 // Command mode
94 // Command mode
95 var cmd_div = this.build_command_help();
95 var cmd_div = this.build_command_help();
96 element.append(cmd_div);
96 element.append(cmd_div);
97
97
98 // Edit mode
98 // Edit mode
99 var edit_div = this.build_edit_help(cm_shortcuts);
99 var edit_div = this.build_edit_help(cm_shortcuts);
100 element.append(edit_div);
100 element.append(edit_div);
101
101
102 this.shortcut_dialog = dialog.modal({
102 this.shortcut_dialog = dialog.modal({
103 title : "Keyboard shortcuts",
103 title : "Keyboard shortcuts",
104 body : element,
104 body : element,
105 destroy : false,
105 destroy : false,
106 buttons : {
106 buttons : {
107 Close : {}
107 Close : {}
108 }
108 }
109 });
109 });
110 this.shortcut_dialog.addClass("modal_stretch");
110 this.shortcut_dialog.addClass("modal_stretch");
111
111
112 this.events.on('rebuild.QuickHelp', function() { that.force_rebuild = true;});
112 this.events.on('rebuild.QuickHelp', function() { that.force_rebuild = true;});
113 };
113 };
114
114
115 QuickHelp.prototype.build_command_help = function () {
115 QuickHelp.prototype.build_command_help = function () {
116 var command_shortcuts = this.keyboard_manager.command_shortcuts.help();
116 var command_shortcuts = this.keyboard_manager.command_shortcuts.help();
117 return build_div('<h4>Command Mode (press <code>Esc</code> to enable)</h4>', command_shortcuts);
117 return build_div('<h4>Command Mode (press <code>Esc</code> to enable)</h4>', command_shortcuts);
118 };
118 };
119
119
120 var special_case = { pageup: "PageUp", pagedown: "Page Down", 'minus': '-' };
120 var special_case = { pageup: "PageUp", pagedown: "Page Down", 'minus': '-' };
121 var prettify = function (s) {
121 var prettify = function (s) {
122 s = s.replace(/-$/, 'minus'); // catch shortcuts using '-' key
122 s = s.replace(/-$/, 'minus'); // catch shortcuts using '-' key
123 var keys = s.split('-');
123 var keys = s.split('-');
124 var k, i;
124 var k, i;
125 for (i=0; i < keys.length; i++) {
125 for (i=0; i < keys.length; i++) {
126 k = keys[i];
126 k = keys[i];
127 if ( k.length == 1 ) {
127 if ( k.length == 1 ) {
128 keys[i] = "<code><strong>" + k + "</strong></code>";
128 keys[i] = "<code><strong>" + k + "</strong></code>";
129 continue; // leave individual keys lower-cased
129 continue; // leave individual keys lower-cased
130 }
130 }
131 keys[i] = ( special_case[k] ? special_case[k] : k.charAt(0).toUpperCase() + k.slice(1) );
131 keys[i] = ( special_case[k] ? special_case[k] : k.charAt(0).toUpperCase() + k.slice(1) );
132 keys[i] = "<code><strong>" + keys[i] + "</strong></code>";
132 keys[i] = "<code><strong>" + keys[i] + "</strong></code>";
133 }
133 }
134 return keys.join('-');
134 return keys.join('-');
135
135
136
136
137 };
137 };
138
138
139 QuickHelp.prototype.build_edit_help = function (cm_shortcuts) {
139 QuickHelp.prototype.build_edit_help = function (cm_shortcuts) {
140 var edit_shortcuts = this.keyboard_manager.edit_shortcuts.help();
140 var edit_shortcuts = this.keyboard_manager.edit_shortcuts.help();
141 jQuery.merge(cm_shortcuts, edit_shortcuts);
141 jQuery.merge(cm_shortcuts, edit_shortcuts);
142 return build_div('<h4>Edit Mode (press <code>Enter</code> to enable)</h4>', cm_shortcuts);
142 return build_div('<h4>Edit Mode (press <code>Enter</code> to enable)</h4>', cm_shortcuts);
143 };
143 };
144
144
145 var build_one = function (s) {
145 var build_one = function (s) {
146 var help = s.help;
146 var help = s.help;
147 var shortcut = prettify(s.shortcut);
147 var shortcut = prettify(s.shortcut);
148 return $('<div>').addClass('quickhelp').
148 return $('<div>').addClass('quickhelp').
149 append($('<span/>').addClass('shortcut_key').append($(shortcut))).
149 append($('<span/>').addClass('shortcut_key').append($(shortcut))).
150 append($('<span/>').addClass('shortcut_descr').text(' : ' + help));
150 append($('<span/>').addClass('shortcut_descr').text(' : ' + help));
151
151
152 };
152 };
153
153
154 var build_div = function (title, shortcuts) {
154 var build_div = function (title, shortcuts) {
155 var i, half, n;
155 var i, half, n;
156 var div = $('<div/>').append($(title));
156 var div = $('<div/>').append($(title));
157 var sub_div = $('<div/>').addClass('hbox');
157 var sub_div = $('<div/>').addClass('hbox');
158 var col1 = $('<div/>').addClass('box-flex1');
158 var col1 = $('<div/>').addClass('box-flex1');
159 var col2 = $('<div/>').addClass('box-flex1');
159 var col2 = $('<div/>').addClass('box-flex1');
160 n = shortcuts.length;
160 n = shortcuts.length;
161 half = ~~(n/2); // Truncate :)
161 half = ~~(n/2); // Truncate :)
162 for (i=0; i<half; i++) { col1.append( build_one(shortcuts[i]) ); }
162 for (i=0; i<half; i++) { col1.append( build_one(shortcuts[i]) ); }
163 for (i=half; i<n; i++) { col2.append( build_one(shortcuts[i]) ); }
163 for (i=half; i<n; i++) { col2.append( build_one(shortcuts[i]) ); }
164 sub_div.append(col1).append(col2);
164 sub_div.append(col1).append(col2);
165 div.append(sub_div);
165 div.append(sub_div);
166 return div;
166 return div;
167 };
167 };
168
168
169 // Backwards compatability.
169 // Backwards compatability.
170 IPython.QuickHelp = QuickHelp;
170 IPython.QuickHelp = QuickHelp;
171
171
172 return {'QuickHelp': QuickHelp};
172 return {'QuickHelp': QuickHelp};
173 });
173 });
@@ -1,454 +1,453 b''
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 'notebook/js/cell',
7 'notebook/js/cell',
8 'base/js/security',
8 'base/js/security',
9 'notebook/js/mathjaxutils',
9 'notebook/js/mathjaxutils',
10 'notebook/js/celltoolbar',
10 'notebook/js/celltoolbar',
11 'components/marked/lib/marked',
11 'components/marked/lib/marked',
12 ], function(IPython, $, cell, security, mathjaxutils, celltoolbar, marked) {
12 ], function(IPython, $, cell, security, mathjaxutils, celltoolbar, marked) {
13 "use strict";
13 "use strict";
14 var Cell = cell.Cell;
14 var Cell = cell.Cell;
15
15
16 /**
16 /**
17 * Construct a new TextCell, codemirror mode is by default 'htmlmixed', and cell type is 'text'
17 * Construct a new TextCell, codemirror mode is by default 'htmlmixed', and cell type is 'text'
18 * cell start as not redered.
18 * cell start as not redered.
19 *
19 *
20 * @class TextCell
20 * @class TextCell
21 * @constructor TextCell
21 * @constructor TextCell
22 * @extend Cell
22 * @extend Cell
23 * @param {object|undefined} [options]
23 * @param {object|undefined} [options]
24 * @param [options.cm_config] {object} config to pass to CodeMirror, will extend/overwrite default config
24 * @param [options.cm_config] {object} config to pass to CodeMirror, will extend/overwrite default config
25 * @param [options.placeholder] {string} default string to use when souce in empty for rendering (only use in some TextCell subclass)
25 * @param [options.placeholder] {string} default string to use when souce in empty for rendering (only use in some TextCell subclass)
26 */
26 */
27 var TextCell = function (options, events, config, keyboard_manager, notebook) {
27 var TextCell = function (options) {
28 options = options || {};
28 // in all TextCell/Cell subclasses
29 // in all TextCell/Cell subclasses
29 // do not assign most of members here, just pass it down
30 // do not assign most of members here, just pass it down
30 // in the options dict potentially overwriting what you wish.
31 // in the options dict potentially overwriting what you wish.
31 // they will be assigned in the base class.
32 // they will be assigned in the base class.
32 this.notebook = notebook;
33 this.notebook = options.notebook;
33 this.events = events;
34 this.events = options.events;
34 this.config = config;
35 this.config = options.config;
35
36
36 // we cannot put this as a class key as it has handle to "this".
37 // we cannot put this as a class key as it has handle to "this".
37 var cm_overwrite_options = {
38 var cm_overwrite_options = {
38 onKeyEvent: $.proxy(this.handle_keyevent,this)
39 onKeyEvent: $.proxy(this.handle_keyevent,this)
39 };
40 };
40
41 var config = this.mergeopt(TextCell, this.config, {cm_config:cm_overwrite_options});
41 options = this.mergeopt(TextCell,options,{cm_config:cm_overwrite_options});
42 Cell.apply(this, [{
43 config: config,
44 keyboard_manager: options.keyboard_manager,
45 events: events}]);
42
46
43 this.cell_type = this.cell_type || 'text';
47 this.cell_type = this.cell_type || 'text';
44 mathjaxutils = mathjaxutils;
48 mathjaxutils = mathjaxutils;
45
46 Cell.apply(this, [options, keyboard_manager, events]);
47
48 this.rendered = false;
49 this.rendered = false;
49 };
50 };
50
51
51 TextCell.prototype = new Cell();
52 TextCell.prototype = new Cell();
52
53
53 TextCell.options_default = {
54 TextCell.options_default = {
54 cm_config : {
55 cm_config : {
55 extraKeys: {"Tab": "indentMore","Shift-Tab" : "indentLess"},
56 extraKeys: {"Tab": "indentMore","Shift-Tab" : "indentLess"},
56 mode: 'htmlmixed',
57 mode: 'htmlmixed',
57 lineWrapping : true,
58 lineWrapping : true,
58 }
59 }
59 };
60 };
60
61
61
62
62 /**
63 /**
63 * Create the DOM element of the TextCell
64 * Create the DOM element of the TextCell
64 * @method create_element
65 * @method create_element
65 * @private
66 * @private
66 */
67 */
67 TextCell.prototype.create_element = function () {
68 TextCell.prototype.create_element = function () {
68 Cell.prototype.create_element.apply(this, arguments);
69 Cell.prototype.create_element.apply(this, arguments);
69
70
70 var cell = $("<div>").addClass('cell text_cell border-box-sizing');
71 var cell = $("<div>").addClass('cell text_cell border-box-sizing');
71 cell.attr('tabindex','2');
72 cell.attr('tabindex','2');
72
73
73 var prompt = $('<div/>').addClass('prompt input_prompt');
74 var prompt = $('<div/>').addClass('prompt input_prompt');
74 cell.append(prompt);
75 cell.append(prompt);
75 var inner_cell = $('<div/>').addClass('inner_cell');
76 var inner_cell = $('<div/>').addClass('inner_cell');
76 this.celltoolbar = new celltoolbar.CellToolbar(this, this.events, this.notebook);
77 this.celltoolbar = new celltoolbar.CellToolbar(this, this.events, this.notebook);
77 inner_cell.append(this.celltoolbar.element);
78 inner_cell.append(this.celltoolbar.element);
78 var input_area = $('<div/>').addClass('input_area');
79 var input_area = $('<div/>').addClass('input_area');
79 this.code_mirror = new CodeMirror(input_area.get(0), this.cm_config);
80 this.code_mirror = new CodeMirror(input_area.get(0), this.cm_config);
80 // The tabindex=-1 makes this div focusable.
81 // The tabindex=-1 makes this div focusable.
81 var render_area = $('<div/>').addClass('text_cell_render border-box-sizing').
82 var render_area = $('<div/>').addClass('text_cell_render border-box-sizing').
82 addClass('rendered_html').attr('tabindex','-1');
83 addClass('rendered_html').attr('tabindex','-1');
83 inner_cell.append(input_area).append(render_area);
84 inner_cell.append(input_area).append(render_area);
84 cell.append(inner_cell);
85 cell.append(inner_cell);
85 this.element = cell;
86 this.element = cell;
86 };
87 };
87
88
88
89
89 /**
90 /**
90 * Bind the DOM evet to cell actions
91 * Bind the DOM evet to cell actions
91 * Need to be called after TextCell.create_element
92 * Need to be called after TextCell.create_element
92 * @private
93 * @private
93 * @method bind_event
94 * @method bind_event
94 */
95 */
95 TextCell.prototype.bind_events = function () {
96 TextCell.prototype.bind_events = function () {
96 Cell.prototype.bind_events.apply(this);
97 Cell.prototype.bind_events.apply(this);
97 var that = this;
98 var that = this;
98
99
99 this.element.dblclick(function () {
100 this.element.dblclick(function () {
100 if (that.selected === false) {
101 if (that.selected === false) {
101 this.events.trigger('select.Cell', {'cell':that});
102 this.events.trigger('select.Cell', {'cell':that});
102 }
103 }
103 var cont = that.unrender();
104 var cont = that.unrender();
104 if (cont) {
105 if (cont) {
105 that.focus_editor();
106 that.focus_editor();
106 }
107 }
107 });
108 });
108 };
109 };
109
110
110 // Cell level actions
111 // Cell level actions
111
112
112 TextCell.prototype.select = function () {
113 TextCell.prototype.select = function () {
113 var cont = Cell.prototype.select.apply(this);
114 var cont = Cell.prototype.select.apply(this);
114 if (cont) {
115 if (cont) {
115 if (this.mode === 'edit') {
116 if (this.mode === 'edit') {
116 this.code_mirror.refresh();
117 this.code_mirror.refresh();
117 }
118 }
118 }
119 }
119 return cont;
120 return cont;
120 };
121 };
121
122
122 TextCell.prototype.unrender = function () {
123 TextCell.prototype.unrender = function () {
123 if (this.read_only) return;
124 if (this.read_only) return;
124 var cont = Cell.prototype.unrender.apply(this);
125 var cont = Cell.prototype.unrender.apply(this);
125 if (cont) {
126 if (cont) {
126 var text_cell = this.element;
127 var text_cell = this.element;
127 var output = text_cell.find("div.text_cell_render");
128 var output = text_cell.find("div.text_cell_render");
128 output.hide();
129 output.hide();
129 text_cell.find('div.input_area').show();
130 text_cell.find('div.input_area').show();
130 if (this.get_text() === this.placeholder) {
131 if (this.get_text() === this.placeholder) {
131 this.set_text('');
132 this.set_text('');
132 }
133 }
133 this.refresh();
134 this.refresh();
134 }
135 }
135 if (this.celltoolbar.ui_controls_list.length) {
136 if (this.celltoolbar.ui_controls_list.length) {
136 this.celltoolbar.show();
137 this.celltoolbar.show();
137 }
138 }
138 return cont;
139 return cont;
139 };
140 };
140
141
141 TextCell.prototype.execute = function () {
142 TextCell.prototype.execute = function () {
142 this.render();
143 this.render();
143 };
144 };
144
145
145 /**
146 /**
146 * setter: {{#crossLink "TextCell/set_text"}}{{/crossLink}}
147 * setter: {{#crossLink "TextCell/set_text"}}{{/crossLink}}
147 * @method get_text
148 * @method get_text
148 * @retrun {string} CodeMirror current text value
149 * @retrun {string} CodeMirror current text value
149 */
150 */
150 TextCell.prototype.get_text = function() {
151 TextCell.prototype.get_text = function() {
151 return this.code_mirror.getValue();
152 return this.code_mirror.getValue();
152 };
153 };
153
154
154 /**
155 /**
155 * @param {string} text - Codemiror text value
156 * @param {string} text - Codemiror text value
156 * @see TextCell#get_text
157 * @see TextCell#get_text
157 * @method set_text
158 * @method set_text
158 * */
159 * */
159 TextCell.prototype.set_text = function(text) {
160 TextCell.prototype.set_text = function(text) {
160 this.code_mirror.setValue(text);
161 this.code_mirror.setValue(text);
161 this.code_mirror.refresh();
162 this.code_mirror.refresh();
162 };
163 };
163
164
164 /**
165 /**
165 * setter :{{#crossLink "TextCell/set_rendered"}}{{/crossLink}}
166 * setter :{{#crossLink "TextCell/set_rendered"}}{{/crossLink}}
166 * @method get_rendered
167 * @method get_rendered
167 * */
168 * */
168 TextCell.prototype.get_rendered = function() {
169 TextCell.prototype.get_rendered = function() {
169 return this.element.find('div.text_cell_render').html();
170 return this.element.find('div.text_cell_render').html();
170 };
171 };
171
172
172 /**
173 /**
173 * @method set_rendered
174 * @method set_rendered
174 */
175 */
175 TextCell.prototype.set_rendered = function(text) {
176 TextCell.prototype.set_rendered = function(text) {
176 this.element.find('div.text_cell_render').html(text);
177 this.element.find('div.text_cell_render').html(text);
177 this.celltoolbar.hide();
178 this.celltoolbar.hide();
178 };
179 };
179
180
180
181
181 /**
182 /**
182 * Create Text cell from JSON
183 * Create Text cell from JSON
183 * @param {json} data - JSON serialized text-cell
184 * @param {json} data - JSON serialized text-cell
184 * @method fromJSON
185 * @method fromJSON
185 */
186 */
186 TextCell.prototype.fromJSON = function (data) {
187 TextCell.prototype.fromJSON = function (data) {
187 Cell.prototype.fromJSON.apply(this, arguments);
188 Cell.prototype.fromJSON.apply(this, arguments);
188 if (data.cell_type === this.cell_type) {
189 if (data.cell_type === this.cell_type) {
189 if (data.source !== undefined) {
190 if (data.source !== undefined) {
190 this.set_text(data.source);
191 this.set_text(data.source);
191 // make this value the starting point, so that we can only undo
192 // make this value the starting point, so that we can only undo
192 // to this state, instead of a blank cell
193 // to this state, instead of a blank cell
193 this.code_mirror.clearHistory();
194 this.code_mirror.clearHistory();
194 // TODO: This HTML needs to be treated as potentially dangerous
195 // TODO: This HTML needs to be treated as potentially dangerous
195 // user input and should be handled before set_rendered.
196 // user input and should be handled before set_rendered.
196 this.set_rendered(data.rendered || '');
197 this.set_rendered(data.rendered || '');
197 this.rendered = false;
198 this.rendered = false;
198 this.render();
199 this.render();
199 }
200 }
200 }
201 }
201 };
202 };
202
203
203 /** Generate JSON from cell
204 /** Generate JSON from cell
204 * @return {object} cell data serialised to json
205 * @return {object} cell data serialised to json
205 */
206 */
206 TextCell.prototype.toJSON = function () {
207 TextCell.prototype.toJSON = function () {
207 var data = Cell.prototype.toJSON.apply(this);
208 var data = Cell.prototype.toJSON.apply(this);
208 data.source = this.get_text();
209 data.source = this.get_text();
209 if (data.source == this.placeholder) {
210 if (data.source == this.placeholder) {
210 data.source = "";
211 data.source = "";
211 }
212 }
212 return data;
213 return data;
213 };
214 };
214
215
215
216
216 /**
217 /**
217 * @class MarkdownCell
218 * @class MarkdownCell
218 * @constructor MarkdownCell
219 * @constructor MarkdownCell
219 * @extends IPython.HTMLCell
220 * @extends IPython.HTMLCell
220 */
221 */
221 var MarkdownCell = function (options, events, config, keyboard_manager) {
222 var MarkdownCell = function (options) {
222 options = this.mergeopt(MarkdownCell, options);
223 options = options || {};
224 var config = this.mergeopt(MarkdownCell, options.config);
225 TextCell.apply(this, [$.extend({}, options, {config: config})]);
223
226
224 this.cell_type = 'markdown';
227 this.cell_type = 'markdown';
225 TextCell.apply(this, [options, events, config, keyboard_manager]);
226 };
228 };
227
229
228 MarkdownCell.options_default = {
230 MarkdownCell.options_default = {
229 cm_config: {
231 cm_config: {
230 mode: 'ipythongfm'
232 mode: 'ipythongfm'
231 },
233 },
232 placeholder: "Type *Markdown* and LaTeX: $\\alpha^2$"
234 placeholder: "Type *Markdown* and LaTeX: $\\alpha^2$"
233 };
235 };
234
236
235 MarkdownCell.prototype = new TextCell();
237 MarkdownCell.prototype = new TextCell();
236
238
237 /**
239 /**
238 * @method render
240 * @method render
239 */
241 */
240 MarkdownCell.prototype.render = function () {
242 MarkdownCell.prototype.render = function () {
241 var cont = TextCell.prototype.render.apply(this);
243 var cont = TextCell.prototype.render.apply(this);
242 if (cont) {
244 if (cont) {
243 var text = this.get_text();
245 var text = this.get_text();
244 var math = null;
246 var math = null;
245 if (text === "") { text = this.placeholder; }
247 if (text === "") { text = this.placeholder; }
246 var text_and_math = mathjaxutils.remove_math(text);
248 var text_and_math = mathjaxutils.remove_math(text);
247 text = text_and_math[0];
249 text = text_and_math[0];
248 math = text_and_math[1];
250 math = text_and_math[1];
249 var html = marked.parser(marked.lexer(text));
251 var html = marked.parser(marked.lexer(text));
250 html = mathjaxutils.replace_math(html, math);
252 html = mathjaxutils.replace_math(html, math);
251 html = security.sanitize_html(html);
253 html = security.sanitize_html(html);
252 html = $($.parseHTML(html));
254 html = $($.parseHTML(html));
253 // links in markdown cells should open in new tabs
255 // links in markdown cells should open in new tabs
254 html.find("a[href]").not('[href^="#"]').attr("target", "_blank");
256 html.find("a[href]").not('[href^="#"]').attr("target", "_blank");
255 this.set_rendered(html);
257 this.set_rendered(html);
256 this.element.find('div.input_area').hide();
258 this.element.find('div.input_area').hide();
257 this.element.find("div.text_cell_render").show();
259 this.element.find("div.text_cell_render").show();
258 this.typeset();
260 this.typeset();
259 }
261 }
260 return cont;
262 return cont;
261 };
263 };
262
264
263
265
264 // RawCell
266 // RawCell
265
267
266 /**
268 /**
267 * @class RawCell
269 * @class RawCell
268 * @constructor RawCell
270 * @constructor RawCell
269 * @extends TextCell
271 * @extends TextCell
270 */
272 */
271 var RawCell = function (options, events, config, keyboard_manager) {
273 var RawCell = function (options) {
274 options = options || {};
275 var config = this.mergeopt(RawCell, options.config);
276 TextCell.apply(this, [$.extend({}, options, {config: config})]);
272
277
273 options = this.mergeopt(RawCell,options);
274 TextCell.apply(this, [options, events, config, keyboard_manager]);
275 this.cell_type = 'raw';
276 // RawCell should always hide its rendered div
278 // RawCell should always hide its rendered div
277 this.element.find('div.text_cell_render').hide();
279 this.element.find('div.text_cell_render').hide();
280 this.cell_type = 'raw';
278 };
281 };
279
282
280 RawCell.options_default = {
283 RawCell.options_default = {
281 placeholder : "Write raw LaTeX or other formats here, for use with nbconvert. " +
284 placeholder : "Write raw LaTeX or other formats here, for use with nbconvert. " +
282 "It will not be rendered in the notebook. " +
285 "It will not be rendered in the notebook. " +
283 "When passing through nbconvert, a Raw Cell's content is added to the output unmodified."
286 "When passing through nbconvert, a Raw Cell's content is added to the output unmodified."
284 };
287 };
285
288
286 RawCell.prototype = new TextCell();
289 RawCell.prototype = new TextCell();
287
290
288 /** @method bind_events **/
291 /** @method bind_events **/
289 RawCell.prototype.bind_events = function () {
292 RawCell.prototype.bind_events = function () {
290 TextCell.prototype.bind_events.apply(this);
293 TextCell.prototype.bind_events.apply(this);
291 var that = this;
294 var that = this;
292 this.element.focusout(function() {
295 this.element.focusout(function() {
293 that.auto_highlight();
296 that.auto_highlight();
294 that.render();
297 that.render();
295 });
298 });
296
299
297 this.code_mirror.on('focus', function() { that.unrender(); });
300 this.code_mirror.on('focus', function() { that.unrender(); });
298 };
301 };
299
302
300 /**
303 /**
301 * Trigger autodetection of highlight scheme for current cell
304 * Trigger autodetection of highlight scheme for current cell
302 * @method auto_highlight
305 * @method auto_highlight
303 */
306 */
304 RawCell.prototype.auto_highlight = function () {
307 RawCell.prototype.auto_highlight = function () {
305 this._auto_highlight(this.config.raw_cell_highlight);
308 this._auto_highlight(this.config.raw_cell_highlight);
306 };
309 };
307
310
308 /** @method render **/
311 /** @method render **/
309 RawCell.prototype.render = function () {
312 RawCell.prototype.render = function () {
310 var cont = TextCell.prototype.render.apply(this);
313 var cont = TextCell.prototype.render.apply(this);
311 if (cont){
314 if (cont){
312 var text = this.get_text();
315 var text = this.get_text();
313 if (text === "") { text = this.placeholder; }
316 if (text === "") { text = this.placeholder; }
314 this.set_text(text);
317 this.set_text(text);
315 this.element.removeClass('rendered');
318 this.element.removeClass('rendered');
316 }
319 }
317 return cont;
320 return cont;
318 };
321 };
319
322
320
323
321 /**
324 /**
322 * @class HeadingCell
325 * @class HeadingCell
323 * @extends TextCell
326 * @extends TextCell
324 */
327 */
325
328
326 /**
329 /**
327 * @constructor HeadingCell
330 * @constructor HeadingCell
328 * @extends TextCell
331 * @extends TextCell
329 */
332 */
330 var HeadingCell = function (options, events, config, keyboard_manager) {
333 var HeadingCell = function (options) {
331 options = this.mergeopt(HeadingCell, options);
334 options = options || {};
335 var config = this.mergeopt(HeadingCell, options.config);
336 TextCell.apply(this, [$.extend({}, options, {config: config})]);
332
337
333 this.level = 1;
338 this.level = 1;
334 this.cell_type = 'heading';
339 this.cell_type = 'heading';
335 TextCell.apply(this, [options, events, config, keyboard_manager]);
336
337 /**
338 * heading level of the cell, use getter and setter to access
339 * @property level
340 */
341 };
340 };
342
341
343 HeadingCell.options_default = {
342 HeadingCell.options_default = {
344 placeholder: "Type Heading Here"
343 placeholder: "Type Heading Here"
345 };
344 };
346
345
347 HeadingCell.prototype = new TextCell();
346 HeadingCell.prototype = new TextCell();
348
347
349 /** @method fromJSON */
348 /** @method fromJSON */
350 HeadingCell.prototype.fromJSON = function (data) {
349 HeadingCell.prototype.fromJSON = function (data) {
351 if (data.level !== undefined){
350 if (data.level !== undefined){
352 this.level = data.level;
351 this.level = data.level;
353 }
352 }
354 TextCell.prototype.fromJSON.apply(this, arguments);
353 TextCell.prototype.fromJSON.apply(this, arguments);
355 };
354 };
356
355
357
356
358 /** @method toJSON */
357 /** @method toJSON */
359 HeadingCell.prototype.toJSON = function () {
358 HeadingCell.prototype.toJSON = function () {
360 var data = TextCell.prototype.toJSON.apply(this);
359 var data = TextCell.prototype.toJSON.apply(this);
361 data.level = this.get_level();
360 data.level = this.get_level();
362 return data;
361 return data;
363 };
362 };
364
363
365 /**
364 /**
366 * can the cell be split into two cells
365 * can the cell be split into two cells
367 * @method is_splittable
366 * @method is_splittable
368 **/
367 **/
369 HeadingCell.prototype.is_splittable = function () {
368 HeadingCell.prototype.is_splittable = function () {
370 return false;
369 return false;
371 };
370 };
372
371
373
372
374 /**
373 /**
375 * can the cell be merged with other cells
374 * can the cell be merged with other cells
376 * @method is_mergeable
375 * @method is_mergeable
377 **/
376 **/
378 HeadingCell.prototype.is_mergeable = function () {
377 HeadingCell.prototype.is_mergeable = function () {
379 return false;
378 return false;
380 };
379 };
381
380
382 /**
381 /**
383 * Change heading level of cell, and re-render
382 * Change heading level of cell, and re-render
384 * @method set_level
383 * @method set_level
385 */
384 */
386 HeadingCell.prototype.set_level = function (level) {
385 HeadingCell.prototype.set_level = function (level) {
387 this.level = level;
386 this.level = level;
388 if (this.rendered) {
387 if (this.rendered) {
389 this.rendered = false;
388 this.rendered = false;
390 this.render();
389 this.render();
391 }
390 }
392 };
391 };
393
392
394 /** The depth of header cell, based on html (h1 to h6)
393 /** The depth of header cell, based on html (h1 to h6)
395 * @method get_level
394 * @method get_level
396 * @return {integer} level - for 1 to 6
395 * @return {integer} level - for 1 to 6
397 */
396 */
398 HeadingCell.prototype.get_level = function () {
397 HeadingCell.prototype.get_level = function () {
399 return this.level;
398 return this.level;
400 };
399 };
401
400
402
401
403 HeadingCell.prototype.get_rendered = function () {
402 HeadingCell.prototype.get_rendered = function () {
404 var r = this.element.find("div.text_cell_render");
403 var r = this.element.find("div.text_cell_render");
405 return r.children().first().html();
404 return r.children().first().html();
406 };
405 };
407
406
408 HeadingCell.prototype.render = function () {
407 HeadingCell.prototype.render = function () {
409 var cont = TextCell.prototype.render.apply(this);
408 var cont = TextCell.prototype.render.apply(this);
410 if (cont) {
409 if (cont) {
411 var text = this.get_text();
410 var text = this.get_text();
412 var math = null;
411 var math = null;
413 // Markdown headings must be a single line
412 // Markdown headings must be a single line
414 text = text.replace(/\n/g, ' ');
413 text = text.replace(/\n/g, ' ');
415 if (text === "") { text = this.placeholder; }
414 if (text === "") { text = this.placeholder; }
416 text = new Array(this.level + 1).join("#") + " " + text;
415 text = new Array(this.level + 1).join("#") + " " + text;
417 var text_and_math = mathjaxutils.remove_math(text);
416 var text_and_math = mathjaxutils.remove_math(text);
418 text = text_and_math[0];
417 text = text_and_math[0];
419 math = text_and_math[1];
418 math = text_and_math[1];
420 var html = marked.parser(marked.lexer(text));
419 var html = marked.parser(marked.lexer(text));
421 html = mathjaxutils.replace_math(html, math);
420 html = mathjaxutils.replace_math(html, math);
422 html = security.sanitize_html(html);
421 html = security.sanitize_html(html);
423 var h = $($.parseHTML(html));
422 var h = $($.parseHTML(html));
424 // add id and linkback anchor
423 // add id and linkback anchor
425 var hash = h.text().replace(/ /g, '-');
424 var hash = h.text().replace(/ /g, '-');
426 h.attr('id', hash);
425 h.attr('id', hash);
427 h.append(
426 h.append(
428 $('<a/>')
427 $('<a/>')
429 .addClass('anchor-link')
428 .addClass('anchor-link')
430 .attr('href', '#' + hash)
429 .attr('href', '#' + hash)
431 .text('ΒΆ')
430 .text('ΒΆ')
432 );
431 );
433 this.set_rendered(h);
432 this.set_rendered(h);
434 this.element.find('div.input_area').hide();
433 this.element.find('div.input_area').hide();
435 this.element.find("div.text_cell_render").show();
434 this.element.find("div.text_cell_render").show();
436 this.typeset();
435 this.typeset();
437 }
436 }
438 return cont;
437 return cont;
439 };
438 };
440
439
441 // Backwards compatability.
440 // Backwards compatability.
442 IPython.TextCell = TextCell;
441 IPython.TextCell = TextCell;
443 IPython.MarkdownCell = MarkdownCell;
442 IPython.MarkdownCell = MarkdownCell;
444 IPython.RawCell = RawCell;
443 IPython.RawCell = RawCell;
445 IPython.HeadingCell = HeadingCell;
444 IPython.HeadingCell = HeadingCell;
446
445
447 var Cells = {
446 var Cells = {
448 'TextCell': TextCell,
447 'TextCell': TextCell,
449 'MarkdownCell': MarkdownCell,
448 'MarkdownCell': MarkdownCell,
450 'RawCell': RawCell,
449 'RawCell': RawCell,
451 'HeadingCell': HeadingCell,
450 'HeadingCell': HeadingCell,
452 };
451 };
453 return Cells;
452 return Cells;
454 });
453 });
@@ -1,116 +1,116 b''
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 'services/kernels/js/kernel',
8 'services/kernels/js/kernel',
9 ], function(IPython, $, utils, kernel) {
9 ], function(IPython, $, utils, kernel) {
10 "use strict";
10 "use strict";
11
11
12 var Session = function(notebook, options){
12 var Session = function(options){
13 this.kernel = null;
13 this.kernel = null;
14 this.id = null;
14 this.id = null;
15 this.notebook = notebook;
15 this.notebook = options.notebook;
16 this.name = notebook.notebook_name;
16 this.name = notebook.notebook_name;
17 this.path = notebook.notebook_path;
17 this.path = notebook.notebook_path;
18 this.base_url = notebook.base_url;
18 this.base_url = notebook.base_url;
19 };
19 };
20
20
21 Session.prototype.start = function(callback) {
21 Session.prototype.start = function(callback) {
22 var that = this;
22 var that = this;
23 var model = {
23 var model = {
24 notebook : {
24 notebook : {
25 name : this.name,
25 name : this.name,
26 path : this.path
26 path : this.path
27 }
27 }
28 };
28 };
29 var settings = {
29 var settings = {
30 processData : false,
30 processData : false,
31 cache : false,
31 cache : false,
32 type : "POST",
32 type : "POST",
33 data: JSON.stringify(model),
33 data: JSON.stringify(model),
34 dataType : "json",
34 dataType : "json",
35 success : function (data, status, xhr) {
35 success : function (data, status, xhr) {
36 that._handle_start_success(data);
36 that._handle_start_success(data);
37 if (callback) {
37 if (callback) {
38 callback(data, status, xhr);
38 callback(data, status, xhr);
39 }
39 }
40 },
40 },
41 error : utils.log_ajax_error,
41 error : utils.log_ajax_error,
42 };
42 };
43 var url = utils.url_join_encode(this.base_url, 'api/sessions');
43 var url = utils.url_join_encode(this.base_url, 'api/sessions');
44 $.ajax(url, settings);
44 $.ajax(url, settings);
45 };
45 };
46
46
47 Session.prototype.rename_notebook = function (name, path) {
47 Session.prototype.rename_notebook = function (name, path) {
48 this.name = name;
48 this.name = name;
49 this.path = path;
49 this.path = path;
50 var model = {
50 var model = {
51 notebook : {
51 notebook : {
52 name : this.name,
52 name : this.name,
53 path : this.path
53 path : this.path
54 }
54 }
55 };
55 };
56 var settings = {
56 var settings = {
57 processData : false,
57 processData : false,
58 cache : false,
58 cache : false,
59 type : "PATCH",
59 type : "PATCH",
60 data: JSON.stringify(model),
60 data: JSON.stringify(model),
61 dataType : "json",
61 dataType : "json",
62 error : utils.log_ajax_error,
62 error : utils.log_ajax_error,
63 };
63 };
64 var url = utils.url_join_encode(this.base_url, 'api/sessions', this.id);
64 var url = utils.url_join_encode(this.base_url, 'api/sessions', this.id);
65 $.ajax(url, settings);
65 $.ajax(url, settings);
66 };
66 };
67
67
68 Session.prototype.delete = function() {
68 Session.prototype.delete = function() {
69 var settings = {
69 var settings = {
70 processData : false,
70 processData : false,
71 cache : false,
71 cache : false,
72 type : "DELETE",
72 type : "DELETE",
73 dataType : "json",
73 dataType : "json",
74 error : utils.log_ajax_error,
74 error : utils.log_ajax_error,
75 };
75 };
76 this.kernel.running = false;
76 this.kernel.running = false;
77 var url = utils.url_join_encode(this.base_url, 'api/sessions', this.id);
77 var url = utils.url_join_encode(this.base_url, 'api/sessions', this.id);
78 $.ajax(url, settings);
78 $.ajax(url, settings);
79 };
79 };
80
80
81 // Kernel related things
81 // Kernel related things
82 /**
82 /**
83 * Create the Kernel object associated with this Session.
83 * Create the Kernel object associated with this Session.
84 *
84 *
85 * @method _handle_start_success
85 * @method _handle_start_success
86 */
86 */
87 Session.prototype._handle_start_success = function (data, status, xhr) {
87 Session.prototype._handle_start_success = function (data, status, xhr) {
88 this.id = data.id;
88 this.id = data.id;
89 var kernel_service_url = utils.url_path_join(this.base_url, "api/kernels");
89 var kernel_service_url = utils.url_path_join(this.base_url, "api/kernels");
90 this.kernel = new kernel.Kernel(kernel_service_url, this.notebook);
90 this.kernel = new kernel.Kernel(kernel_service_url, this.notebook);
91 this.kernel._kernel_started(data.kernel);
91 this.kernel._kernel_started(data.kernel);
92 };
92 };
93
93
94 /**
94 /**
95 * Prompt the user to restart the IPython kernel.
95 * Prompt the user to restart the IPython kernel.
96 *
96 *
97 * @method restart_kernel
97 * @method restart_kernel
98 */
98 */
99 Session.prototype.restart_kernel = function () {
99 Session.prototype.restart_kernel = function () {
100 this.kernel.restart();
100 this.kernel.restart();
101 };
101 };
102
102
103 Session.prototype.interrupt_kernel = function() {
103 Session.prototype.interrupt_kernel = function() {
104 this.kernel.interrupt();
104 this.kernel.interrupt();
105 };
105 };
106
106
107
107
108 Session.prototype.kill_kernel = function() {
108 Session.prototype.kill_kernel = function() {
109 this.kernel.kill();
109 this.kernel.kill();
110 };
110 };
111
111
112 // For backwards compatability.
112 // For backwards compatability.
113 IPython.Session = Session;
113 IPython.Session = Session;
114
114
115 return {'Session': Session};
115 return {'Session': Session};
116 });
116 });
@@ -1,34 +1,36 b''
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 'tree/js/notebooklist',
7 'tree/js/notebooklist',
8 ], function(IPython, $, notebooklist) {
8 ], function(IPython, $, notebooklist) {
9 "use strict";
9 "use strict";
10
10
11 var KernelList = function (selector, options, session_list) {
11 var KernelList = function (selector, options) {
12 notebooklist.NotebookList.call(this, selector, options, 'running', session_list);
12 notebooklist.NotebookList.call(this, selector, $.extend({
13 element_name: 'running'},
14 options));
13 };
15 };
14
16
15 KernelList.prototype = Object.create(notebooklist.NotebookList.prototype);
17 KernelList.prototype = Object.create(notebooklist.NotebookList.prototype);
16
18
17 KernelList.prototype.sessions_loaded = function (d) {
19 KernelList.prototype.sessions_loaded = function (d) {
18 this.sessions = d;
20 this.sessions = d;
19 this.clear_list();
21 this.clear_list();
20 var item;
22 var item;
21 for (var path in d) {
23 for (var path in d) {
22 item = this.new_notebook_item(-1);
24 item = this.new_notebook_item(-1);
23 this.add_link('', path, item);
25 this.add_link('', path, item);
24 this.add_shutdown_button(item, this.sessions[path]);
26 this.add_shutdown_button(item, this.sessions[path]);
25 }
27 }
26
28
27 $('#running_list_header').toggle($.isEmptyObject(d));
29 $('#running_list_header').toggle($.isEmptyObject(d));
28 };
30 };
29
31
30 // Backwards compatability.
32 // Backwards compatability.
31 IPython.KernelList = KernelList;
33 IPython.KernelList = KernelList;
32
34
33 return {'KernelList': KernelList};
35 return {'KernelList': KernelList};
34 });
36 });
@@ -1,114 +1,119 b''
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 var ipython = ipython || {};
5 require([
4 require([
6 'base/js/namespace',
5 'base/js/namespace',
7 'jquery',
6 'jquery',
8 'base/js/events',
7 'base/js/events',
9 'base/js/page',
8 'base/js/page',
10 'base/js/utils',
9 'base/js/utils',
11 'tree/js/notebooklist',
10 'tree/js/notebooklist',
12 'tree/js/clusterlist',
11 'tree/js/clusterlist',
13 'tree/js/sessionlist',
12 'tree/js/sessionlist',
14 'tree/js/kernellist',
13 'tree/js/kernellist',
15 'auth/js/loginwidget',
14 'auth/js/loginwidget',
16 'components/jquery-ui/ui/minified/jquery-ui.min',
15 'components/jquery-ui/ui/minified/jquery-ui.min',
17 'components/bootstrap/js/bootstrap.min',
16 'components/bootstrap/js/bootstrap.min',
18 ], function(
17 ], function(
19 IPython,
18 IPython,
20 $,
19 $,
21 events,
20 events,
22 page,
21 page,
23 utils,
22 utils,
24 notebooklist,
23 notebooklist,
25 clusterlist,
24 clusterlist,
26 sesssionlist,
25 sesssionlist,
27 kernellist,
26 kernellist,
28 loginwidget){
27 loginwidget){
29
28
30 page = new page.Page();
29 page = new page.Page();
31
30
32 var opts = {
31 var common_options = {
33 base_url: utils.get_body_data("baseUrl"),
32 base_url: utils.get_body_data("baseUrl"),
34 notebook_path: utils.get_body_data("notebookPath"),
33 notebook_path: utils.get_body_data("notebookPath"),
35 };
34 };
36 events = $([new events.Events()]);
35 events = $([new events.Events()]);
37 session_list = new sesssionlist.SesssionList(opts, events);
36 session_list = new sesssionlist.SesssionList($.extend({
38 notebook_list = new notebooklist.NotebookList('#notebook_list', opts, undefined, session_list);
37 events: events},
39 cluster_list = new clusterlist.ClusterList('#cluster_list', opts);
38 common_options));
40 kernel_list = new kernellist.KernelList('#running_list', opts, session_list);
39 notebook_list = new notebooklist.NotebookList('#notebook_list', $.extend({
41 login_widget = new loginwidget.LoginWidget('#login_widget', opts);
40 session_list: session_list},
41 common_options));
42 cluster_list = new clusterlist.ClusterList('#cluster_list', common_options);
43 kernel_list = new kernellist.KernelList('#running_list', $.extend({
44 session_list: session_list},
45 common_options));
46 login_widget = new loginwidget.LoginWidget('#login_widget', common_options);
42
47
43 $('#new_notebook').button().click(function (e) {
48 $('#new_notebook').button().click(function (e) {
44 notebook_list.new_notebook();
49 notebook_list.new_notebook();
45 });
50 });
46
51
47 var interval_id=0;
52 var interval_id=0;
48 // auto refresh every xx secondes, no need to be fast,
53 // auto refresh every xx secondes, no need to be fast,
49 // update is done at least when page get focus
54 // update is done at least when page get focus
50 var time_refresh = 60; // in sec
55 var time_refresh = 60; // in sec
51
56
52 var enable_autorefresh = function(){
57 var enable_autorefresh = function(){
53 //refresh immediately , then start interval
58 //refresh immediately , then start interval
54 if($('.upload_button').length === 0)
59 if($('.upload_button').length === 0)
55 {
60 {
56 session_list.load_sessions();
61 session_list.load_sessions();
57 cluster_list.load_list();
62 cluster_list.load_list();
58 }
63 }
59 if (!interval_id){
64 if (!interval_id){
60 interval_id = setInterval(function(){
65 interval_id = setInterval(function(){
61 if($('.upload_button').length === 0)
66 if($('.upload_button').length === 0)
62 {
67 {
63 session_list.load_sessions();
68 session_list.load_sessions();
64 cluster_list.load_list();
69 cluster_list.load_list();
65 }
70 }
66 }, time_refresh*1000);
71 }, time_refresh*1000);
67 }
72 }
68 };
73 };
69
74
70 var disable_autorefresh = function(){
75 var disable_autorefresh = function(){
71 clearInterval(interval_id);
76 clearInterval(interval_id);
72 interval_id = 0;
77 interval_id = 0;
73 };
78 };
74
79
75 // stop autorefresh when page lose focus
80 // stop autorefresh when page lose focus
76 $(window).blur(function() {
81 $(window).blur(function() {
77 disable_autorefresh();
82 disable_autorefresh();
78 });
83 });
79
84
80 //re-enable when page get focus back
85 //re-enable when page get focus back
81 $(window).focus(function() {
86 $(window).focus(function() {
82 enable_autorefresh();
87 enable_autorefresh();
83 });
88 });
84
89
85 // finally start it, it will refresh immediately
90 // finally start it, it will refresh immediately
86 enable_autorefresh();
91 enable_autorefresh();
87
92
88 page.show();
93 page.show();
89 events.trigger('app_initialized.DashboardApp');
94 events.trigger('app_initialized.DashboardApp');
90
95
91 // bound the upload method to the on change of the file select list
96 // bound the upload method to the on change of the file select list
92 $("#alternate_upload").change(function (event){
97 $("#alternate_upload").change(function (event){
93 notebook_list.handleFilesUpload(event,'form');
98 notebook_list.handleFilesUpload(event,'form');
94 });
99 });
95
100
96 // set hash on tab click
101 // set hash on tab click
97 $("#tabs").find("a").click(function() {
102 $("#tabs").find("a").click(function() {
98 window.location.hash = $(this).attr("href");
103 window.location.hash = $(this).attr("href");
99 });
104 });
100
105
101 // load tab if url hash
106 // load tab if url hash
102 if (window.location.hash) {
107 if (window.location.hash) {
103 $("#tabs").find("a[href=" + window.location.hash + "]").click();
108 $("#tabs").find("a[href=" + window.location.hash + "]").click();
104 }
109 }
105
110
106 // For backwards compatability.
111 // For backwards compatability.
107 ipython.page = page;
112 IPython.page = page;
108 ipython.notebook_list = notebook_list;
113 IPython.notebook_list = notebook_list;
109 ipython.cluster_list = cluster_list;
114 IPython.cluster_list = cluster_list;
110 ipython.session_list = session_list;
115 IPython.session_list = session_list;
111 ipython.kernel_list = kernel_list;
116 IPython.kernel_list = kernel_list;
112 ipython.login_widget = login_widget;
117 IPython.login_widget = login_widget;
113 ipython.events = events;
118 IPython.events = events;
114 });
119 });
@@ -1,438 +1,438 b''
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 ], function(IPython, $, utils, dialog) {
10 "use strict";
10 "use strict";
11
11
12 var NotebookList = function (selector, options, element_name, session_list) {
12 var NotebookList = function (selector, options) {
13 var that = this;
13 var that = this;
14 this.session_list = session_list;
14 this.session_list = options.session_list;
15 // allow code re-use by just changing element_name in kernellist.js
15 // allow code re-use by just changing element_name in kernellist.js
16 this.element_name = element_name || 'notebook';
16 this.element_name = options.element_name || 'notebook';
17 this.selector = selector;
17 this.selector = selector;
18 if (this.selector !== undefined) {
18 if (this.selector !== undefined) {
19 this.element = $(selector);
19 this.element = $(selector);
20 this.style();
20 this.style();
21 this.bind_events();
21 this.bind_events();
22 }
22 }
23 this.notebooks_list = [];
23 this.notebooks_list = [];
24 this.sessions = {};
24 this.sessions = {};
25 this.base_url = options.base_url || utils.get_body_data("baseUrl");
25 this.base_url = options.base_url || utils.get_body_data("baseUrl");
26 this.notebook_path = options.notebook_path || utils.get_body_data("notebookPath");
26 this.notebook_path = options.notebook_path || utils.get_body_data("notebookPath");
27 if (this.session_list && this.session_list.events) {
27 if (this.session_list && this.session_list.events) {
28 this.session_list.events.on('sessions_loaded.Dashboard',
28 this.session_list.events.on('sessions_loaded.Dashboard',
29 function(e, d) { that.sessions_loaded(d); });
29 function(e, d) { that.sessions_loaded(d); });
30 }
30 }
31 };
31 };
32
32
33 NotebookList.prototype.style = function () {
33 NotebookList.prototype.style = function () {
34 var prefix = '#' + this.element_name;
34 var prefix = '#' + this.element_name;
35 $(prefix + '_toolbar').addClass('list_toolbar');
35 $(prefix + '_toolbar').addClass('list_toolbar');
36 $(prefix + '_list_info').addClass('toolbar_info');
36 $(prefix + '_list_info').addClass('toolbar_info');
37 $(prefix + '_buttons').addClass('toolbar_buttons');
37 $(prefix + '_buttons').addClass('toolbar_buttons');
38 $(prefix + '_list_header').addClass('list_header');
38 $(prefix + '_list_header').addClass('list_header');
39 this.element.addClass("list_container");
39 this.element.addClass("list_container");
40 };
40 };
41
41
42
42
43 NotebookList.prototype.bind_events = function () {
43 NotebookList.prototype.bind_events = function () {
44 var that = this;
44 var that = this;
45 $('#refresh_' + this.element_name + '_list').click(function () {
45 $('#refresh_' + this.element_name + '_list').click(function () {
46 that.load_sessions();
46 that.load_sessions();
47 });
47 });
48 this.element.bind('dragover', function () {
48 this.element.bind('dragover', function () {
49 return false;
49 return false;
50 });
50 });
51 this.element.bind('drop', function(event){
51 this.element.bind('drop', function(event){
52 that.handleFilesUpload(event,'drop');
52 that.handleFilesUpload(event,'drop');
53 return false;
53 return false;
54 });
54 });
55 };
55 };
56
56
57 NotebookList.prototype.handleFilesUpload = function(event, dropOrForm) {
57 NotebookList.prototype.handleFilesUpload = function(event, dropOrForm) {
58 var that = this;
58 var that = this;
59 var files;
59 var files;
60 if(dropOrForm =='drop'){
60 if(dropOrForm =='drop'){
61 files = event.originalEvent.dataTransfer.files;
61 files = event.originalEvent.dataTransfer.files;
62 } else
62 } else
63 {
63 {
64 files = event.originalEvent.target.files;
64 files = event.originalEvent.target.files;
65 }
65 }
66 for (var i = 0; i < files.length; i++) {
66 for (var i = 0; i < files.length; i++) {
67 var f = files[i];
67 var f = files[i];
68 var reader = new FileReader();
68 var reader = new FileReader();
69 reader.readAsText(f);
69 reader.readAsText(f);
70 var name_and_ext = utils.splitext(f.name);
70 var name_and_ext = utils.splitext(f.name);
71 var file_ext = name_and_ext[1];
71 var file_ext = name_and_ext[1];
72 if (file_ext === '.ipynb') {
72 if (file_ext === '.ipynb') {
73 var item = that.new_notebook_item(0);
73 var item = that.new_notebook_item(0);
74 item.addClass('new-file');
74 item.addClass('new-file');
75 that.add_name_input(f.name, item);
75 that.add_name_input(f.name, item);
76 // Store the notebook item in the reader so we can use it later
76 // Store the notebook item in the reader so we can use it later
77 // to know which item it belongs to.
77 // to know which item it belongs to.
78 $(reader).data('item', item);
78 $(reader).data('item', item);
79 reader.onload = function (event) {
79 reader.onload = function (event) {
80 var nbitem = $(event.target).data('item');
80 var nbitem = $(event.target).data('item');
81 that.add_notebook_data(event.target.result, nbitem);
81 that.add_notebook_data(event.target.result, nbitem);
82 that.add_upload_button(nbitem);
82 that.add_upload_button(nbitem);
83 };
83 };
84 } else {
84 } else {
85 var dialog_body = 'Uploaded notebooks must be .ipynb files';
85 var dialog_body = 'Uploaded notebooks must be .ipynb files';
86 dialog.modal({
86 dialog.modal({
87 title : 'Invalid file type',
87 title : 'Invalid file type',
88 body : dialog_body,
88 body : dialog_body,
89 buttons : {'OK' : {'class' : 'btn-primary'}}
89 buttons : {'OK' : {'class' : 'btn-primary'}}
90 });
90 });
91 }
91 }
92 }
92 }
93 // Replace the file input form wth a clone of itself. This is required to
93 // Replace the file input form wth a clone of itself. This is required to
94 // reset the form. Otherwise, if you upload a file, delete it and try to
94 // reset the form. Otherwise, if you upload a file, delete it and try to
95 // upload it again, the changed event won't fire.
95 // upload it again, the changed event won't fire.
96 var form = $('input.fileinput');
96 var form = $('input.fileinput');
97 form.replaceWith(form.clone(true));
97 form.replaceWith(form.clone(true));
98 return false;
98 return false;
99 };
99 };
100
100
101 NotebookList.prototype.clear_list = function (remove_uploads) {
101 NotebookList.prototype.clear_list = function (remove_uploads) {
102 // Clears the navigation tree.
102 // Clears the navigation tree.
103 //
103 //
104 // Parameters
104 // Parameters
105 // remove_uploads: bool=False
105 // remove_uploads: bool=False
106 // Should upload prompts also be removed from the tree.
106 // Should upload prompts also be removed from the tree.
107 if (remove_uploads) {
107 if (remove_uploads) {
108 this.element.children('.list_item').remove();
108 this.element.children('.list_item').remove();
109 } else {
109 } else {
110 this.element.children('.list_item:not(.new-file)').remove();
110 this.element.children('.list_item:not(.new-file)').remove();
111 }
111 }
112 };
112 };
113
113
114 NotebookList.prototype.load_sessions = function(){
114 NotebookList.prototype.load_sessions = function(){
115 this.session_list.load_sessions();
115 this.session_list.load_sessions();
116 };
116 };
117
117
118
118
119 NotebookList.prototype.sessions_loaded = function(data){
119 NotebookList.prototype.sessions_loaded = function(data){
120 this.sessions = data;
120 this.sessions = data;
121 this.load_list();
121 this.load_list();
122 };
122 };
123
123
124 NotebookList.prototype.load_list = function () {
124 NotebookList.prototype.load_list = function () {
125 var that = this;
125 var that = this;
126 var settings = {
126 var settings = {
127 processData : false,
127 processData : false,
128 cache : false,
128 cache : false,
129 type : "GET",
129 type : "GET",
130 dataType : "json",
130 dataType : "json",
131 success : $.proxy(this.list_loaded, this),
131 success : $.proxy(this.list_loaded, this),
132 error : $.proxy( function(xhr, status, error){
132 error : $.proxy( function(xhr, status, error){
133 utils.log_ajax_error(xhr, status, error);
133 utils.log_ajax_error(xhr, status, error);
134 that.list_loaded([], null, null, {msg:"Error connecting to server."});
134 that.list_loaded([], null, null, {msg:"Error connecting to server."});
135 },this)
135 },this)
136 };
136 };
137
137
138 var url = utils.url_join_encode(
138 var url = utils.url_join_encode(
139 this.base_url,
139 this.base_url,
140 'api',
140 'api',
141 'notebooks',
141 'notebooks',
142 this.notebook_path
142 this.notebook_path
143 );
143 );
144 $.ajax(url, settings);
144 $.ajax(url, settings);
145 };
145 };
146
146
147
147
148 NotebookList.prototype.list_loaded = function (data, status, xhr, param) {
148 NotebookList.prototype.list_loaded = function (data, status, xhr, param) {
149 var message = 'Notebook list empty.';
149 var message = 'Notebook list empty.';
150 if (param !== undefined && param.msg) {
150 if (param !== undefined && param.msg) {
151 message = param.msg;
151 message = param.msg;
152 }
152 }
153 var item = null;
153 var item = null;
154 var len = data.length;
154 var len = data.length;
155 this.clear_list();
155 this.clear_list();
156 if (len === 0) {
156 if (len === 0) {
157 item = this.new_notebook_item(0);
157 item = this.new_notebook_item(0);
158 var span12 = item.children().first();
158 var span12 = item.children().first();
159 span12.empty();
159 span12.empty();
160 span12.append($('<div style="margin:auto;text-align:center;color:grey"/>').text(message));
160 span12.append($('<div style="margin:auto;text-align:center;color:grey"/>').text(message));
161 }
161 }
162 var path = this.notebook_path;
162 var path = this.notebook_path;
163 var offset = 0;
163 var offset = 0;
164 if (path !== '') {
164 if (path !== '') {
165 item = this.new_notebook_item(0);
165 item = this.new_notebook_item(0);
166 this.add_dir(path, '..', item);
166 this.add_dir(path, '..', item);
167 offset = 1;
167 offset = 1;
168 }
168 }
169 for (var i=0; i<len; i++) {
169 for (var i=0; i<len; i++) {
170 if (data[i].type === 'directory') {
170 if (data[i].type === 'directory') {
171 var name = data[i].name;
171 var name = data[i].name;
172 item = this.new_notebook_item(i+offset);
172 item = this.new_notebook_item(i+offset);
173 this.add_dir(path, name, item);
173 this.add_dir(path, name, item);
174 } else {
174 } else {
175 var name = data[i].name;
175 var name = data[i].name;
176 item = this.new_notebook_item(i+offset);
176 item = this.new_notebook_item(i+offset);
177 this.add_link(path, name, item);
177 this.add_link(path, name, item);
178 name = utils.url_path_join(path, name);
178 name = utils.url_path_join(path, name);
179 if(this.sessions[name] === undefined){
179 if(this.sessions[name] === undefined){
180 this.add_delete_button(item);
180 this.add_delete_button(item);
181 } else {
181 } else {
182 this.add_shutdown_button(item,this.sessions[name]);
182 this.add_shutdown_button(item,this.sessions[name]);
183 }
183 }
184 }
184 }
185 }
185 }
186 };
186 };
187
187
188
188
189 NotebookList.prototype.new_notebook_item = function (index) {
189 NotebookList.prototype.new_notebook_item = function (index) {
190 var item = $('<div/>').addClass("list_item").addClass("row");
190 var item = $('<div/>').addClass("list_item").addClass("row");
191 // item.addClass('list_item ui-widget ui-widget-content ui-helper-clearfix');
191 // item.addClass('list_item ui-widget ui-widget-content ui-helper-clearfix');
192 // item.css('border-top-style','none');
192 // item.css('border-top-style','none');
193 item.append($("<div/>").addClass("col-md-12").append(
193 item.append($("<div/>").addClass("col-md-12").append(
194 $('<i/>').addClass('item_icon')
194 $('<i/>').addClass('item_icon')
195 ).append(
195 ).append(
196 $("<a/>").addClass("item_link").append(
196 $("<a/>").addClass("item_link").append(
197 $("<span/>").addClass("item_name")
197 $("<span/>").addClass("item_name")
198 )
198 )
199 ).append(
199 ).append(
200 $('<div/>').addClass("item_buttons btn-group pull-right")
200 $('<div/>').addClass("item_buttons btn-group pull-right")
201 ));
201 ));
202
202
203 if (index === -1) {
203 if (index === -1) {
204 this.element.append(item);
204 this.element.append(item);
205 } else {
205 } else {
206 this.element.children().eq(index).after(item);
206 this.element.children().eq(index).after(item);
207 }
207 }
208 return item;
208 return item;
209 };
209 };
210
210
211
211
212 NotebookList.prototype.add_dir = function (path, name, item) {
212 NotebookList.prototype.add_dir = function (path, name, item) {
213 item.data('name', name);
213 item.data('name', name);
214 item.data('path', path);
214 item.data('path', path);
215 item.find(".item_name").text(name);
215 item.find(".item_name").text(name);
216 item.find(".item_icon").addClass('folder_icon').addClass('icon-fixed-width');
216 item.find(".item_icon").addClass('folder_icon').addClass('icon-fixed-width');
217 item.find("a.item_link")
217 item.find("a.item_link")
218 .attr('href',
218 .attr('href',
219 utils.url_join_encode(
219 utils.url_join_encode(
220 this.base_url,
220 this.base_url,
221 "tree",
221 "tree",
222 path,
222 path,
223 name
223 name
224 )
224 )
225 );
225 );
226 };
226 };
227
227
228
228
229 NotebookList.prototype.add_link = function (path, nbname, item) {
229 NotebookList.prototype.add_link = function (path, nbname, item) {
230 item.data('nbname', nbname);
230 item.data('nbname', nbname);
231 item.data('path', path);
231 item.data('path', path);
232 item.find(".item_name").text(nbname);
232 item.find(".item_name").text(nbname);
233 item.find(".item_icon").addClass('notebook_icon').addClass('icon-fixed-width');
233 item.find(".item_icon").addClass('notebook_icon').addClass('icon-fixed-width');
234 item.find("a.item_link")
234 item.find("a.item_link")
235 .attr('href',
235 .attr('href',
236 utils.url_join_encode(
236 utils.url_join_encode(
237 this.base_url,
237 this.base_url,
238 "notebooks",
238 "notebooks",
239 path,
239 path,
240 nbname
240 nbname
241 )
241 )
242 ).attr('target','_blank');
242 ).attr('target','_blank');
243 };
243 };
244
244
245
245
246 NotebookList.prototype.add_name_input = function (nbname, item) {
246 NotebookList.prototype.add_name_input = function (nbname, item) {
247 item.data('nbname', nbname);
247 item.data('nbname', nbname);
248 item.find(".item_icon").addClass('notebook_icon').addClass('icon-fixed-width');
248 item.find(".item_icon").addClass('notebook_icon').addClass('icon-fixed-width');
249 item.find(".item_name").empty().append(
249 item.find(".item_name").empty().append(
250 $('<input/>')
250 $('<input/>')
251 .addClass("nbname_input")
251 .addClass("nbname_input")
252 .attr('value', utils.splitext(nbname)[0])
252 .attr('value', utils.splitext(nbname)[0])
253 .attr('size', '30')
253 .attr('size', '30')
254 .attr('type', 'text')
254 .attr('type', 'text')
255 );
255 );
256 };
256 };
257
257
258
258
259 NotebookList.prototype.add_notebook_data = function (data, item) {
259 NotebookList.prototype.add_notebook_data = function (data, item) {
260 item.data('nbdata', data);
260 item.data('nbdata', data);
261 };
261 };
262
262
263
263
264 NotebookList.prototype.add_shutdown_button = function (item, session) {
264 NotebookList.prototype.add_shutdown_button = function (item, session) {
265 var that = this;
265 var that = this;
266 var shutdown_button = $("<button/>").text("Shutdown").addClass("btn btn-xs btn-danger").
266 var shutdown_button = $("<button/>").text("Shutdown").addClass("btn btn-xs btn-danger").
267 click(function (e) {
267 click(function (e) {
268 var settings = {
268 var settings = {
269 processData : false,
269 processData : false,
270 cache : false,
270 cache : false,
271 type : "DELETE",
271 type : "DELETE",
272 dataType : "json",
272 dataType : "json",
273 success : function () {
273 success : function () {
274 that.load_sessions();
274 that.load_sessions();
275 },
275 },
276 error : utils.log_ajax_error,
276 error : utils.log_ajax_error,
277 };
277 };
278 var url = utils.url_join_encode(
278 var url = utils.url_join_encode(
279 that.base_url,
279 that.base_url,
280 'api/sessions',
280 'api/sessions',
281 session
281 session
282 );
282 );
283 $.ajax(url, settings);
283 $.ajax(url, settings);
284 return false;
284 return false;
285 });
285 });
286 // var new_buttons = item.find('a'); // shutdown_button;
286 // var new_buttons = item.find('a'); // shutdown_button;
287 item.find(".item_buttons").text("").append(shutdown_button);
287 item.find(".item_buttons").text("").append(shutdown_button);
288 };
288 };
289
289
290 NotebookList.prototype.add_delete_button = function (item) {
290 NotebookList.prototype.add_delete_button = function (item) {
291 var new_buttons = $('<span/>').addClass("btn-group pull-right");
291 var new_buttons = $('<span/>').addClass("btn-group pull-right");
292 var notebooklist = this;
292 var notebooklist = this;
293 var delete_button = $("<button/>").text("Delete").addClass("btn btn-default btn-xs").
293 var delete_button = $("<button/>").text("Delete").addClass("btn btn-default btn-xs").
294 click(function (e) {
294 click(function (e) {
295 // $(this) is the button that was clicked.
295 // $(this) is the button that was clicked.
296 var that = $(this);
296 var that = $(this);
297 // We use the nbname and notebook_id from the parent notebook_item element's
297 // We use the nbname and notebook_id from the parent notebook_item element's
298 // data because the outer scopes values change as we iterate through the loop.
298 // data because the outer scopes values change as we iterate through the loop.
299 var parent_item = that.parents('div.list_item');
299 var parent_item = that.parents('div.list_item');
300 var nbname = parent_item.data('nbname');
300 var nbname = parent_item.data('nbname');
301 var message = 'Are you sure you want to permanently delete the notebook: ' + nbname + '?';
301 var message = 'Are you sure you want to permanently delete the notebook: ' + nbname + '?';
302 dialog.modal({
302 dialog.modal({
303 title : "Delete notebook",
303 title : "Delete notebook",
304 body : message,
304 body : message,
305 buttons : {
305 buttons : {
306 Delete : {
306 Delete : {
307 class: "btn-danger",
307 class: "btn-danger",
308 click: function() {
308 click: function() {
309 var settings = {
309 var settings = {
310 processData : false,
310 processData : false,
311 cache : false,
311 cache : false,
312 type : "DELETE",
312 type : "DELETE",
313 dataType : "json",
313 dataType : "json",
314 success : function (data, status, xhr) {
314 success : function (data, status, xhr) {
315 parent_item.remove();
315 parent_item.remove();
316 },
316 },
317 error : utils.log_ajax_error,
317 error : utils.log_ajax_error,
318 };
318 };
319 var url = utils.url_join_encode(
319 var url = utils.url_join_encode(
320 notebooklist.base_url,
320 notebooklist.base_url,
321 'api/notebooks',
321 'api/notebooks',
322 notebooklist.notebook_path,
322 notebooklist.notebook_path,
323 nbname
323 nbname
324 );
324 );
325 $.ajax(url, settings);
325 $.ajax(url, settings);
326 }
326 }
327 },
327 },
328 Cancel : {}
328 Cancel : {}
329 }
329 }
330 });
330 });
331 return false;
331 return false;
332 });
332 });
333 item.find(".item_buttons").text("").append(delete_button);
333 item.find(".item_buttons").text("").append(delete_button);
334 };
334 };
335
335
336
336
337 NotebookList.prototype.add_upload_button = function (item) {
337 NotebookList.prototype.add_upload_button = function (item) {
338 var that = this;
338 var that = this;
339 var upload_button = $('<button/>').text("Upload")
339 var upload_button = $('<button/>').text("Upload")
340 .addClass('btn btn-primary btn-xs upload_button')
340 .addClass('btn btn-primary btn-xs upload_button')
341 .click(function (e) {
341 .click(function (e) {
342 var nbname = item.find('.item_name > input').val();
342 var nbname = item.find('.item_name > input').val();
343 if (nbname.slice(nbname.length-6, nbname.length) != ".ipynb") {
343 if (nbname.slice(nbname.length-6, nbname.length) != ".ipynb") {
344 nbname = nbname + ".ipynb";
344 nbname = nbname + ".ipynb";
345 }
345 }
346 var path = that.notebook_path;
346 var path = that.notebook_path;
347 var nbdata = item.data('nbdata');
347 var nbdata = item.data('nbdata');
348 var content_type = 'application/json';
348 var content_type = 'application/json';
349 var model = {
349 var model = {
350 content : JSON.parse(nbdata),
350 content : JSON.parse(nbdata),
351 };
351 };
352 var settings = {
352 var settings = {
353 processData : false,
353 processData : false,
354 cache : false,
354 cache : false,
355 type : 'PUT',
355 type : 'PUT',
356 dataType : 'json',
356 dataType : 'json',
357 data : JSON.stringify(model),
357 data : JSON.stringify(model),
358 headers : {'Content-Type': content_type},
358 headers : {'Content-Type': content_type},
359 success : function (data, status, xhr) {
359 success : function (data, status, xhr) {
360 that.add_link(path, nbname, item);
360 that.add_link(path, nbname, item);
361 that.add_delete_button(item);
361 that.add_delete_button(item);
362 },
362 },
363 error : utils.log_ajax_error,
363 error : utils.log_ajax_error,
364 };
364 };
365
365
366 var url = utils.url_join_encode(
366 var url = utils.url_join_encode(
367 that.base_url,
367 that.base_url,
368 'api/notebooks',
368 'api/notebooks',
369 that.notebook_path,
369 that.notebook_path,
370 nbname
370 nbname
371 );
371 );
372 $.ajax(url, settings);
372 $.ajax(url, settings);
373 return false;
373 return false;
374 });
374 });
375 var cancel_button = $('<button/>').text("Cancel")
375 var cancel_button = $('<button/>').text("Cancel")
376 .addClass("btn btn-default btn-xs")
376 .addClass("btn btn-default btn-xs")
377 .click(function (e) {
377 .click(function (e) {
378 console.log('cancel click');
378 console.log('cancel click');
379 item.remove();
379 item.remove();
380 return false;
380 return false;
381 });
381 });
382 item.find(".item_buttons").empty()
382 item.find(".item_buttons").empty()
383 .append(upload_button)
383 .append(upload_button)
384 .append(cancel_button);
384 .append(cancel_button);
385 };
385 };
386
386
387
387
388 NotebookList.prototype.new_notebook = function(){
388 NotebookList.prototype.new_notebook = function(){
389 var path = this.notebook_path;
389 var path = this.notebook_path;
390 var base_url = this.base_url;
390 var base_url = this.base_url;
391 var settings = {
391 var settings = {
392 processData : false,
392 processData : false,
393 cache : false,
393 cache : false,
394 type : "POST",
394 type : "POST",
395 dataType : "json",
395 dataType : "json",
396 async : false,
396 async : false,
397 success : function (data, status, xhr) {
397 success : function (data, status, xhr) {
398 var notebook_name = data.name;
398 var notebook_name = data.name;
399 window.open(
399 window.open(
400 utils.url_join_encode(
400 utils.url_join_encode(
401 base_url,
401 base_url,
402 'notebooks',
402 'notebooks',
403 path,
403 path,
404 notebook_name),
404 notebook_name),
405 '_blank'
405 '_blank'
406 );
406 );
407 },
407 },
408 error : $.proxy(this.new_notebook_failed, this),
408 error : $.proxy(this.new_notebook_failed, this),
409 };
409 };
410 var url = utils.url_join_encode(
410 var url = utils.url_join_encode(
411 base_url,
411 base_url,
412 'api/notebooks',
412 'api/notebooks',
413 path
413 path
414 );
414 );
415 $.ajax(url, settings);
415 $.ajax(url, settings);
416 };
416 };
417
417
418
418
419 NotebookList.prototype.new_notebook_failed = function (xhr, status, error) {
419 NotebookList.prototype.new_notebook_failed = function (xhr, status, error) {
420 utils.log_ajax_error(xhr, status, error);
420 utils.log_ajax_error(xhr, status, error);
421 var msg;
421 var msg;
422 if (xhr.responseJSON && xhr.responseJSON.message) {
422 if (xhr.responseJSON && xhr.responseJSON.message) {
423 msg = xhr.responseJSON.message;
423 msg = xhr.responseJSON.message;
424 } else {
424 } else {
425 msg = xhr.statusText;
425 msg = xhr.statusText;
426 }
426 }
427 dialog.modal({
427 dialog.modal({
428 title : 'Creating Notebook Failed',
428 title : 'Creating Notebook Failed',
429 body : "The error was: " + msg,
429 body : "The error was: " + msg,
430 buttons : {'OK' : {'class' : 'btn-primary'}}
430 buttons : {'OK' : {'class' : 'btn-primary'}}
431 });
431 });
432 };
432 };
433
433
434 // Backwards compatability.
434 // Backwards compatability.
435 IPython.NotebookList = NotebookList;
435 IPython.NotebookList = NotebookList;
436
436
437 return {'NotebookList': NotebookList};
437 return {'NotebookList': NotebookList};
438 });
438 });
@@ -1,49 +1,49 b''
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 ], function(IPython, $, utils) {
8 ], function(IPython, $, utils) {
9 "use strict";
9 "use strict";
10
10
11 var SesssionList = function (options, events) {
11 var SesssionList = function (options, events) {
12 this.events = events;
12 this.events = options.events;
13 this.sessions = {};
13 this.sessions = {};
14 this.base_url = options.base_url || utils.get_body_data("baseUrl");
14 this.base_url = options.base_url || utils.get_body_data("baseUrl");
15 };
15 };
16
16
17 SesssionList.prototype.load_sessions = function(){
17 SesssionList.prototype.load_sessions = function(){
18 var that = this;
18 var that = this;
19 var settings = {
19 var settings = {
20 processData : false,
20 processData : false,
21 cache : false,
21 cache : false,
22 type : "GET",
22 type : "GET",
23 dataType : "json",
23 dataType : "json",
24 success : $.proxy(that.sessions_loaded, this),
24 success : $.proxy(that.sessions_loaded, this),
25 error : utils.log_ajax_error,
25 error : utils.log_ajax_error,
26 };
26 };
27 var url = utils.url_join_encode(this.base_url, 'api/sessions');
27 var url = utils.url_join_encode(this.base_url, 'api/sessions');
28 $.ajax(url, settings);
28 $.ajax(url, settings);
29 };
29 };
30
30
31 SesssionList.prototype.sessions_loaded = function(data){
31 SesssionList.prototype.sessions_loaded = function(data){
32 this.sessions = {};
32 this.sessions = {};
33 var len = data.length;
33 var len = data.length;
34 var nb_path;
34 var nb_path;
35 for (var i=0; i<len; i++) {
35 for (var i=0; i<len; i++) {
36 nb_path = utils.url_path_join(
36 nb_path = utils.url_path_join(
37 data[i].notebook.path,
37 data[i].notebook.path,
38 data[i].notebook.name
38 data[i].notebook.name
39 );
39 );
40 this.sessions[nb_path] = data[i].id;
40 this.sessions[nb_path] = data[i].id;
41 }
41 }
42 this.events.trigger('sessions_loaded.Dashboard', this.sessions);
42 this.events.trigger('sessions_loaded.Dashboard', this.sessions);
43 };
43 };
44
44
45 // Backwards compatability.
45 // Backwards compatability.
46 IPython.SesssionList = SesssionList;
46 IPython.SesssionList = SesssionList;
47
47
48 return {'SesssionList': SesssionList};
48 return {'SesssionList': SesssionList};
49 });
49 });
General Comments 0
You need to be logged in to leave comments. Login now