##// END OF EJS Templates
Added some nice comments,...
jon -
Show More
@@ -1,564 +1,563 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 /**
31 * The Base `Cell` class from which to inherit
32 * @class Cell
33 **/
34
35 /*
36 * @constructor
37 *
38 * @param {object|undefined} [options]
39 * @param [options.cm_config] {object} config to pass to CodeMirror, will extend default parameters
40 */
41 var Cell = function (options) {
30 var Cell = function (options) {
31 // Constructor
32 //
33 // The Base `Cell` class from which to inherit.
34 //
35 // Parameters:
36 // options: dictionary
37 // Dictionary of keyword arguments.
38 // events: $(Events) instance
39 // config: dictionary
40 // keyboard_manager: KeyboardManager instance
42 options = options || {};
41 options = options || {};
43 this.keyboard_manager = options.keyboard_manager;
42 this.keyboard_manager = options.keyboard_manager;
44 this.events = options.events;
43 this.events = options.events;
45 var config = this.mergeopt(Cell, options.config);
44 var config = this.mergeopt(Cell, options.config);
46 // superclass default overwrite our default
45 // superclass default overwrite our default
47
46
48 this.placeholder = config.placeholder || '';
47 this.placeholder = config.placeholder || '';
49 this.read_only = config.cm_config.readOnly;
48 this.read_only = config.cm_config.readOnly;
50 this.selected = false;
49 this.selected = false;
51 this.rendered = false;
50 this.rendered = false;
52 this.mode = 'command';
51 this.mode = 'command';
53 this.metadata = {};
52 this.metadata = {};
54 // load this from metadata later ?
53 // load this from metadata later ?
55 this.user_highlight = 'auto';
54 this.user_highlight = 'auto';
56 this.cm_config = config.cm_config;
55 this.cm_config = config.cm_config;
57 this.cell_id = utils.uuid();
56 this.cell_id = utils.uuid();
58 this._options = config;
57 this._options = config;
59
58
60 // For JS VM engines optimization, attributes should be all set (even
59 // For JS VM engines optimization, attributes should be all set (even
61 // to null) in the constructor, and if possible, if different subclass
60 // to null) in the constructor, and if possible, if different subclass
62 // have new attributes with same name, they should be created in the
61 // have new attributes with same name, they should be created in the
63 // same order. Easiest is to create and set to null in parent class.
62 // same order. Easiest is to create and set to null in parent class.
64
63
65 this.element = null;
64 this.element = null;
66 this.cell_type = this.cell_type || null;
65 this.cell_type = this.cell_type || null;
67 this.code_mirror = null;
66 this.code_mirror = null;
68
67
69 this.create_element();
68 this.create_element();
70 if (this.element !== null) {
69 if (this.element !== null) {
71 this.element.data("cell", this);
70 this.element.data("cell", this);
72 this.bind_events();
71 this.bind_events();
73 this.init_classes();
72 this.init_classes();
74 }
73 }
75 };
74 };
76
75
77 Cell.options_default = {
76 Cell.options_default = {
78 cm_config : {
77 cm_config : {
79 indentUnit : 4,
78 indentUnit : 4,
80 readOnly: false,
79 readOnly: false,
81 theme: "default",
80 theme: "default",
82 extraKeys: {
81 extraKeys: {
83 "Cmd-Right":"goLineRight",
82 "Cmd-Right":"goLineRight",
84 "End":"goLineRight",
83 "End":"goLineRight",
85 "Cmd-Left":"goLineLeft"
84 "Cmd-Left":"goLineLeft"
86 }
85 }
87 }
86 }
88 };
87 };
89
88
90 // FIXME: Workaround CM Bug #332 (Safari segfault on drag)
89 // FIXME: Workaround CM Bug #332 (Safari segfault on drag)
91 // by disabling drag/drop altogether on Safari
90 // by disabling drag/drop altogether on Safari
92 // https://github.com/marijnh/CodeMirror/issues/332
91 // https://github.com/marijnh/CodeMirror/issues/332
93 if (utils.browser[0] == "Safari") {
92 if (utils.browser[0] == "Safari") {
94 Cell.options_default.cm_config.dragDrop = false;
93 Cell.options_default.cm_config.dragDrop = false;
95 }
94 }
96
95
97 Cell.prototype.mergeopt = function(_class, options, overwrite){
96 Cell.prototype.mergeopt = function(_class, options, overwrite){
98 options = options || {};
97 options = options || {};
99 overwrite = overwrite || {};
98 overwrite = overwrite || {};
100 return $.extend(true, {}, _class.options_default, options, overwrite);
99 return $.extend(true, {}, _class.options_default, options, overwrite);
101 };
100 };
102
101
103 /**
102 /**
104 * Empty. Subclasses must implement create_element.
103 * Empty. Subclasses must implement create_element.
105 * This should contain all the code to create the DOM element in notebook
104 * This should contain all the code to create the DOM element in notebook
106 * and will be called by Base Class constructor.
105 * and will be called by Base Class constructor.
107 * @method create_element
106 * @method create_element
108 */
107 */
109 Cell.prototype.create_element = function () {
108 Cell.prototype.create_element = function () {
110 };
109 };
111
110
112 Cell.prototype.init_classes = function () {
111 Cell.prototype.init_classes = function () {
113 // Call after this.element exists to initialize the css classes
112 // Call after this.element exists to initialize the css classes
114 // related to selected, rendered and mode.
113 // related to selected, rendered and mode.
115 if (this.selected) {
114 if (this.selected) {
116 this.element.addClass('selected');
115 this.element.addClass('selected');
117 } else {
116 } else {
118 this.element.addClass('unselected');
117 this.element.addClass('unselected');
119 }
118 }
120 if (this.rendered) {
119 if (this.rendered) {
121 this.element.addClass('rendered');
120 this.element.addClass('rendered');
122 } else {
121 } else {
123 this.element.addClass('unrendered');
122 this.element.addClass('unrendered');
124 }
123 }
125 if (this.mode === 'edit') {
124 if (this.mode === 'edit') {
126 this.element.addClass('edit_mode');
125 this.element.addClass('edit_mode');
127 } else {
126 } else {
128 this.element.addClass('command_mode');
127 this.element.addClass('command_mode');
129 }
128 }
130 };
129 };
131
130
132 /**
131 /**
133 * Subclasses can implement override bind_events.
132 * Subclasses can implement override bind_events.
134 * Be carefull to call the parent method when overwriting as it fires event.
133 * Be carefull to call the parent method when overwriting as it fires event.
135 * this will be triggerd after create_element in constructor.
134 * this will be triggerd after create_element in constructor.
136 * @method bind_events
135 * @method bind_events
137 */
136 */
138 Cell.prototype.bind_events = function () {
137 Cell.prototype.bind_events = function () {
139 var that = this;
138 var that = this;
140 // We trigger events so that Cell doesn't have to depend on Notebook.
139 // We trigger events so that Cell doesn't have to depend on Notebook.
141 that.element.click(function (event) {
140 that.element.click(function (event) {
142 if (!that.selected) {
141 if (!that.selected) {
143 that.events.trigger('select.Cell', {'cell':that});
142 that.events.trigger('select.Cell', {'cell':that});
144 }
143 }
145 });
144 });
146 that.element.focusin(function (event) {
145 that.element.focusin(function (event) {
147 if (!that.selected) {
146 if (!that.selected) {
148 that.events.trigger('select.Cell', {'cell':that});
147 that.events.trigger('select.Cell', {'cell':that});
149 }
148 }
150 });
149 });
151 if (this.code_mirror) {
150 if (this.code_mirror) {
152 this.code_mirror.on("change", function(cm, change) {
151 this.code_mirror.on("change", function(cm, change) {
153 that.events.trigger("set_dirty.Notebook", {value: true});
152 that.events.trigger("set_dirty.Notebook", {value: true});
154 });
153 });
155 }
154 }
156 if (this.code_mirror) {
155 if (this.code_mirror) {
157 this.code_mirror.on('focus', function(cm, change) {
156 this.code_mirror.on('focus', function(cm, change) {
158 that.events.trigger('edit_mode.Cell', {cell: that});
157 that.events.trigger('edit_mode.Cell', {cell: that});
159 });
158 });
160 }
159 }
161 if (this.code_mirror) {
160 if (this.code_mirror) {
162 this.code_mirror.on('blur', function(cm, change) {
161 this.code_mirror.on('blur', function(cm, change) {
163 that.events.trigger('command_mode.Cell', {cell: that});
162 that.events.trigger('command_mode.Cell', {cell: that});
164 });
163 });
165 }
164 }
166 };
165 };
167
166
168 /**
167 /**
169 * This method gets called in CodeMirror's onKeyDown/onKeyPress
168 * This method gets called in CodeMirror's onKeyDown/onKeyPress
170 * handlers and is used to provide custom key handling.
169 * handlers and is used to provide custom key handling.
171 *
170 *
172 * To have custom handling, subclasses should override this method, but still call it
171 * To have custom handling, subclasses should override this method, but still call it
173 * in order to process the Edit mode keyboard shortcuts.
172 * in order to process the Edit mode keyboard shortcuts.
174 *
173 *
175 * @method handle_codemirror_keyevent
174 * @method handle_codemirror_keyevent
176 * @param {CodeMirror} editor - The codemirror instance bound to the cell
175 * @param {CodeMirror} editor - The codemirror instance bound to the cell
177 * @param {event} event - key press event which either should or should not be handled by CodeMirror
176 * @param {event} event - key press event which either should or should not be handled by CodeMirror
178 * @return {Boolean} `true` if CodeMirror should ignore the event, `false` Otherwise
177 * @return {Boolean} `true` if CodeMirror should ignore the event, `false` Otherwise
179 */
178 */
180 Cell.prototype.handle_codemirror_keyevent = function (editor, event) {
179 Cell.prototype.handle_codemirror_keyevent = function (editor, event) {
181 var that = this;
180 var that = this;
182 var shortcuts = this.keyboard_manager.edit_shortcuts;
181 var shortcuts = this.keyboard_manager.edit_shortcuts;
183
182
184 // if this is an edit_shortcuts shortcut, the global keyboard/shortcut
183 // if this is an edit_shortcuts shortcut, the global keyboard/shortcut
185 // manager will handle it
184 // manager will handle it
186 if (shortcuts.handles(event)) { return true; }
185 if (shortcuts.handles(event)) { return true; }
187
186
188 return false;
187 return false;
189 };
188 };
190
189
191
190
192 /**
191 /**
193 * Triger typsetting of math by mathjax on current cell element
192 * Triger typsetting of math by mathjax on current cell element
194 * @method typeset
193 * @method typeset
195 */
194 */
196 Cell.prototype.typeset = function () {
195 Cell.prototype.typeset = function () {
197 if (window.MathJax) {
196 if (window.MathJax) {
198 var cell_math = this.element.get(0);
197 var cell_math = this.element.get(0);
199 MathJax.Hub.Queue(["Typeset", MathJax.Hub, cell_math]);
198 MathJax.Hub.Queue(["Typeset", MathJax.Hub, cell_math]);
200 }
199 }
201 };
200 };
202
201
203 /**
202 /**
204 * handle cell level logic when a cell is selected
203 * handle cell level logic when a cell is selected
205 * @method select
204 * @method select
206 * @return is the action being taken
205 * @return is the action being taken
207 */
206 */
208 Cell.prototype.select = function () {
207 Cell.prototype.select = function () {
209 if (!this.selected) {
208 if (!this.selected) {
210 this.element.addClass('selected');
209 this.element.addClass('selected');
211 this.element.removeClass('unselected');
210 this.element.removeClass('unselected');
212 this.selected = true;
211 this.selected = true;
213 return true;
212 return true;
214 } else {
213 } else {
215 return false;
214 return false;
216 }
215 }
217 };
216 };
218
217
219 /**
218 /**
220 * handle cell level logic when a cell is unselected
219 * handle cell level logic when a cell is unselected
221 * @method unselect
220 * @method unselect
222 * @return is the action being taken
221 * @return is the action being taken
223 */
222 */
224 Cell.prototype.unselect = function () {
223 Cell.prototype.unselect = function () {
225 if (this.selected) {
224 if (this.selected) {
226 this.element.addClass('unselected');
225 this.element.addClass('unselected');
227 this.element.removeClass('selected');
226 this.element.removeClass('selected');
228 this.selected = false;
227 this.selected = false;
229 return true;
228 return true;
230 } else {
229 } else {
231 return false;
230 return false;
232 }
231 }
233 };
232 };
234
233
235 /**
234 /**
236 * handle cell level logic when a cell is rendered
235 * handle cell level logic when a cell is rendered
237 * @method render
236 * @method render
238 * @return is the action being taken
237 * @return is the action being taken
239 */
238 */
240 Cell.prototype.render = function () {
239 Cell.prototype.render = function () {
241 if (!this.rendered) {
240 if (!this.rendered) {
242 this.element.addClass('rendered');
241 this.element.addClass('rendered');
243 this.element.removeClass('unrendered');
242 this.element.removeClass('unrendered');
244 this.rendered = true;
243 this.rendered = true;
245 return true;
244 return true;
246 } else {
245 } else {
247 return false;
246 return false;
248 }
247 }
249 };
248 };
250
249
251 /**
250 /**
252 * handle cell level logic when a cell is unrendered
251 * handle cell level logic when a cell is unrendered
253 * @method unrender
252 * @method unrender
254 * @return is the action being taken
253 * @return is the action being taken
255 */
254 */
256 Cell.prototype.unrender = function () {
255 Cell.prototype.unrender = function () {
257 if (this.rendered) {
256 if (this.rendered) {
258 this.element.addClass('unrendered');
257 this.element.addClass('unrendered');
259 this.element.removeClass('rendered');
258 this.element.removeClass('rendered');
260 this.rendered = false;
259 this.rendered = false;
261 return true;
260 return true;
262 } else {
261 } else {
263 return false;
262 return false;
264 }
263 }
265 };
264 };
266
265
267 /**
266 /**
268 * Delegates keyboard shortcut handling to either IPython keyboard
267 * Delegates keyboard shortcut handling to either IPython keyboard
269 * manager when in command mode, or CodeMirror when in edit mode
268 * manager when in command mode, or CodeMirror when in edit mode
270 *
269 *
271 * @method handle_keyevent
270 * @method handle_keyevent
272 * @param {CodeMirror} editor - The codemirror instance bound to the cell
271 * @param {CodeMirror} editor - The codemirror instance bound to the cell
273 * @param {event} - key event to be handled
272 * @param {event} - key event to be handled
274 * @return {Boolean} `true` if CodeMirror should ignore the event, `false` Otherwise
273 * @return {Boolean} `true` if CodeMirror should ignore the event, `false` Otherwise
275 */
274 */
276 Cell.prototype.handle_keyevent = function (editor, event) {
275 Cell.prototype.handle_keyevent = function (editor, event) {
277
276
278 // console.log('CM', this.mode, event.which, event.type)
277 // console.log('CM', this.mode, event.which, event.type)
279
278
280 if (this.mode === 'command') {
279 if (this.mode === 'command') {
281 return true;
280 return true;
282 } else if (this.mode === 'edit') {
281 } else if (this.mode === 'edit') {
283 return this.handle_codemirror_keyevent(editor, event);
282 return this.handle_codemirror_keyevent(editor, event);
284 }
283 }
285 };
284 };
286
285
287 /**
286 /**
288 * @method at_top
287 * @method at_top
289 * @return {Boolean}
288 * @return {Boolean}
290 */
289 */
291 Cell.prototype.at_top = function () {
290 Cell.prototype.at_top = function () {
292 var cm = this.code_mirror;
291 var cm = this.code_mirror;
293 var cursor = cm.getCursor();
292 var cursor = cm.getCursor();
294 if (cursor.line === 0 && cursor.ch === 0) {
293 if (cursor.line === 0 && cursor.ch === 0) {
295 return true;
294 return true;
296 }
295 }
297 return false;
296 return false;
298 };
297 };
299
298
300 /**
299 /**
301 * @method at_bottom
300 * @method at_bottom
302 * @return {Boolean}
301 * @return {Boolean}
303 * */
302 * */
304 Cell.prototype.at_bottom = function () {
303 Cell.prototype.at_bottom = function () {
305 var cm = this.code_mirror;
304 var cm = this.code_mirror;
306 var cursor = cm.getCursor();
305 var cursor = cm.getCursor();
307 if (cursor.line === (cm.lineCount()-1) && cursor.ch === cm.getLine(cursor.line).length) {
306 if (cursor.line === (cm.lineCount()-1) && cursor.ch === cm.getLine(cursor.line).length) {
308 return true;
307 return true;
309 }
308 }
310 return false;
309 return false;
311 };
310 };
312
311
313 /**
312 /**
314 * enter the command mode for the cell
313 * enter the command mode for the cell
315 * @method command_mode
314 * @method command_mode
316 * @return is the action being taken
315 * @return is the action being taken
317 */
316 */
318 Cell.prototype.command_mode = function () {
317 Cell.prototype.command_mode = function () {
319 if (this.mode !== 'command') {
318 if (this.mode !== 'command') {
320 this.element.addClass('command_mode');
319 this.element.addClass('command_mode');
321 this.element.removeClass('edit_mode');
320 this.element.removeClass('edit_mode');
322 this.mode = 'command';
321 this.mode = 'command';
323 return true;
322 return true;
324 } else {
323 } else {
325 return false;
324 return false;
326 }
325 }
327 };
326 };
328
327
329 /**
328 /**
330 * enter the edit mode for the cell
329 * enter the edit mode for the cell
331 * @method command_mode
330 * @method command_mode
332 * @return is the action being taken
331 * @return is the action being taken
333 */
332 */
334 Cell.prototype.edit_mode = function () {
333 Cell.prototype.edit_mode = function () {
335 if (this.mode !== 'edit') {
334 if (this.mode !== 'edit') {
336 this.element.addClass('edit_mode');
335 this.element.addClass('edit_mode');
337 this.element.removeClass('command_mode');
336 this.element.removeClass('command_mode');
338 this.mode = 'edit';
337 this.mode = 'edit';
339 return true;
338 return true;
340 } else {
339 } else {
341 return false;
340 return false;
342 }
341 }
343 };
342 };
344
343
345 /**
344 /**
346 * Focus the cell in the DOM sense
345 * Focus the cell in the DOM sense
347 * @method focus_cell
346 * @method focus_cell
348 */
347 */
349 Cell.prototype.focus_cell = function () {
348 Cell.prototype.focus_cell = function () {
350 this.element.focus();
349 this.element.focus();
351 };
350 };
352
351
353 /**
352 /**
354 * Focus the editor area so a user can type
353 * Focus the editor area so a user can type
355 *
354 *
356 * NOTE: If codemirror is focused via a mouse click event, you don't want to
355 * NOTE: If codemirror is focused via a mouse click event, you don't want to
357 * call this because it will cause a page jump.
356 * call this because it will cause a page jump.
358 * @method focus_editor
357 * @method focus_editor
359 */
358 */
360 Cell.prototype.focus_editor = function () {
359 Cell.prototype.focus_editor = function () {
361 this.refresh();
360 this.refresh();
362 this.code_mirror.focus();
361 this.code_mirror.focus();
363 };
362 };
364
363
365 /**
364 /**
366 * Refresh codemirror instance
365 * Refresh codemirror instance
367 * @method refresh
366 * @method refresh
368 */
367 */
369 Cell.prototype.refresh = function () {
368 Cell.prototype.refresh = function () {
370 this.code_mirror.refresh();
369 this.code_mirror.refresh();
371 };
370 };
372
371
373 /**
372 /**
374 * should be overritten by subclass
373 * should be overritten by subclass
375 * @method get_text
374 * @method get_text
376 */
375 */
377 Cell.prototype.get_text = function () {
376 Cell.prototype.get_text = function () {
378 };
377 };
379
378
380 /**
379 /**
381 * should be overritten by subclass
380 * should be overritten by subclass
382 * @method set_text
381 * @method set_text
383 * @param {string} text
382 * @param {string} text
384 */
383 */
385 Cell.prototype.set_text = function (text) {
384 Cell.prototype.set_text = function (text) {
386 };
385 };
387
386
388 /**
387 /**
389 * should be overritten by subclass
388 * should be overritten by subclass
390 * serialise cell to json.
389 * serialise cell to json.
391 * @method toJSON
390 * @method toJSON
392 **/
391 **/
393 Cell.prototype.toJSON = function () {
392 Cell.prototype.toJSON = function () {
394 var data = {};
393 var data = {};
395 data.metadata = this.metadata;
394 data.metadata = this.metadata;
396 data.cell_type = this.cell_type;
395 data.cell_type = this.cell_type;
397 return data;
396 return data;
398 };
397 };
399
398
400
399
401 /**
400 /**
402 * should be overritten by subclass
401 * should be overritten by subclass
403 * @method fromJSON
402 * @method fromJSON
404 **/
403 **/
405 Cell.prototype.fromJSON = function (data) {
404 Cell.prototype.fromJSON = function (data) {
406 if (data.metadata !== undefined) {
405 if (data.metadata !== undefined) {
407 this.metadata = data.metadata;
406 this.metadata = data.metadata;
408 }
407 }
409 this.celltoolbar.rebuild();
408 this.celltoolbar.rebuild();
410 };
409 };
411
410
412
411
413 /**
412 /**
414 * can the cell be split into two cells
413 * can the cell be split into two cells
415 * @method is_splittable
414 * @method is_splittable
416 **/
415 **/
417 Cell.prototype.is_splittable = function () {
416 Cell.prototype.is_splittable = function () {
418 return true;
417 return true;
419 };
418 };
420
419
421
420
422 /**
421 /**
423 * can the cell be merged with other cells
422 * can the cell be merged with other cells
424 * @method is_mergeable
423 * @method is_mergeable
425 **/
424 **/
426 Cell.prototype.is_mergeable = function () {
425 Cell.prototype.is_mergeable = function () {
427 return true;
426 return true;
428 };
427 };
429
428
430
429
431 /**
430 /**
432 * @return {String} - the text before the cursor
431 * @return {String} - the text before the cursor
433 * @method get_pre_cursor
432 * @method get_pre_cursor
434 **/
433 **/
435 Cell.prototype.get_pre_cursor = function () {
434 Cell.prototype.get_pre_cursor = function () {
436 var cursor = this.code_mirror.getCursor();
435 var cursor = this.code_mirror.getCursor();
437 var text = this.code_mirror.getRange({line:0, ch:0}, cursor);
436 var text = this.code_mirror.getRange({line:0, ch:0}, cursor);
438 text = text.replace(/^\n+/, '').replace(/\n+$/, '');
437 text = text.replace(/^\n+/, '').replace(/\n+$/, '');
439 return text;
438 return text;
440 };
439 };
441
440
442
441
443 /**
442 /**
444 * @return {String} - the text after the cursor
443 * @return {String} - the text after the cursor
445 * @method get_post_cursor
444 * @method get_post_cursor
446 **/
445 **/
447 Cell.prototype.get_post_cursor = function () {
446 Cell.prototype.get_post_cursor = function () {
448 var cursor = this.code_mirror.getCursor();
447 var cursor = this.code_mirror.getCursor();
449 var last_line_num = this.code_mirror.lineCount()-1;
448 var last_line_num = this.code_mirror.lineCount()-1;
450 var last_line_len = this.code_mirror.getLine(last_line_num).length;
449 var last_line_len = this.code_mirror.getLine(last_line_num).length;
451 var end = {line:last_line_num, ch:last_line_len};
450 var end = {line:last_line_num, ch:last_line_len};
452 var text = this.code_mirror.getRange(cursor, end);
451 var text = this.code_mirror.getRange(cursor, end);
453 text = text.replace(/^\n+/, '').replace(/\n+$/, '');
452 text = text.replace(/^\n+/, '').replace(/\n+$/, '');
454 return text;
453 return text;
455 };
454 };
456
455
457 /**
456 /**
458 * Show/Hide CodeMirror LineNumber
457 * Show/Hide CodeMirror LineNumber
459 * @method show_line_numbers
458 * @method show_line_numbers
460 *
459 *
461 * @param value {Bool} show (true), or hide (false) the line number in CodeMirror
460 * @param value {Bool} show (true), or hide (false) the line number in CodeMirror
462 **/
461 **/
463 Cell.prototype.show_line_numbers = function (value) {
462 Cell.prototype.show_line_numbers = function (value) {
464 this.code_mirror.setOption('lineNumbers', value);
463 this.code_mirror.setOption('lineNumbers', value);
465 this.code_mirror.refresh();
464 this.code_mirror.refresh();
466 };
465 };
467
466
468 /**
467 /**
469 * Toggle CodeMirror LineNumber
468 * Toggle CodeMirror LineNumber
470 * @method toggle_line_numbers
469 * @method toggle_line_numbers
471 **/
470 **/
472 Cell.prototype.toggle_line_numbers = function () {
471 Cell.prototype.toggle_line_numbers = function () {
473 var val = this.code_mirror.getOption('lineNumbers');
472 var val = this.code_mirror.getOption('lineNumbers');
474 this.show_line_numbers(!val);
473 this.show_line_numbers(!val);
475 };
474 };
476
475
477 /**
476 /**
478 * Force codemirror highlight mode
477 * Force codemirror highlight mode
479 * @method force_highlight
478 * @method force_highlight
480 * @param {object} - CodeMirror mode
479 * @param {object} - CodeMirror mode
481 **/
480 **/
482 Cell.prototype.force_highlight = function(mode) {
481 Cell.prototype.force_highlight = function(mode) {
483 this.user_highlight = mode;
482 this.user_highlight = mode;
484 this.auto_highlight();
483 this.auto_highlight();
485 };
484 };
486
485
487 /**
486 /**
488 * Try to autodetect cell highlight mode, or use selected mode
487 * Try to autodetect cell highlight mode, or use selected mode
489 * @methods _auto_highlight
488 * @methods _auto_highlight
490 * @private
489 * @private
491 * @param {String|object|undefined} - CodeMirror mode | 'auto'
490 * @param {String|object|undefined} - CodeMirror mode | 'auto'
492 **/
491 **/
493 Cell.prototype._auto_highlight = function (modes) {
492 Cell.prototype._auto_highlight = function (modes) {
494 //Here we handle manually selected modes
493 //Here we handle manually selected modes
495 var mode;
494 var mode;
496 if( this.user_highlight !== undefined && this.user_highlight != 'auto' )
495 if( this.user_highlight !== undefined && this.user_highlight != 'auto' )
497 {
496 {
498 mode = this.user_highlight;
497 mode = this.user_highlight;
499 CodeMirror.autoLoadMode(this.code_mirror, mode);
498 CodeMirror.autoLoadMode(this.code_mirror, mode);
500 this.code_mirror.setOption('mode', mode);
499 this.code_mirror.setOption('mode', mode);
501 return;
500 return;
502 }
501 }
503 var current_mode = this.code_mirror.getOption('mode', mode);
502 var current_mode = this.code_mirror.getOption('mode', mode);
504 var first_line = this.code_mirror.getLine(0);
503 var first_line = this.code_mirror.getLine(0);
505 // loop on every pairs
504 // loop on every pairs
506 for(mode in modes) {
505 for(mode in modes) {
507 var regs = modes[mode].reg;
506 var regs = modes[mode].reg;
508 // only one key every time but regexp can't be keys...
507 // only one key every time but regexp can't be keys...
509 for(var i=0; i<regs.length; i++) {
508 for(var i=0; i<regs.length; i++) {
510 // here we handle non magic_modes
509 // here we handle non magic_modes
511 if(first_line.match(regs[i]) !== null) {
510 if(first_line.match(regs[i]) !== null) {
512 if(current_mode == mode){
511 if(current_mode == mode){
513 return;
512 return;
514 }
513 }
515 if (mode.search('magic_') !== 0) {
514 if (mode.search('magic_') !== 0) {
516 this.code_mirror.setOption('mode', mode);
515 this.code_mirror.setOption('mode', mode);
517 CodeMirror.autoLoadMode(this.code_mirror, mode);
516 CodeMirror.autoLoadMode(this.code_mirror, mode);
518 return;
517 return;
519 }
518 }
520 var open = modes[mode].open || "%%";
519 var open = modes[mode].open || "%%";
521 var close = modes[mode].close || "%%end";
520 var close = modes[mode].close || "%%end";
522 var mmode = mode;
521 var mmode = mode;
523 mode = mmode.substr(6);
522 mode = mmode.substr(6);
524 if(current_mode == mode){
523 if(current_mode == mode){
525 return;
524 return;
526 }
525 }
527 CodeMirror.autoLoadMode(this.code_mirror, mode);
526 CodeMirror.autoLoadMode(this.code_mirror, mode);
528 // create on the fly a mode that swhitch between
527 // create on the fly a mode that swhitch between
529 // plain/text and smth else otherwise `%%` is
528 // plain/text and smth else otherwise `%%` is
530 // source of some highlight issues.
529 // source of some highlight issues.
531 // we use patchedGetMode to circumvent a bug in CM
530 // we use patchedGetMode to circumvent a bug in CM
532 CodeMirror.defineMode(mmode , function(config) {
531 CodeMirror.defineMode(mmode , function(config) {
533 return CodeMirror.multiplexingMode(
532 return CodeMirror.multiplexingMode(
534 CodeMirror.patchedGetMode(config, 'text/plain'),
533 CodeMirror.patchedGetMode(config, 'text/plain'),
535 // always set someting on close
534 // always set someting on close
536 {open: open, close: close,
535 {open: open, close: close,
537 mode: CodeMirror.patchedGetMode(config, mode),
536 mode: CodeMirror.patchedGetMode(config, mode),
538 delimStyle: "delimit"
537 delimStyle: "delimit"
539 }
538 }
540 );
539 );
541 });
540 });
542 this.code_mirror.setOption('mode', mmode);
541 this.code_mirror.setOption('mode', mmode);
543 return;
542 return;
544 }
543 }
545 }
544 }
546 }
545 }
547 // fallback on default
546 // fallback on default
548 var default_mode;
547 var default_mode;
549 try {
548 try {
550 default_mode = this._options.cm_config.mode;
549 default_mode = this._options.cm_config.mode;
551 } catch(e) {
550 } catch(e) {
552 default_mode = 'text/plain';
551 default_mode = 'text/plain';
553 }
552 }
554 if( current_mode === default_mode){
553 if( current_mode === default_mode){
555 return;
554 return;
556 }
555 }
557 this.code_mirror.setOption('mode', default_mode);
556 this.code_mirror.setOption('mode', default_mode);
558 };
557 };
559
558
560 // Backwards compatability.
559 // Backwards compatability.
561 IPython.Cell = Cell;
560 IPython.Cell = Cell;
562
561
563 return {'Cell': Cell};
562 return {'Cell': Cell};
564 });
563 });
@@ -1,519 +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 /**
45 * A Cell conceived to write code.
46 *
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.
49 * @class CodeCell
50 * @extends Cell
51 *
52 * @constructor
53 * @param {Object|null} kernel
54 * @param {object|undefined} [options]
55 * @param [options.cm_config] {object} config to pass to CodeMirror
56 */
57 var CodeCell = function (kernel, options) {
44 var CodeCell = function (kernel, options) {
58 options = options || {};
45 // Constructor
46 //
47 // A Cell conceived to write code.
48 //
49 // Parameters:
50 // kernel: Kernel instance
51 // The kernel doesn't have to be set at creation time, in that case
52 // it will be null and set_kernel has to be called later.
53 // options: dictionary
54 // Dictionary of keyword arguments.
55 // events: $(Events) instance
56 // config: dictionary
57 // keyboard_manager: KeyboardManager instance
58 // notebook: Notebook instance
59 this.kernel = kernel || null;
59 this.kernel = kernel || null;
60 this.notebook = options.notebook;
60 this.notebook = options.notebook;
61 this.collapsed = false;
61 this.collapsed = false;
62 this.events = options.events;
62 this.events = options.events;
63 this.tooltip = new tooltip.Tooltip(this.events);
63 this.tooltip = new tooltip.Tooltip(this.events);
64 this.config = options.config;
64 this.config = options.config;
65
65
66 // create all attributed in constructor function
66 // create all attributed in constructor function
67 // even if null for V8 VM optimisation
67 // even if null for V8 VM optimisation
68 this.input_prompt_number = null;
68 this.input_prompt_number = null;
69 this.celltoolbar = null;
69 this.celltoolbar = null;
70 this.output_area = null;
70 this.output_area = null;
71 this.last_msg_id = null;
71 this.last_msg_id = null;
72 this.completer = null;
72 this.completer = null;
73
73
74
74
75 var cm_overwrite_options = {
75 var cm_overwrite_options = {
76 onKeyEvent: $.proxy(this.handle_keyevent,this)
76 onKeyEvent: $.proxy(this.handle_keyevent,this)
77 };
77 };
78
78
79 var config = this.mergeopt(CodeCell, this.config, {cm_config: cm_overwrite_options});
79 var config = this.mergeopt(CodeCell, this.config, {cm_config: cm_overwrite_options});
80 Cell.apply(this,[{
80 Cell.apply(this,[{
81 config: config,
81 config: config,
82 keyboard_manager: options.keyboard_manager,
82 keyboard_manager: options.keyboard_manager,
83 events: this.events}]);
83 events: this.events}]);
84
84
85 // Attributes we want to override in this subclass.
85 // Attributes we want to override in this subclass.
86 this.cell_type = "code";
86 this.cell_type = "code";
87
87
88 var that = this;
88 var that = this;
89 this.element.focusout(
89 this.element.focusout(
90 function() { that.auto_highlight(); }
90 function() { that.auto_highlight(); }
91 );
91 );
92 };
92 };
93
93
94 CodeCell.options_default = {
94 CodeCell.options_default = {
95 cm_config : {
95 cm_config : {
96 extraKeys: {
96 extraKeys: {
97 "Tab" : "indentMore",
97 "Tab" : "indentMore",
98 "Shift-Tab" : "indentLess",
98 "Shift-Tab" : "indentLess",
99 "Backspace" : "delSpaceToPrevTabStop",
99 "Backspace" : "delSpaceToPrevTabStop",
100 "Cmd-/" : "toggleComment",
100 "Cmd-/" : "toggleComment",
101 "Ctrl-/" : "toggleComment"
101 "Ctrl-/" : "toggleComment"
102 },
102 },
103 mode: 'ipython',
103 mode: 'ipython',
104 theme: 'ipython',
104 theme: 'ipython',
105 matchBrackets: true,
105 matchBrackets: true,
106 // don't auto-close strings because of CodeMirror #2385
106 // don't auto-close strings because of CodeMirror #2385
107 autoCloseBrackets: "()[]{}"
107 autoCloseBrackets: "()[]{}"
108 }
108 }
109 };
109 };
110
110
111 CodeCell.msg_cells = {};
111 CodeCell.msg_cells = {};
112
112
113 CodeCell.prototype = new Cell();
113 CodeCell.prototype = new Cell();
114
114
115 /**
115 /**
116 * @method auto_highlight
116 * @method auto_highlight
117 */
117 */
118 CodeCell.prototype.auto_highlight = function () {
118 CodeCell.prototype.auto_highlight = function () {
119 this._auto_highlight(this.config.cell_magic_highlight);
119 this._auto_highlight(this.config.cell_magic_highlight);
120 };
120 };
121
121
122 /** @method create_element */
122 /** @method create_element */
123 CodeCell.prototype.create_element = function () {
123 CodeCell.prototype.create_element = function () {
124 Cell.prototype.create_element.apply(this, arguments);
124 Cell.prototype.create_element.apply(this, arguments);
125
125
126 var cell = $('<div></div>').addClass('cell border-box-sizing code_cell');
126 var cell = $('<div></div>').addClass('cell border-box-sizing code_cell');
127 cell.attr('tabindex','2');
127 cell.attr('tabindex','2');
128
128
129 var input = $('<div></div>').addClass('input');
129 var input = $('<div></div>').addClass('input');
130 var prompt = $('<div/>').addClass('prompt input_prompt');
130 var prompt = $('<div/>').addClass('prompt input_prompt');
131 var inner_cell = $('<div/>').addClass('inner_cell');
131 var inner_cell = $('<div/>').addClass('inner_cell');
132 this.celltoolbar = new celltoolbar.CellToolbar(this, this.events, this.notebook);
132 this.celltoolbar = new celltoolbar.CellToolbar(this, this.events, this.notebook);
133 inner_cell.append(this.celltoolbar.element);
133 inner_cell.append(this.celltoolbar.element);
134 var input_area = $('<div/>').addClass('input_area');
134 var input_area = $('<div/>').addClass('input_area');
135 this.code_mirror = CodeMirror(input_area.get(0), this.cm_config);
135 this.code_mirror = CodeMirror(input_area.get(0), this.cm_config);
136 $(this.code_mirror.getInputField()).attr("spellcheck", "false");
136 $(this.code_mirror.getInputField()).attr("spellcheck", "false");
137 inner_cell.append(input_area);
137 inner_cell.append(input_area);
138 input.append(prompt).append(inner_cell);
138 input.append(prompt).append(inner_cell);
139
139
140 var widget_area = $('<div/>')
140 var widget_area = $('<div/>')
141 .addClass('widget-area')
141 .addClass('widget-area')
142 .hide();
142 .hide();
143 this.widget_area = widget_area;
143 this.widget_area = widget_area;
144 var widget_prompt = $('<div/>')
144 var widget_prompt = $('<div/>')
145 .addClass('prompt')
145 .addClass('prompt')
146 .appendTo(widget_area);
146 .appendTo(widget_area);
147 var widget_subarea = $('<div/>')
147 var widget_subarea = $('<div/>')
148 .addClass('widget-subarea')
148 .addClass('widget-subarea')
149 .appendTo(widget_area);
149 .appendTo(widget_area);
150 this.widget_subarea = widget_subarea;
150 this.widget_subarea = widget_subarea;
151 var widget_clear_buton = $('<button />')
151 var widget_clear_buton = $('<button />')
152 .addClass('close')
152 .addClass('close')
153 .html('&times;')
153 .html('&times;')
154 .click(function() {
154 .click(function() {
155 widget_area.slideUp('', function(){ widget_subarea.html(''); });
155 widget_area.slideUp('', function(){ widget_subarea.html(''); });
156 })
156 })
157 .appendTo(widget_prompt);
157 .appendTo(widget_prompt);
158
158
159 var output = $('<div></div>');
159 var output = $('<div></div>');
160 cell.append(input).append(widget_area).append(output);
160 cell.append(input).append(widget_area).append(output);
161 this.element = cell;
161 this.element = cell;
162 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);
163 this.completer = new completer.Completer(this, this.events);
163 this.completer = new completer.Completer(this, this.events);
164 };
164 };
165
165
166 /** @method bind_events */
166 /** @method bind_events */
167 CodeCell.prototype.bind_events = function () {
167 CodeCell.prototype.bind_events = function () {
168 Cell.prototype.bind_events.apply(this);
168 Cell.prototype.bind_events.apply(this);
169 var that = this;
169 var that = this;
170
170
171 this.element.focusout(
171 this.element.focusout(
172 function() { that.auto_highlight(); }
172 function() { that.auto_highlight(); }
173 );
173 );
174 };
174 };
175
175
176
176
177 /**
177 /**
178 * This method gets called in CodeMirror's onKeyDown/onKeyPress
178 * This method gets called in CodeMirror's onKeyDown/onKeyPress
179 * handlers and is used to provide custom key handling. Its return
179 * handlers and is used to provide custom key handling. Its return
180 * value is used to determine if CodeMirror should ignore the event:
180 * value is used to determine if CodeMirror should ignore the event:
181 * true = ignore, false = don't ignore.
181 * true = ignore, false = don't ignore.
182 * @method handle_codemirror_keyevent
182 * @method handle_codemirror_keyevent
183 */
183 */
184 CodeCell.prototype.handle_codemirror_keyevent = function (editor, event) {
184 CodeCell.prototype.handle_codemirror_keyevent = function (editor, event) {
185
185
186 var that = this;
186 var that = this;
187 // whatever key is pressed, first, cancel the tooltip request before
187 // whatever key is pressed, first, cancel the tooltip request before
188 // 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
189 var tooltip_closed = null;
189 var tooltip_closed = null;
190 if (event.type === 'keydown' && event.which != keycodes.tab ) {
190 if (event.type === 'keydown' && event.which != keycodes.tab ) {
191 tooltip_closed = this.tooltip.remove_and_cancel_tooltip();
191 tooltip_closed = this.tooltip.remove_and_cancel_tooltip();
192 }
192 }
193
193
194 var cur = editor.getCursor();
194 var cur = editor.getCursor();
195 if (event.keyCode === keycodes.enter){
195 if (event.keyCode === keycodes.enter){
196 this.auto_highlight();
196 this.auto_highlight();
197 }
197 }
198
198
199 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) {
200 // triger on keypress (!) otherwise inconsistent event.which depending on plateform
200 // triger on keypress (!) otherwise inconsistent event.which depending on plateform
201 // browser and keyboard layout !
201 // browser and keyboard layout !
202 // Pressing '(' , request tooltip, don't forget to reappend it
202 // Pressing '(' , request tooltip, don't forget to reappend it
203 // The second argument says to hide the tooltip if the docstring
203 // The second argument says to hide the tooltip if the docstring
204 // is actually empty
204 // is actually empty
205 this.tooltip.pending(that, true);
205 this.tooltip.pending(that, true);
206 } else if ( tooltip_closed && event.which === keycodes.esc && event.type === 'keydown') {
206 } else if ( tooltip_closed && event.which === keycodes.esc && event.type === 'keydown') {
207 // If tooltip is active, cancel it. The call to
207 // If tooltip is active, cancel it. The call to
208 // remove_and_cancel_tooltip above doesn't pass, force=true.
208 // remove_and_cancel_tooltip above doesn't pass, force=true.
209 // Because of this it won't actually close the tooltip
209 // Because of this it won't actually close the tooltip
210 // 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
211 // and close it with force=true.
211 // and close it with force=true.
212 if (!this.tooltip._hidden) {
212 if (!this.tooltip._hidden) {
213 this.tooltip.remove_and_cancel_tooltip(true);
213 this.tooltip.remove_and_cancel_tooltip(true);
214 }
214 }
215 // 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
216 // handle this event.
216 // handle this event.
217 event.stop();
217 event.stop();
218 return true;
218 return true;
219 } else if (event.keyCode === keycodes.tab && event.type === 'keydown' && event.shiftKey) {
219 } else if (event.keyCode === keycodes.tab && event.type === 'keydown' && event.shiftKey) {
220 if (editor.somethingSelected()){
220 if (editor.somethingSelected()){
221 var anchor = editor.getCursor("anchor");
221 var anchor = editor.getCursor("anchor");
222 var head = editor.getCursor("head");
222 var head = editor.getCursor("head");
223 if( anchor.line != head.line){
223 if( anchor.line != head.line){
224 return false;
224 return false;
225 }
225 }
226 }
226 }
227 this.tooltip.request(that);
227 this.tooltip.request(that);
228 event.stop();
228 event.stop();
229 return true;
229 return true;
230 } else if (event.keyCode === keycodes.tab && event.type == 'keydown') {
230 } else if (event.keyCode === keycodes.tab && event.type == 'keydown') {
231 // Tab completion.
231 // Tab completion.
232 this.tooltip.remove_and_cancel_tooltip();
232 this.tooltip.remove_and_cancel_tooltip();
233 if (editor.somethingSelected()) {
233 if (editor.somethingSelected()) {
234 return false;
234 return false;
235 }
235 }
236 var pre_cursor = editor.getRange({line:cur.line,ch:0},cur);
236 var pre_cursor = editor.getRange({line:cur.line,ch:0},cur);
237 if (pre_cursor.trim() === "") {
237 if (pre_cursor.trim() === "") {
238 // 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
239 // is empty. In this case, let CodeMirror handle indentation.
239 // is empty. In this case, let CodeMirror handle indentation.
240 return false;
240 return false;
241 } else {
241 } else {
242 event.stop();
242 event.stop();
243 this.completer.startCompletion();
243 this.completer.startCompletion();
244 return true;
244 return true;
245 }
245 }
246 }
246 }
247
247
248 // 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
249 // 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)
250 return Cell.prototype.handle_codemirror_keyevent.apply(this, [editor, event]);
250 return Cell.prototype.handle_codemirror_keyevent.apply(this, [editor, event]);
251 };
251 };
252
252
253 // Kernel related calls.
253 // Kernel related calls.
254
254
255 CodeCell.prototype.set_kernel = function (kernel) {
255 CodeCell.prototype.set_kernel = function (kernel) {
256 this.kernel = kernel;
256 this.kernel = kernel;
257 };
257 };
258
258
259 /**
259 /**
260 * Execute current code cell to the kernel
260 * Execute current code cell to the kernel
261 * @method execute
261 * @method execute
262 */
262 */
263 CodeCell.prototype.execute = function () {
263 CodeCell.prototype.execute = function () {
264 this.output_area.clear_output();
264 this.output_area.clear_output();
265
265
266 // Clear widget area
266 // Clear widget area
267 this.widget_subarea.html('');
267 this.widget_subarea.html('');
268 this.widget_subarea.height('');
268 this.widget_subarea.height('');
269 this.widget_area.height('');
269 this.widget_area.height('');
270 this.widget_area.hide();
270 this.widget_area.hide();
271
271
272 this.set_input_prompt('*');
272 this.set_input_prompt('*');
273 this.element.addClass("running");
273 this.element.addClass("running");
274 if (this.last_msg_id) {
274 if (this.last_msg_id) {
275 this.kernel.clear_callbacks_for_msg(this.last_msg_id);
275 this.kernel.clear_callbacks_for_msg(this.last_msg_id);
276 }
276 }
277 var callbacks = this.get_callbacks();
277 var callbacks = this.get_callbacks();
278
278
279 var old_msg_id = this.last_msg_id;
279 var old_msg_id = this.last_msg_id;
280 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});
281 if (old_msg_id) {
281 if (old_msg_id) {
282 delete CodeCell.msg_cells[old_msg_id];
282 delete CodeCell.msg_cells[old_msg_id];
283 }
283 }
284 CodeCell.msg_cells[this.last_msg_id] = this;
284 CodeCell.msg_cells[this.last_msg_id] = this;
285 };
285 };
286
286
287 /**
287 /**
288 * Construct the default callbacks for
288 * Construct the default callbacks for
289 * @method get_callbacks
289 * @method get_callbacks
290 */
290 */
291 CodeCell.prototype.get_callbacks = function () {
291 CodeCell.prototype.get_callbacks = function () {
292 return {
292 return {
293 shell : {
293 shell : {
294 reply : $.proxy(this._handle_execute_reply, this),
294 reply : $.proxy(this._handle_execute_reply, this),
295 payload : {
295 payload : {
296 set_next_input : $.proxy(this._handle_set_next_input, this),
296 set_next_input : $.proxy(this._handle_set_next_input, this),
297 page : $.proxy(this._open_with_pager, this)
297 page : $.proxy(this._open_with_pager, this)
298 }
298 }
299 },
299 },
300 iopub : {
300 iopub : {
301 output : $.proxy(this.output_area.handle_output, this.output_area),
301 output : $.proxy(this.output_area.handle_output, this.output_area),
302 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),
303 },
303 },
304 input : $.proxy(this._handle_input_request, this)
304 input : $.proxy(this._handle_input_request, this)
305 };
305 };
306 };
306 };
307
307
308 CodeCell.prototype._open_with_pager = function (payload) {
308 CodeCell.prototype._open_with_pager = function (payload) {
309 this.events.trigger('open_with_text.Pager', payload);
309 this.events.trigger('open_with_text.Pager', payload);
310 };
310 };
311
311
312 /**
312 /**
313 * @method _handle_execute_reply
313 * @method _handle_execute_reply
314 * @private
314 * @private
315 */
315 */
316 CodeCell.prototype._handle_execute_reply = function (msg) {
316 CodeCell.prototype._handle_execute_reply = function (msg) {
317 this.set_input_prompt(msg.content.execution_count);
317 this.set_input_prompt(msg.content.execution_count);
318 this.element.removeClass("running");
318 this.element.removeClass("running");
319 this.events.trigger('set_dirty.Notebook', {value: true});
319 this.events.trigger('set_dirty.Notebook', {value: true});
320 };
320 };
321
321
322 /**
322 /**
323 * @method _handle_set_next_input
323 * @method _handle_set_next_input
324 * @private
324 * @private
325 */
325 */
326 CodeCell.prototype._handle_set_next_input = function (payload) {
326 CodeCell.prototype._handle_set_next_input = function (payload) {
327 var data = {'cell': this, 'text': payload.text};
327 var data = {'cell': this, 'text': payload.text};
328 this.events.trigger('set_next_input.Notebook', data);
328 this.events.trigger('set_next_input.Notebook', data);
329 };
329 };
330
330
331 /**
331 /**
332 * @method _handle_input_request
332 * @method _handle_input_request
333 * @private
333 * @private
334 */
334 */
335 CodeCell.prototype._handle_input_request = function (msg) {
335 CodeCell.prototype._handle_input_request = function (msg) {
336 this.output_area.append_raw_input(msg);
336 this.output_area.append_raw_input(msg);
337 };
337 };
338
338
339
339
340 // Basic cell manipulation.
340 // Basic cell manipulation.
341
341
342 CodeCell.prototype.select = function () {
342 CodeCell.prototype.select = function () {
343 var cont = Cell.prototype.select.apply(this);
343 var cont = Cell.prototype.select.apply(this);
344 if (cont) {
344 if (cont) {
345 this.code_mirror.refresh();
345 this.code_mirror.refresh();
346 this.auto_highlight();
346 this.auto_highlight();
347 }
347 }
348 return cont;
348 return cont;
349 };
349 };
350
350
351 CodeCell.prototype.render = function () {
351 CodeCell.prototype.render = function () {
352 var cont = Cell.prototype.render.apply(this);
352 var cont = Cell.prototype.render.apply(this);
353 // Always execute, even if we are already in the rendered state
353 // Always execute, even if we are already in the rendered state
354 return cont;
354 return cont;
355 };
355 };
356
356
357 CodeCell.prototype.unrender = function () {
357 CodeCell.prototype.unrender = function () {
358 // CodeCell is always rendered
358 // CodeCell is always rendered
359 return false;
359 return false;
360 };
360 };
361
361
362 CodeCell.prototype.select_all = function () {
362 CodeCell.prototype.select_all = function () {
363 var start = {line: 0, ch: 0};
363 var start = {line: 0, ch: 0};
364 var nlines = this.code_mirror.lineCount();
364 var nlines = this.code_mirror.lineCount();
365 var last_line = this.code_mirror.getLine(nlines-1);
365 var last_line = this.code_mirror.getLine(nlines-1);
366 var end = {line: nlines-1, ch: last_line.length};
366 var end = {line: nlines-1, ch: last_line.length};
367 this.code_mirror.setSelection(start, end);
367 this.code_mirror.setSelection(start, end);
368 };
368 };
369
369
370
370
371 CodeCell.prototype.collapse_output = function () {
371 CodeCell.prototype.collapse_output = function () {
372 this.collapsed = true;
372 this.collapsed = true;
373 this.output_area.collapse();
373 this.output_area.collapse();
374 };
374 };
375
375
376
376
377 CodeCell.prototype.expand_output = function () {
377 CodeCell.prototype.expand_output = function () {
378 this.collapsed = false;
378 this.collapsed = false;
379 this.output_area.expand();
379 this.output_area.expand();
380 this.output_area.unscroll_area();
380 this.output_area.unscroll_area();
381 };
381 };
382
382
383 CodeCell.prototype.scroll_output = function () {
383 CodeCell.prototype.scroll_output = function () {
384 this.output_area.expand();
384 this.output_area.expand();
385 this.output_area.scroll_if_long();
385 this.output_area.scroll_if_long();
386 };
386 };
387
387
388 CodeCell.prototype.toggle_output = function () {
388 CodeCell.prototype.toggle_output = function () {
389 this.collapsed = Boolean(1 - this.collapsed);
389 this.collapsed = Boolean(1 - this.collapsed);
390 this.output_area.toggle_output();
390 this.output_area.toggle_output();
391 };
391 };
392
392
393 CodeCell.prototype.toggle_output_scroll = function () {
393 CodeCell.prototype.toggle_output_scroll = function () {
394 this.output_area.toggle_scroll();
394 this.output_area.toggle_scroll();
395 };
395 };
396
396
397
397
398 CodeCell.input_prompt_classical = function (prompt_value, lines_number) {
398 CodeCell.input_prompt_classical = function (prompt_value, lines_number) {
399 var ns;
399 var ns;
400 if (prompt_value === undefined) {
400 if (prompt_value === undefined) {
401 ns = "&nbsp;";
401 ns = "&nbsp;";
402 } else {
402 } else {
403 ns = encodeURIComponent(prompt_value);
403 ns = encodeURIComponent(prompt_value);
404 }
404 }
405 return 'In&nbsp;[' + ns + ']:';
405 return 'In&nbsp;[' + ns + ']:';
406 };
406 };
407
407
408 CodeCell.input_prompt_continuation = function (prompt_value, lines_number) {
408 CodeCell.input_prompt_continuation = function (prompt_value, lines_number) {
409 var html = [CodeCell.input_prompt_classical(prompt_value, lines_number)];
409 var html = [CodeCell.input_prompt_classical(prompt_value, lines_number)];
410 for(var i=1; i < lines_number; i++) {
410 for(var i=1; i < lines_number; i++) {
411 html.push(['...:']);
411 html.push(['...:']);
412 }
412 }
413 return html.join('<br/>');
413 return html.join('<br/>');
414 };
414 };
415
415
416 CodeCell.input_prompt_function = CodeCell.input_prompt_classical;
416 CodeCell.input_prompt_function = CodeCell.input_prompt_classical;
417
417
418
418
419 CodeCell.prototype.set_input_prompt = function (number) {
419 CodeCell.prototype.set_input_prompt = function (number) {
420 var nline = 1;
420 var nline = 1;
421 if (this.code_mirror !== undefined) {
421 if (this.code_mirror !== undefined) {
422 nline = this.code_mirror.lineCount();
422 nline = this.code_mirror.lineCount();
423 }
423 }
424 this.input_prompt_number = number;
424 this.input_prompt_number = number;
425 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);
426 // This HTML call is okay because the user contents are escaped.
426 // This HTML call is okay because the user contents are escaped.
427 this.element.find('div.input_prompt').html(prompt_html);
427 this.element.find('div.input_prompt').html(prompt_html);
428 };
428 };
429
429
430
430
431 CodeCell.prototype.clear_input = function () {
431 CodeCell.prototype.clear_input = function () {
432 this.code_mirror.setValue('');
432 this.code_mirror.setValue('');
433 };
433 };
434
434
435
435
436 CodeCell.prototype.get_text = function () {
436 CodeCell.prototype.get_text = function () {
437 return this.code_mirror.getValue();
437 return this.code_mirror.getValue();
438 };
438 };
439
439
440
440
441 CodeCell.prototype.set_text = function (code) {
441 CodeCell.prototype.set_text = function (code) {
442 return this.code_mirror.setValue(code);
442 return this.code_mirror.setValue(code);
443 };
443 };
444
444
445
445
446 CodeCell.prototype.clear_output = function (wait) {
446 CodeCell.prototype.clear_output = function (wait) {
447 this.output_area.clear_output(wait);
447 this.output_area.clear_output(wait);
448 this.set_input_prompt();
448 this.set_input_prompt();
449 };
449 };
450
450
451
451
452 // JSON serialization
452 // JSON serialization
453
453
454 CodeCell.prototype.fromJSON = function (data) {
454 CodeCell.prototype.fromJSON = function (data) {
455 Cell.prototype.fromJSON.apply(this, arguments);
455 Cell.prototype.fromJSON.apply(this, arguments);
456 if (data.cell_type === 'code') {
456 if (data.cell_type === 'code') {
457 if (data.input !== undefined) {
457 if (data.input !== undefined) {
458 this.set_text(data.input);
458 this.set_text(data.input);
459 // 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
460 // to this state, instead of a blank cell
460 // to this state, instead of a blank cell
461 this.code_mirror.clearHistory();
461 this.code_mirror.clearHistory();
462 this.auto_highlight();
462 this.auto_highlight();
463 }
463 }
464 if (data.prompt_number !== undefined) {
464 if (data.prompt_number !== undefined) {
465 this.set_input_prompt(data.prompt_number);
465 this.set_input_prompt(data.prompt_number);
466 } else {
466 } else {
467 this.set_input_prompt();
467 this.set_input_prompt();
468 }
468 }
469 this.output_area.trusted = data.trusted || false;
469 this.output_area.trusted = data.trusted || false;
470 this.output_area.fromJSON(data.outputs);
470 this.output_area.fromJSON(data.outputs);
471 if (data.collapsed !== undefined) {
471 if (data.collapsed !== undefined) {
472 if (data.collapsed) {
472 if (data.collapsed) {
473 this.collapse_output();
473 this.collapse_output();
474 } else {
474 } else {
475 this.expand_output();
475 this.expand_output();
476 }
476 }
477 }
477 }
478 }
478 }
479 };
479 };
480
480
481
481
482 CodeCell.prototype.toJSON = function () {
482 CodeCell.prototype.toJSON = function () {
483 var data = Cell.prototype.toJSON.apply(this);
483 var data = Cell.prototype.toJSON.apply(this);
484 data.input = this.get_text();
484 data.input = this.get_text();
485 // is finite protect against undefined and '*' value
485 // is finite protect against undefined and '*' value
486 if (isFinite(this.input_prompt_number)) {
486 if (isFinite(this.input_prompt_number)) {
487 data.prompt_number = this.input_prompt_number;
487 data.prompt_number = this.input_prompt_number;
488 }
488 }
489 var outputs = this.output_area.toJSON();
489 var outputs = this.output_area.toJSON();
490 data.outputs = outputs;
490 data.outputs = outputs;
491 data.language = 'python';
491 data.language = 'python';
492 data.trusted = this.output_area.trusted;
492 data.trusted = this.output_area.trusted;
493 data.collapsed = this.collapsed;
493 data.collapsed = this.collapsed;
494 return data;
494 return data;
495 };
495 };
496
496
497 /**
497 /**
498 * handle cell level logic when a cell is unselected
498 * handle cell level logic when a cell is unselected
499 * @method unselect
499 * @method unselect
500 * @return is the action being taken
500 * @return is the action being taken
501 */
501 */
502 CodeCell.prototype.unselect = function () {
502 CodeCell.prototype.unselect = function () {
503 var cont = Cell.prototype.unselect.apply(this);
503 var cont = Cell.prototype.unselect.apply(this);
504 if (cont) {
504 if (cont) {
505 // When a code cell is usnelected, make sure that the corresponding
505 // When a code cell is usnelected, make sure that the corresponding
506 // tooltip and completer to that cell is closed.
506 // tooltip and completer to that cell is closed.
507 this.tooltip.remove_and_cancel_tooltip(true);
507 this.tooltip.remove_and_cancel_tooltip(true);
508 if (this.completer !== null) {
508 if (this.completer !== null) {
509 this.completer.close();
509 this.completer.close();
510 }
510 }
511 }
511 }
512 return cont;
512 return cont;
513 };
513 };
514
514
515 // Backwards compatability.
515 // Backwards compatability.
516 IPython.CodeCell = CodeCell;
516 IPython.CodeCell = CodeCell;
517
517
518 return {'CodeCell': CodeCell};
518 return {'CodeCell': CodeCell};
519 });
519 });
@@ -1,559 +1,566 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 (options) {
18 var KeyboardManager = function (options) {
19 // Constructor
20 //
21 // Parameters:
22 // options: dictionary
23 // Dictionary of keyword arguments.
24 // events: $(Events) instance
25 // pager: Pager instance
19 this.mode = 'command';
26 this.mode = 'command';
20 this.enabled = true;
27 this.enabled = true;
21 this.pager = options.pager;
28 this.pager = options.pager;
22 this.quick_help = undefined;
29 this.quick_help = undefined;
23 this.notebook = undefined;
30 this.notebook = undefined;
24 this.bind_events();
31 this.bind_events();
25 this.command_shortcuts = new keyboard.ShortcutManager(undefined, options.events);
32 this.command_shortcuts = new keyboard.ShortcutManager(undefined, options.events);
26 this.command_shortcuts.add_shortcuts(this.get_default_common_shortcuts());
33 this.command_shortcuts.add_shortcuts(this.get_default_common_shortcuts());
27 this.command_shortcuts.add_shortcuts(this.get_default_command_shortcuts());
34 this.command_shortcuts.add_shortcuts(this.get_default_command_shortcuts());
28 this.edit_shortcuts = new keyboard.ShortcutManager(undefined, options.events);
35 this.edit_shortcuts = new keyboard.ShortcutManager(undefined, options.events);
29 this.edit_shortcuts.add_shortcuts(this.get_default_common_shortcuts());
36 this.edit_shortcuts.add_shortcuts(this.get_default_common_shortcuts());
30 this.edit_shortcuts.add_shortcuts(this.get_default_edit_shortcuts());
37 this.edit_shortcuts.add_shortcuts(this.get_default_edit_shortcuts());
31 };
38 };
32
39
33 KeyboardManager.prototype.get_default_common_shortcuts = function() {
40 KeyboardManager.prototype.get_default_common_shortcuts = function() {
34 var that = this;
41 var that = this;
35 var shortcuts = {
42 var shortcuts = {
36 'shift' : {
43 'shift' : {
37 help : '',
44 help : '',
38 help_index : '',
45 help_index : '',
39 handler : function (event) {
46 handler : function (event) {
40 // ignore shift keydown
47 // ignore shift keydown
41 return true;
48 return true;
42 }
49 }
43 },
50 },
44 'shift-enter' : {
51 'shift-enter' : {
45 help : 'run cell, select below',
52 help : 'run cell, select below',
46 help_index : 'ba',
53 help_index : 'ba',
47 handler : function (event) {
54 handler : function (event) {
48 that.notebook.execute_cell_and_select_below();
55 that.notebook.execute_cell_and_select_below();
49 return false;
56 return false;
50 }
57 }
51 },
58 },
52 'ctrl-enter' : {
59 'ctrl-enter' : {
53 help : 'run cell',
60 help : 'run cell',
54 help_index : 'bb',
61 help_index : 'bb',
55 handler : function (event) {
62 handler : function (event) {
56 that.notebook.execute_cell();
63 that.notebook.execute_cell();
57 return false;
64 return false;
58 }
65 }
59 },
66 },
60 'alt-enter' : {
67 'alt-enter' : {
61 help : 'run cell, insert below',
68 help : 'run cell, insert below',
62 help_index : 'bc',
69 help_index : 'bc',
63 handler : function (event) {
70 handler : function (event) {
64 that.notebook.execute_cell_and_insert_below();
71 that.notebook.execute_cell_and_insert_below();
65 return false;
72 return false;
66 }
73 }
67 }
74 }
68 };
75 };
69
76
70 if (platform === 'MacOS') {
77 if (platform === 'MacOS') {
71 shortcuts['cmd-s'] =
78 shortcuts['cmd-s'] =
72 {
79 {
73 help : 'save notebook',
80 help : 'save notebook',
74 help_index : 'fb',
81 help_index : 'fb',
75 handler : function (event) {
82 handler : function (event) {
76 that.notebook.save_checkpoint();
83 that.notebook.save_checkpoint();
77 event.preventDefault();
84 event.preventDefault();
78 return false;
85 return false;
79 }
86 }
80 };
87 };
81 } else {
88 } else {
82 shortcuts['ctrl-s'] =
89 shortcuts['ctrl-s'] =
83 {
90 {
84 help : 'save notebook',
91 help : 'save notebook',
85 help_index : 'fb',
92 help_index : 'fb',
86 handler : function (event) {
93 handler : function (event) {
87 that.notebook.save_checkpoint();
94 that.notebook.save_checkpoint();
88 event.preventDefault();
95 event.preventDefault();
89 return false;
96 return false;
90 }
97 }
91 };
98 };
92 }
99 }
93 return shortcuts;
100 return shortcuts;
94 };
101 };
95
102
96 KeyboardManager.prototype.get_default_edit_shortcuts = function() {
103 KeyboardManager.prototype.get_default_edit_shortcuts = function() {
97 var that = this;
104 var that = this;
98 return {
105 return {
99 'esc' : {
106 'esc' : {
100 help : 'command mode',
107 help : 'command mode',
101 help_index : 'aa',
108 help_index : 'aa',
102 handler : function (event) {
109 handler : function (event) {
103 that.notebook.command_mode();
110 that.notebook.command_mode();
104 return false;
111 return false;
105 }
112 }
106 },
113 },
107 'ctrl-m' : {
114 'ctrl-m' : {
108 help : 'command mode',
115 help : 'command mode',
109 help_index : 'ab',
116 help_index : 'ab',
110 handler : function (event) {
117 handler : function (event) {
111 that.notebook.command_mode();
118 that.notebook.command_mode();
112 return false;
119 return false;
113 }
120 }
114 },
121 },
115 'up' : {
122 'up' : {
116 help : '',
123 help : '',
117 help_index : '',
124 help_index : '',
118 handler : function (event) {
125 handler : function (event) {
119 var index = that.notebook.get_selected_index();
126 var index = that.notebook.get_selected_index();
120 var cell = that.notebook.get_cell(index);
127 var cell = that.notebook.get_cell(index);
121 if (cell && cell.at_top() && index !== 0) {
128 if (cell && cell.at_top() && index !== 0) {
122 event.preventDefault();
129 event.preventDefault();
123 that.notebook.command_mode();
130 that.notebook.command_mode();
124 that.notebook.select_prev();
131 that.notebook.select_prev();
125 that.notebook.edit_mode();
132 that.notebook.edit_mode();
126 var cm = that.notebook.get_selected_cell().code_mirror;
133 var cm = that.notebook.get_selected_cell().code_mirror;
127 cm.setCursor(cm.lastLine(), 0);
134 cm.setCursor(cm.lastLine(), 0);
128 return false;
135 return false;
129 } else if (cell) {
136 } else if (cell) {
130 var cm = cell.code_mirror;
137 var cm = cell.code_mirror;
131 cm.execCommand('goLineUp');
138 cm.execCommand('goLineUp');
132 return false;
139 return false;
133 }
140 }
134 }
141 }
135 },
142 },
136 'down' : {
143 'down' : {
137 help : '',
144 help : '',
138 help_index : '',
145 help_index : '',
139 handler : function (event) {
146 handler : function (event) {
140 var index = that.notebook.get_selected_index();
147 var index = that.notebook.get_selected_index();
141 var cell = that.notebook.get_cell(index);
148 var cell = that.notebook.get_cell(index);
142 if (cell.at_bottom() && index !== (that.notebook.ncells()-1)) {
149 if (cell.at_bottom() && index !== (that.notebook.ncells()-1)) {
143 event.preventDefault();
150 event.preventDefault();
144 that.notebook.command_mode();
151 that.notebook.command_mode();
145 that.notebook.select_next();
152 that.notebook.select_next();
146 that.notebook.edit_mode();
153 that.notebook.edit_mode();
147 var cm = that.notebook.get_selected_cell().code_mirror;
154 var cm = that.notebook.get_selected_cell().code_mirror;
148 cm.setCursor(0, 0);
155 cm.setCursor(0, 0);
149 return false;
156 return false;
150 } else {
157 } else {
151 var cm = cell.code_mirror;
158 var cm = cell.code_mirror;
152 cm.execCommand('goLineDown');
159 cm.execCommand('goLineDown');
153 return false;
160 return false;
154 }
161 }
155 }
162 }
156 },
163 },
157 'ctrl-shift--' : {
164 'ctrl-shift--' : {
158 help : 'split cell',
165 help : 'split cell',
159 help_index : 'ea',
166 help_index : 'ea',
160 handler : function (event) {
167 handler : function (event) {
161 that.notebook.split_cell();
168 that.notebook.split_cell();
162 return false;
169 return false;
163 }
170 }
164 },
171 },
165 'ctrl-shift-subtract' : {
172 'ctrl-shift-subtract' : {
166 help : '',
173 help : '',
167 help_index : 'eb',
174 help_index : 'eb',
168 handler : function (event) {
175 handler : function (event) {
169 that.notebook.split_cell();
176 that.notebook.split_cell();
170 return false;
177 return false;
171 }
178 }
172 },
179 },
173 };
180 };
174 };
181 };
175
182
176 KeyboardManager.prototype.get_default_command_shortcuts = function() {
183 KeyboardManager.prototype.get_default_command_shortcuts = function() {
177 var that = this;
184 var that = this;
178 return {
185 return {
179 'enter' : {
186 'enter' : {
180 help : 'edit mode',
187 help : 'edit mode',
181 help_index : 'aa',
188 help_index : 'aa',
182 handler : function (event) {
189 handler : function (event) {
183 that.notebook.edit_mode();
190 that.notebook.edit_mode();
184 return false;
191 return false;
185 }
192 }
186 },
193 },
187 'up' : {
194 'up' : {
188 help : 'select previous cell',
195 help : 'select previous cell',
189 help_index : 'da',
196 help_index : 'da',
190 handler : function (event) {
197 handler : function (event) {
191 var index = that.notebook.get_selected_index();
198 var index = that.notebook.get_selected_index();
192 if (index !== 0 && index !== null) {
199 if (index !== 0 && index !== null) {
193 that.notebook.select_prev();
200 that.notebook.select_prev();
194 that.notebook.focus_cell();
201 that.notebook.focus_cell();
195 }
202 }
196 return false;
203 return false;
197 }
204 }
198 },
205 },
199 'down' : {
206 'down' : {
200 help : 'select next cell',
207 help : 'select next cell',
201 help_index : 'db',
208 help_index : 'db',
202 handler : function (event) {
209 handler : function (event) {
203 var index = that.notebook.get_selected_index();
210 var index = that.notebook.get_selected_index();
204 if (index !== (that.notebook.ncells()-1) && index !== null) {
211 if (index !== (that.notebook.ncells()-1) && index !== null) {
205 that.notebook.select_next();
212 that.notebook.select_next();
206 that.notebook.focus_cell();
213 that.notebook.focus_cell();
207 }
214 }
208 return false;
215 return false;
209 }
216 }
210 },
217 },
211 'k' : {
218 'k' : {
212 help : 'select previous cell',
219 help : 'select previous cell',
213 help_index : 'dc',
220 help_index : 'dc',
214 handler : function (event) {
221 handler : function (event) {
215 var index = that.notebook.get_selected_index();
222 var index = that.notebook.get_selected_index();
216 if (index !== 0 && index !== null) {
223 if (index !== 0 && index !== null) {
217 that.notebook.select_prev();
224 that.notebook.select_prev();
218 that.notebook.focus_cell();
225 that.notebook.focus_cell();
219 }
226 }
220 return false;
227 return false;
221 }
228 }
222 },
229 },
223 'j' : {
230 'j' : {
224 help : 'select next cell',
231 help : 'select next cell',
225 help_index : 'dd',
232 help_index : 'dd',
226 handler : function (event) {
233 handler : function (event) {
227 var index = that.notebook.get_selected_index();
234 var index = that.notebook.get_selected_index();
228 if (index !== (that.notebook.ncells()-1) && index !== null) {
235 if (index !== (that.notebook.ncells()-1) && index !== null) {
229 that.notebook.select_next();
236 that.notebook.select_next();
230 that.notebook.focus_cell();
237 that.notebook.focus_cell();
231 }
238 }
232 return false;
239 return false;
233 }
240 }
234 },
241 },
235 'x' : {
242 'x' : {
236 help : 'cut cell',
243 help : 'cut cell',
237 help_index : 'ee',
244 help_index : 'ee',
238 handler : function (event) {
245 handler : function (event) {
239 that.notebook.cut_cell();
246 that.notebook.cut_cell();
240 return false;
247 return false;
241 }
248 }
242 },
249 },
243 'c' : {
250 'c' : {
244 help : 'copy cell',
251 help : 'copy cell',
245 help_index : 'ef',
252 help_index : 'ef',
246 handler : function (event) {
253 handler : function (event) {
247 that.notebook.copy_cell();
254 that.notebook.copy_cell();
248 return false;
255 return false;
249 }
256 }
250 },
257 },
251 'shift-v' : {
258 'shift-v' : {
252 help : 'paste cell above',
259 help : 'paste cell above',
253 help_index : 'eg',
260 help_index : 'eg',
254 handler : function (event) {
261 handler : function (event) {
255 that.notebook.paste_cell_above();
262 that.notebook.paste_cell_above();
256 return false;
263 return false;
257 }
264 }
258 },
265 },
259 'v' : {
266 'v' : {
260 help : 'paste cell below',
267 help : 'paste cell below',
261 help_index : 'eh',
268 help_index : 'eh',
262 handler : function (event) {
269 handler : function (event) {
263 that.notebook.paste_cell_below();
270 that.notebook.paste_cell_below();
264 return false;
271 return false;
265 }
272 }
266 },
273 },
267 'd' : {
274 'd' : {
268 help : 'delete cell (press twice)',
275 help : 'delete cell (press twice)',
269 help_index : 'ej',
276 help_index : 'ej',
270 count: 2,
277 count: 2,
271 handler : function (event) {
278 handler : function (event) {
272 that.notebook.delete_cell();
279 that.notebook.delete_cell();
273 return false;
280 return false;
274 }
281 }
275 },
282 },
276 'a' : {
283 'a' : {
277 help : 'insert cell above',
284 help : 'insert cell above',
278 help_index : 'ec',
285 help_index : 'ec',
279 handler : function (event) {
286 handler : function (event) {
280 that.notebook.insert_cell_above();
287 that.notebook.insert_cell_above();
281 that.notebook.select_prev();
288 that.notebook.select_prev();
282 that.notebook.focus_cell();
289 that.notebook.focus_cell();
283 return false;
290 return false;
284 }
291 }
285 },
292 },
286 'b' : {
293 'b' : {
287 help : 'insert cell below',
294 help : 'insert cell below',
288 help_index : 'ed',
295 help_index : 'ed',
289 handler : function (event) {
296 handler : function (event) {
290 that.notebook.insert_cell_below();
297 that.notebook.insert_cell_below();
291 that.notebook.select_next();
298 that.notebook.select_next();
292 that.notebook.focus_cell();
299 that.notebook.focus_cell();
293 return false;
300 return false;
294 }
301 }
295 },
302 },
296 'y' : {
303 'y' : {
297 help : 'to code',
304 help : 'to code',
298 help_index : 'ca',
305 help_index : 'ca',
299 handler : function (event) {
306 handler : function (event) {
300 that.notebook.to_code();
307 that.notebook.to_code();
301 return false;
308 return false;
302 }
309 }
303 },
310 },
304 'm' : {
311 'm' : {
305 help : 'to markdown',
312 help : 'to markdown',
306 help_index : 'cb',
313 help_index : 'cb',
307 handler : function (event) {
314 handler : function (event) {
308 that.notebook.to_markdown();
315 that.notebook.to_markdown();
309 return false;
316 return false;
310 }
317 }
311 },
318 },
312 'r' : {
319 'r' : {
313 help : 'to raw',
320 help : 'to raw',
314 help_index : 'cc',
321 help_index : 'cc',
315 handler : function (event) {
322 handler : function (event) {
316 that.notebook.to_raw();
323 that.notebook.to_raw();
317 return false;
324 return false;
318 }
325 }
319 },
326 },
320 '1' : {
327 '1' : {
321 help : 'to heading 1',
328 help : 'to heading 1',
322 help_index : 'cd',
329 help_index : 'cd',
323 handler : function (event) {
330 handler : function (event) {
324 that.notebook.to_heading(undefined, 1);
331 that.notebook.to_heading(undefined, 1);
325 return false;
332 return false;
326 }
333 }
327 },
334 },
328 '2' : {
335 '2' : {
329 help : 'to heading 2',
336 help : 'to heading 2',
330 help_index : 'ce',
337 help_index : 'ce',
331 handler : function (event) {
338 handler : function (event) {
332 that.notebook.to_heading(undefined, 2);
339 that.notebook.to_heading(undefined, 2);
333 return false;
340 return false;
334 }
341 }
335 },
342 },
336 '3' : {
343 '3' : {
337 help : 'to heading 3',
344 help : 'to heading 3',
338 help_index : 'cf',
345 help_index : 'cf',
339 handler : function (event) {
346 handler : function (event) {
340 that.notebook.to_heading(undefined, 3);
347 that.notebook.to_heading(undefined, 3);
341 return false;
348 return false;
342 }
349 }
343 },
350 },
344 '4' : {
351 '4' : {
345 help : 'to heading 4',
352 help : 'to heading 4',
346 help_index : 'cg',
353 help_index : 'cg',
347 handler : function (event) {
354 handler : function (event) {
348 that.notebook.to_heading(undefined, 4);
355 that.notebook.to_heading(undefined, 4);
349 return false;
356 return false;
350 }
357 }
351 },
358 },
352 '5' : {
359 '5' : {
353 help : 'to heading 5',
360 help : 'to heading 5',
354 help_index : 'ch',
361 help_index : 'ch',
355 handler : function (event) {
362 handler : function (event) {
356 that.notebook.to_heading(undefined, 5);
363 that.notebook.to_heading(undefined, 5);
357 return false;
364 return false;
358 }
365 }
359 },
366 },
360 '6' : {
367 '6' : {
361 help : 'to heading 6',
368 help : 'to heading 6',
362 help_index : 'ci',
369 help_index : 'ci',
363 handler : function (event) {
370 handler : function (event) {
364 that.notebook.to_heading(undefined, 6);
371 that.notebook.to_heading(undefined, 6);
365 return false;
372 return false;
366 }
373 }
367 },
374 },
368 'o' : {
375 'o' : {
369 help : 'toggle output',
376 help : 'toggle output',
370 help_index : 'gb',
377 help_index : 'gb',
371 handler : function (event) {
378 handler : function (event) {
372 that.notebook.toggle_output();
379 that.notebook.toggle_output();
373 return false;
380 return false;
374 }
381 }
375 },
382 },
376 'shift-o' : {
383 'shift-o' : {
377 help : 'toggle output scrolling',
384 help : 'toggle output scrolling',
378 help_index : 'gc',
385 help_index : 'gc',
379 handler : function (event) {
386 handler : function (event) {
380 that.notebook.toggle_output_scroll();
387 that.notebook.toggle_output_scroll();
381 return false;
388 return false;
382 }
389 }
383 },
390 },
384 's' : {
391 's' : {
385 help : 'save notebook',
392 help : 'save notebook',
386 help_index : 'fa',
393 help_index : 'fa',
387 handler : function (event) {
394 handler : function (event) {
388 that.notebook.save_checkpoint();
395 that.notebook.save_checkpoint();
389 return false;
396 return false;
390 }
397 }
391 },
398 },
392 'ctrl-j' : {
399 'ctrl-j' : {
393 help : 'move cell down',
400 help : 'move cell down',
394 help_index : 'eb',
401 help_index : 'eb',
395 handler : function (event) {
402 handler : function (event) {
396 that.notebook.move_cell_down();
403 that.notebook.move_cell_down();
397 return false;
404 return false;
398 }
405 }
399 },
406 },
400 'ctrl-k' : {
407 'ctrl-k' : {
401 help : 'move cell up',
408 help : 'move cell up',
402 help_index : 'ea',
409 help_index : 'ea',
403 handler : function (event) {
410 handler : function (event) {
404 that.notebook.move_cell_up();
411 that.notebook.move_cell_up();
405 return false;
412 return false;
406 }
413 }
407 },
414 },
408 'l' : {
415 'l' : {
409 help : 'toggle line numbers',
416 help : 'toggle line numbers',
410 help_index : 'ga',
417 help_index : 'ga',
411 handler : function (event) {
418 handler : function (event) {
412 that.notebook.cell_toggle_line_numbers();
419 that.notebook.cell_toggle_line_numbers();
413 return false;
420 return false;
414 }
421 }
415 },
422 },
416 'i' : {
423 'i' : {
417 help : 'interrupt kernel (press twice)',
424 help : 'interrupt kernel (press twice)',
418 help_index : 'ha',
425 help_index : 'ha',
419 count: 2,
426 count: 2,
420 handler : function (event) {
427 handler : function (event) {
421 that.notebook.kernel.interrupt();
428 that.notebook.kernel.interrupt();
422 return false;
429 return false;
423 }
430 }
424 },
431 },
425 '0' : {
432 '0' : {
426 help : 'restart kernel (press twice)',
433 help : 'restart kernel (press twice)',
427 help_index : 'hb',
434 help_index : 'hb',
428 count: 2,
435 count: 2,
429 handler : function (event) {
436 handler : function (event) {
430 that.notebook.restart_kernel();
437 that.notebook.restart_kernel();
431 return false;
438 return false;
432 }
439 }
433 },
440 },
434 'h' : {
441 'h' : {
435 help : 'keyboard shortcuts',
442 help : 'keyboard shortcuts',
436 help_index : 'ge',
443 help_index : 'ge',
437 handler : function (event) {
444 handler : function (event) {
438 that.quick_help.show_keyboard_shortcuts();
445 that.quick_help.show_keyboard_shortcuts();
439 return false;
446 return false;
440 }
447 }
441 },
448 },
442 'z' : {
449 'z' : {
443 help : 'undo last delete',
450 help : 'undo last delete',
444 help_index : 'ei',
451 help_index : 'ei',
445 handler : function (event) {
452 handler : function (event) {
446 that.notebook.undelete_cell();
453 that.notebook.undelete_cell();
447 return false;
454 return false;
448 }
455 }
449 },
456 },
450 'shift-m' : {
457 'shift-m' : {
451 help : 'merge cell below',
458 help : 'merge cell below',
452 help_index : 'ek',
459 help_index : 'ek',
453 handler : function (event) {
460 handler : function (event) {
454 that.notebook.merge_cell_below();
461 that.notebook.merge_cell_below();
455 return false;
462 return false;
456 }
463 }
457 },
464 },
458 'q' : {
465 'q' : {
459 help : 'close pager',
466 help : 'close pager',
460 help_index : 'gd',
467 help_index : 'gd',
461 handler : function (event) {
468 handler : function (event) {
462 that.pager.collapse();
469 that.pager.collapse();
463 return false;
470 return false;
464 }
471 }
465 },
472 },
466 };
473 };
467 };
474 };
468
475
469 KeyboardManager.prototype.bind_events = function () {
476 KeyboardManager.prototype.bind_events = function () {
470 var that = this;
477 var that = this;
471 $(document).keydown(function (event) {
478 $(document).keydown(function (event) {
472 return that.handle_keydown(event);
479 return that.handle_keydown(event);
473 });
480 });
474 };
481 };
475
482
476 KeyboardManager.prototype.handle_keydown = function (event) {
483 KeyboardManager.prototype.handle_keydown = function (event) {
477 var notebook = this.notebook;
484 var notebook = this.notebook;
478
485
479 if (event.which === keycodes.esc) {
486 if (event.which === keycodes.esc) {
480 // Intercept escape at highest level to avoid closing
487 // Intercept escape at highest level to avoid closing
481 // websocket connection with firefox
488 // websocket connection with firefox
482 event.preventDefault();
489 event.preventDefault();
483 }
490 }
484
491
485 if (!this.enabled) {
492 if (!this.enabled) {
486 if (event.which === keycodes.esc) {
493 if (event.which === keycodes.esc) {
487 // ESC
494 // ESC
488 notebook.command_mode();
495 notebook.command_mode();
489 return false;
496 return false;
490 }
497 }
491 return true;
498 return true;
492 }
499 }
493
500
494 if (this.mode === 'edit') {
501 if (this.mode === 'edit') {
495 return this.edit_shortcuts.call_handler(event);
502 return this.edit_shortcuts.call_handler(event);
496 } else if (this.mode === 'command') {
503 } else if (this.mode === 'command') {
497 return this.command_shortcuts.call_handler(event);
504 return this.command_shortcuts.call_handler(event);
498 }
505 }
499 return true;
506 return true;
500 };
507 };
501
508
502 KeyboardManager.prototype.edit_mode = function () {
509 KeyboardManager.prototype.edit_mode = function () {
503 this.last_mode = this.mode;
510 this.last_mode = this.mode;
504 this.mode = 'edit';
511 this.mode = 'edit';
505 };
512 };
506
513
507 KeyboardManager.prototype.command_mode = function () {
514 KeyboardManager.prototype.command_mode = function () {
508 this.last_mode = this.mode;
515 this.last_mode = this.mode;
509 this.mode = 'command';
516 this.mode = 'command';
510 };
517 };
511
518
512 KeyboardManager.prototype.enable = function () {
519 KeyboardManager.prototype.enable = function () {
513 this.enabled = true;
520 this.enabled = true;
514 };
521 };
515
522
516 KeyboardManager.prototype.disable = function () {
523 KeyboardManager.prototype.disable = function () {
517 this.enabled = false;
524 this.enabled = false;
518 };
525 };
519
526
520 KeyboardManager.prototype.register_events = function (e) {
527 KeyboardManager.prototype.register_events = function (e) {
521 var that = this;
528 var that = this;
522 var handle_focus = function () {
529 var handle_focus = function () {
523 that.disable();
530 that.disable();
524 };
531 };
525 var handle_blur = function () {
532 var handle_blur = function () {
526 that.enable();
533 that.enable();
527 };
534 };
528 e.on('focusin', handle_focus);
535 e.on('focusin', handle_focus);
529 e.on('focusout', handle_blur);
536 e.on('focusout', handle_blur);
530 // TODO: Very strange. The focusout event does not seem fire for the
537 // TODO: Very strange. The focusout event does not seem fire for the
531 // bootstrap textboxes on FF25&26... This works around that by
538 // bootstrap textboxes on FF25&26... This works around that by
532 // registering focus and blur events recursively on all inputs within
539 // registering focus and blur events recursively on all inputs within
533 // registered element.
540 // registered element.
534 e.find('input').blur(handle_blur);
541 e.find('input').blur(handle_blur);
535 e.on('DOMNodeInserted', function (event) {
542 e.on('DOMNodeInserted', function (event) {
536 var target = $(event.target);
543 var target = $(event.target);
537 if (target.is('input')) {
544 if (target.is('input')) {
538 target.blur(handle_blur);
545 target.blur(handle_blur);
539 } else {
546 } else {
540 target.find('input').blur(handle_blur);
547 target.find('input').blur(handle_blur);
541 }
548 }
542 });
549 });
543 // There are times (raw_input) where we remove the element from the DOM before
550 // 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,
551 // 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.
552 // 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
553 // is_focused must be used to check for the case where an element within
547 // the element being removed is focused.
554 // the element being removed is focused.
548 e.on('remove', function () {
555 e.on('remove', function () {
549 if (utils.is_focused(e[0])) {
556 if (utils.is_focused(e[0])) {
550 that.enable();
557 that.enable();
551 }
558 }
552 });
559 });
553 };
560 };
554
561
555 // For backwards compatability.
562 // For backwards compatability.
556 IPython.KeyboardManager = KeyboardManager;
563 IPython.KeyboardManager = KeyboardManager;
557
564
558 return {'KeyboardManager': KeyboardManager};
565 return {'KeyboardManager': KeyboardManager};
559 });
566 });
@@ -1,221 +1,229 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, options) {
12 var MainToolBar = function (selector, options) {
13 // Constructor
14 //
15 // Parameters:
16 // selector: string
17 // options: dictionary
18 // Dictionary of keyword arguments.
19 // events: $(Events) instance
20 // notebook: Notebook instance
13 toolbar.ToolBar.apply(this, arguments);
21 toolbar.ToolBar.apply(this, arguments);
14 this.events = options.events;
22 this.events = options.events;
15 this.notebook = options.notebook;
23 this.notebook = options.notebook;
16 this.construct();
24 this.construct();
17 this.add_celltype_list();
25 this.add_celltype_list();
18 this.add_celltoolbar_list();
26 this.add_celltoolbar_list();
19 this.bind_events();
27 this.bind_events();
20 };
28 };
21
29
22 MainToolBar.prototype = new toolbar.ToolBar();
30 MainToolBar.prototype = new toolbar.ToolBar();
23
31
24 MainToolBar.prototype.construct = function () {
32 MainToolBar.prototype.construct = function () {
25 var that = this;
33 var that = this;
26 this.add_buttons_group([
34 this.add_buttons_group([
27 {
35 {
28 id : 'save_b',
36 id : 'save_b',
29 label : 'Save and Checkpoint',
37 label : 'Save and Checkpoint',
30 icon : 'icon-save',
38 icon : 'icon-save',
31 callback : function () {
39 callback : function () {
32 that.notebook.save_checkpoint();
40 that.notebook.save_checkpoint();
33 }
41 }
34 }
42 }
35 ]);
43 ]);
36
44
37 this.add_buttons_group([
45 this.add_buttons_group([
38 {
46 {
39 id : 'insert_below_b',
47 id : 'insert_below_b',
40 label : 'Insert Cell Below',
48 label : 'Insert Cell Below',
41 icon : 'icon-plus-sign',
49 icon : 'icon-plus-sign',
42 callback : function () {
50 callback : function () {
43 that.notebook.insert_cell_below('code');
51 that.notebook.insert_cell_below('code');
44 that.notebook.select_next();
52 that.notebook.select_next();
45 that.notebook.focus_cell();
53 that.notebook.focus_cell();
46 }
54 }
47 }
55 }
48 ],'insert_above_below');
56 ],'insert_above_below');
49
57
50 this.add_buttons_group([
58 this.add_buttons_group([
51 {
59 {
52 id : 'cut_b',
60 id : 'cut_b',
53 label : 'Cut Cell',
61 label : 'Cut Cell',
54 icon : 'icon-cut',
62 icon : 'icon-cut',
55 callback : function () {
63 callback : function () {
56 that.notebook.cut_cell();
64 that.notebook.cut_cell();
57 }
65 }
58 },
66 },
59 {
67 {
60 id : 'copy_b',
68 id : 'copy_b',
61 label : 'Copy Cell',
69 label : 'Copy Cell',
62 icon : 'icon-copy',
70 icon : 'icon-copy',
63 callback : function () {
71 callback : function () {
64 that.notebook.copy_cell();
72 that.notebook.copy_cell();
65 }
73 }
66 },
74 },
67 {
75 {
68 id : 'paste_b',
76 id : 'paste_b',
69 label : 'Paste Cell Below',
77 label : 'Paste Cell Below',
70 icon : 'icon-paste',
78 icon : 'icon-paste',
71 callback : function () {
79 callback : function () {
72 that.notebook.paste_cell_below();
80 that.notebook.paste_cell_below();
73 }
81 }
74 }
82 }
75 ],'cut_copy_paste');
83 ],'cut_copy_paste');
76
84
77 this.add_buttons_group([
85 this.add_buttons_group([
78 {
86 {
79 id : 'move_up_b',
87 id : 'move_up_b',
80 label : 'Move Cell Up',
88 label : 'Move Cell Up',
81 icon : 'icon-arrow-up',
89 icon : 'icon-arrow-up',
82 callback : function () {
90 callback : function () {
83 that.notebook.move_cell_up();
91 that.notebook.move_cell_up();
84 }
92 }
85 },
93 },
86 {
94 {
87 id : 'move_down_b',
95 id : 'move_down_b',
88 label : 'Move Cell Down',
96 label : 'Move Cell Down',
89 icon : 'icon-arrow-down',
97 icon : 'icon-arrow-down',
90 callback : function () {
98 callback : function () {
91 that.notebook.move_cell_down();
99 that.notebook.move_cell_down();
92 }
100 }
93 }
101 }
94 ],'move_up_down');
102 ],'move_up_down');
95
103
96
104
97 this.add_buttons_group([
105 this.add_buttons_group([
98 {
106 {
99 id : 'run_b',
107 id : 'run_b',
100 label : 'Run Cell',
108 label : 'Run Cell',
101 icon : 'icon-play',
109 icon : 'icon-play',
102 callback : function () {
110 callback : function () {
103 // emulate default shift-enter behavior
111 // emulate default shift-enter behavior
104 that.notebook.execute_cell_and_select_below();
112 that.notebook.execute_cell_and_select_below();
105 }
113 }
106 },
114 },
107 {
115 {
108 id : 'interrupt_b',
116 id : 'interrupt_b',
109 label : 'Interrupt',
117 label : 'Interrupt',
110 icon : 'icon-stop',
118 icon : 'icon-stop',
111 callback : function () {
119 callback : function () {
112 that.notebook.session.interrupt_kernel();
120 that.notebook.session.interrupt_kernel();
113 }
121 }
114 },
122 },
115 {
123 {
116 id : 'repeat_b',
124 id : 'repeat_b',
117 label : 'Restart Kernel',
125 label : 'Restart Kernel',
118 icon : 'icon-repeat',
126 icon : 'icon-repeat',
119 callback : function () {
127 callback : function () {
120 that.notebook.restart_kernel();
128 that.notebook.restart_kernel();
121 }
129 }
122 }
130 }
123 ],'run_int');
131 ],'run_int');
124 };
132 };
125
133
126 MainToolBar.prototype.add_celltype_list = function () {
134 MainToolBar.prototype.add_celltype_list = function () {
127 this.element
135 this.element
128 .append($('<select/>')
136 .append($('<select/>')
129 .attr('id','cell_type')
137 .attr('id','cell_type')
130 .addClass('form-control select-xs')
138 .addClass('form-control select-xs')
131 // .addClass('ui-widget-content')
139 // .addClass('ui-widget-content')
132 .append($('<option/>').attr('value','code').text('Code'))
140 .append($('<option/>').attr('value','code').text('Code'))
133 .append($('<option/>').attr('value','markdown').text('Markdown'))
141 .append($('<option/>').attr('value','markdown').text('Markdown'))
134 .append($('<option/>').attr('value','raw').text('Raw NBConvert'))
142 .append($('<option/>').attr('value','raw').text('Raw NBConvert'))
135 .append($('<option/>').attr('value','heading1').text('Heading 1'))
143 .append($('<option/>').attr('value','heading1').text('Heading 1'))
136 .append($('<option/>').attr('value','heading2').text('Heading 2'))
144 .append($('<option/>').attr('value','heading2').text('Heading 2'))
137 .append($('<option/>').attr('value','heading3').text('Heading 3'))
145 .append($('<option/>').attr('value','heading3').text('Heading 3'))
138 .append($('<option/>').attr('value','heading4').text('Heading 4'))
146 .append($('<option/>').attr('value','heading4').text('Heading 4'))
139 .append($('<option/>').attr('value','heading5').text('Heading 5'))
147 .append($('<option/>').attr('value','heading5').text('Heading 5'))
140 .append($('<option/>').attr('value','heading6').text('Heading 6'))
148 .append($('<option/>').attr('value','heading6').text('Heading 6'))
141 );
149 );
142 };
150 };
143
151
144
152
145 MainToolBar.prototype.add_celltoolbar_list = function () {
153 MainToolBar.prototype.add_celltoolbar_list = function () {
146 var label = $('<span/>').addClass("navbar-text").text('Cell Toolbar:');
154 var label = $('<span/>').addClass("navbar-text").text('Cell Toolbar:');
147 var select = $('<select/>')
155 var select = $('<select/>')
148 // .addClass('ui-widget-content')
156 // .addClass('ui-widget-content')
149 .attr('id', 'ctb_select')
157 .attr('id', 'ctb_select')
150 .addClass('form-control select-xs')
158 .addClass('form-control select-xs')
151 .append($('<option/>').attr('value', '').text('None'));
159 .append($('<option/>').attr('value', '').text('None'));
152 this.element.append(label).append(select);
160 this.element.append(label).append(select);
153 select.change(function() {
161 select.change(function() {
154 var val = $(this).val();
162 var val = $(this).val();
155 if (val ==='') {
163 if (val ==='') {
156 celltoolbar.CellToolbar.global_hide();
164 celltoolbar.CellToolbar.global_hide();
157 delete that.notebook.metadata.celltoolbar;
165 delete that.notebook.metadata.celltoolbar;
158 } else {
166 } else {
159 celltoolbar.CellToolbar.global_show();
167 celltoolbar.CellToolbar.global_show();
160 celltoolbar.CellToolbar.activate_preset(val);
168 celltoolbar.CellToolbar.activate_preset(val);
161 that.notebook.metadata.celltoolbar = val;
169 that.notebook.metadata.celltoolbar = val;
162 }
170 }
163 });
171 });
164 // Setup the currently registered presets.
172 // Setup the currently registered presets.
165 var presets = celltoolbar.CellToolbar.list_presets();
173 var presets = celltoolbar.CellToolbar.list_presets();
166 for (var i=0; i<presets.length; i++) {
174 for (var i=0; i<presets.length; i++) {
167 var name = presets[i];
175 var name = presets[i];
168 select.append($('<option/>').attr('value', name).text(name));
176 select.append($('<option/>').attr('value', name).text(name));
169 }
177 }
170 // Setup future preset registrations.
178 // Setup future preset registrations.
171 this.events.on('preset_added.CellToolbar', function (event, data) {
179 this.events.on('preset_added.CellToolbar', function (event, data) {
172 var name = data.name;
180 var name = data.name;
173 select.append($('<option/>').attr('value', name).text(name));
181 select.append($('<option/>').attr('value', name).text(name));
174 });
182 });
175 // Update select value when a preset is activated.
183 // Update select value when a preset is activated.
176 this.events.on('preset_activated.CellToolbar', function (event, data) {
184 this.events.on('preset_activated.CellToolbar', function (event, data) {
177 if (select.val() !== data.name)
185 if (select.val() !== data.name)
178 select.val(data.name);
186 select.val(data.name);
179 });
187 });
180 };
188 };
181
189
182
190
183 MainToolBar.prototype.bind_events = function () {
191 MainToolBar.prototype.bind_events = function () {
184 var that = this;
192 var that = this;
185
193
186 this.element.find('#cell_type').change(function () {
194 this.element.find('#cell_type').change(function () {
187 var cell_type = $(this).val();
195 var cell_type = $(this).val();
188 if (cell_type === 'code') {
196 if (cell_type === 'code') {
189 that.notebook.to_code();
197 that.notebook.to_code();
190 } else if (cell_type === 'markdown') {
198 } else if (cell_type === 'markdown') {
191 that.notebook.to_markdown();
199 that.notebook.to_markdown();
192 } else if (cell_type === 'raw') {
200 } else if (cell_type === 'raw') {
193 that.notebook.to_raw();
201 that.notebook.to_raw();
194 } else if (cell_type === 'heading1') {
202 } else if (cell_type === 'heading1') {
195 that.notebook.to_heading(undefined, 1);
203 that.notebook.to_heading(undefined, 1);
196 } else if (cell_type === 'heading2') {
204 } else if (cell_type === 'heading2') {
197 that.notebook.to_heading(undefined, 2);
205 that.notebook.to_heading(undefined, 2);
198 } else if (cell_type === 'heading3') {
206 } else if (cell_type === 'heading3') {
199 that.notebook.to_heading(undefined, 3);
207 that.notebook.to_heading(undefined, 3);
200 } else if (cell_type === 'heading4') {
208 } else if (cell_type === 'heading4') {
201 that.notebook.to_heading(undefined, 4);
209 that.notebook.to_heading(undefined, 4);
202 } else if (cell_type === 'heading5') {
210 } else if (cell_type === 'heading5') {
203 that.notebook.to_heading(undefined, 5);
211 that.notebook.to_heading(undefined, 5);
204 } else if (cell_type === 'heading6') {
212 } else if (cell_type === 'heading6') {
205 that.notebook.to_heading(undefined, 6);
213 that.notebook.to_heading(undefined, 6);
206 }
214 }
207 });
215 });
208 this.events.on('selected_cell_type_changed.Notebook', function (event, data) {
216 this.events.on('selected_cell_type_changed.Notebook', function (event, data) {
209 if (data.cell_type === 'heading') {
217 if (data.cell_type === 'heading') {
210 that.element.find('#cell_type').val(data.cell_type+data.level);
218 that.element.find('#cell_type').val(data.cell_type+data.level);
211 } else {
219 } else {
212 that.element.find('#cell_type').val(data.cell_type);
220 that.element.find('#cell_type').val(data.cell_type);
213 }
221 }
214 });
222 });
215 };
223 };
216
224
217 // Backwards compatability.
225 // Backwards compatability.
218 IPython.MainToolBar = MainToolBar;
226 IPython.MainToolBar = MainToolBar;
219
227
220 return {'MainToolBar': MainToolBar};
228 return {'MainToolBar': MainToolBar};
221 });
229 });
@@ -1,243 +1,248 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 'jquery',
6 'jquery',
6 'base/js/utils',
7 'base/js/utils',
7 'base/js/dialog',
8 'base/js/dialog',
8 ], function($, utils, dialog) {
9 ], function(IPython, $, utils, dialog) {
9 "use strict";
10 "use strict";
10
11
11 var init = function () {
12 var init = function () {
12 if (window.MathJax) {
13 if (window.MathJax) {
13 // MathJax loaded
14 // MathJax loaded
14 MathJax.Hub.Config({
15 MathJax.Hub.Config({
15 tex2jax: {
16 tex2jax: {
16 inlineMath: [ ['$','$'], ["\\(","\\)"] ],
17 inlineMath: [ ['$','$'], ["\\(","\\)"] ],
17 displayMath: [ ['$$','$$'], ["\\[","\\]"] ],
18 displayMath: [ ['$$','$$'], ["\\[","\\]"] ],
18 processEscapes: true,
19 processEscapes: true,
19 processEnvironments: true
20 processEnvironments: true
20 },
21 },
21 // Center justify equations in code and markdown cells. Elsewhere
22 // Center justify equations in code and markdown cells. Elsewhere
22 // we use CSS to left justify single line equations in code cells.
23 // we use CSS to left justify single line equations in code cells.
23 displayAlign: 'center',
24 displayAlign: 'center',
24 "HTML-CSS": {
25 "HTML-CSS": {
25 styles: {'.MathJax_Display': {"margin": 0}},
26 styles: {'.MathJax_Display': {"margin": 0}},
26 linebreaks: { automatic: true }
27 linebreaks: { automatic: true }
27 }
28 }
28 });
29 });
29 MathJax.Hub.Configured();
30 MathJax.Hub.Configured();
30 } else if (window.mathjax_url !== "") {
31 } else if (window.mathjax_url !== "") {
31 // Don't have MathJax, but should. Show dialog.
32 // Don't have MathJax, but should. Show dialog.
32 var message = $('<div/>')
33 var message = $('<div/>')
33 .append(
34 .append(
34 $("<p/></p>").addClass('dialog').text(
35 $("<p/></p>").addClass('dialog').text(
35 "Math/LaTeX rendering will be disabled."
36 "Math/LaTeX rendering will be disabled."
36 )
37 )
37 ).append(
38 ).append(
38 $("<p></p>").addClass('dialog').text(
39 $("<p></p>").addClass('dialog').text(
39 "If you have administrative access to the notebook server and" +
40 "If you have administrative access to the notebook server and" +
40 " a working internet connection, you can install a local copy" +
41 " a working internet connection, you can install a local copy" +
41 " of MathJax for offline use with the following command on the server" +
42 " of MathJax for offline use with the following command on the server" +
42 " at a Python or IPython prompt:"
43 " at a Python or IPython prompt:"
43 )
44 )
44 ).append(
45 ).append(
45 $("<pre></pre>").addClass('dialog').text(
46 $("<pre></pre>").addClass('dialog').text(
46 ">>> from IPython.external import mathjax; mathjax.install_mathjax()"
47 ">>> from IPython.external import mathjax; mathjax.install_mathjax()"
47 )
48 )
48 ).append(
49 ).append(
49 $("<p></p>").addClass('dialog').text(
50 $("<p></p>").addClass('dialog').text(
50 "This will try to install MathJax into the IPython source directory."
51 "This will try to install MathJax into the IPython source directory."
51 )
52 )
52 ).append(
53 ).append(
53 $("<p></p>").addClass('dialog').text(
54 $("<p></p>").addClass('dialog').text(
54 "If IPython is installed to a location that requires" +
55 "If IPython is installed to a location that requires" +
55 " administrative privileges to write, you will need to make this call as" +
56 " administrative privileges to write, you will need to make this call as" +
56 " an administrator, via 'sudo'."
57 " an administrator, via 'sudo'."
57 )
58 )
58 ).append(
59 ).append(
59 $("<p></p>").addClass('dialog').text(
60 $("<p></p>").addClass('dialog').text(
60 "When you start the notebook server, you can instruct it to disable MathJax support altogether:"
61 "When you start the notebook server, you can instruct it to disable MathJax support altogether:"
61 )
62 )
62 ).append(
63 ).append(
63 $("<pre></pre>").addClass('dialog').text(
64 $("<pre></pre>").addClass('dialog').text(
64 "$ ipython notebook --no-mathjax"
65 "$ ipython notebook --no-mathjax"
65 )
66 )
66 ).append(
67 ).append(
67 $("<p></p>").addClass('dialog').text(
68 $("<p></p>").addClass('dialog').text(
68 "which will prevent this dialog from appearing."
69 "which will prevent this dialog from appearing."
69 )
70 )
70 );
71 );
71 dialog.modal({
72 dialog.modal({
72 title : "Failed to retrieve MathJax from '" + window.mathjax_url + "'",
73 title : "Failed to retrieve MathJax from '" + window.mathjax_url + "'",
73 body : message,
74 body : message,
74 buttons : {
75 buttons : {
75 OK : {class: "btn-danger"}
76 OK : {class: "btn-danger"}
76 }
77 }
77 });
78 });
78 }
79 }
79 };
80 };
80
81
81 // Some magic for deferring mathematical expressions to MathJax
82 // Some magic for deferring mathematical expressions to MathJax
82 // by hiding them from the Markdown parser.
83 // by hiding them from the Markdown parser.
83 // Some of the code here is adapted with permission from Davide Cervone
84 // Some of the code here is adapted with permission from Davide Cervone
84 // under the terms of the Apache2 license governing the MathJax project.
85 // under the terms of the Apache2 license governing the MathJax project.
85 // Other minor modifications are also due to StackExchange and are used with
86 // Other minor modifications are also due to StackExchange and are used with
86 // permission.
87 // permission.
87
88
88 var inline = "$"; // the inline math delimiter
89 var inline = "$"; // the inline math delimiter
89
90
90 // MATHSPLIT contains the pattern for math delimiters and special symbols
91 // MATHSPLIT contains the pattern for math delimiters and special symbols
91 // needed for searching for math in the text input.
92 // needed for searching for math in the text input.
92 var MATHSPLIT = /(\$\$?|\\(?:begin|end)\{[a-z]*\*?\}|\\[\\{}$]|[{}]|(?:\n\s*)+|@@\d+@@)/i;
93 var MATHSPLIT = /(\$\$?|\\(?:begin|end)\{[a-z]*\*?\}|\\[\\{}$]|[{}]|(?:\n\s*)+|@@\d+@@)/i;
93
94
94 // The math is in blocks i through j, so
95 // The math is in blocks i through j, so
95 // collect it into one block and clear the others.
96 // collect it into one block and clear the others.
96 // Replace &, <, and > by named entities.
97 // Replace &, <, and > by named entities.
97 // For IE, put <br> at the ends of comments since IE removes \n.
98 // For IE, put <br> at the ends of comments since IE removes \n.
98 // Clear the current math positions and store the index of the
99 // Clear the current math positions and store the index of the
99 // math, then push the math string onto the storage array.
100 // math, then push the math string onto the storage array.
100 // The preProcess function is called on all blocks if it has been passed in
101 // The preProcess function is called on all blocks if it has been passed in
101 var process_math = function (i, j, pre_process, math, blocks) {
102 var process_math = function (i, j, pre_process, math, blocks) {
102 var block = blocks.slice(i, j + 1).join("").replace(/&/g, "&amp;") // use HTML entity for &
103 var block = blocks.slice(i, j + 1).join("").replace(/&/g, "&amp;") // use HTML entity for &
103 .replace(/</g, "&lt;") // use HTML entity for <
104 .replace(/</g, "&lt;") // use HTML entity for <
104 .replace(/>/g, "&gt;") // use HTML entity for >
105 .replace(/>/g, "&gt;") // use HTML entity for >
105 ;
106 ;
106 if (utils.browser === 'msie') {
107 if (utils.browser === 'msie') {
107 block = block.replace(/(%[^\n]*)\n/g, "$1<br/>\n");
108 block = block.replace(/(%[^\n]*)\n/g, "$1<br/>\n");
108 }
109 }
109 while (j > i) {
110 while (j > i) {
110 blocks[j] = "";
111 blocks[j] = "";
111 j--;
112 j--;
112 }
113 }
113 blocks[i] = "@@" + math.length + "@@"; // replace the current block text with a unique tag to find later
114 blocks[i] = "@@" + math.length + "@@"; // replace the current block text with a unique tag to find later
114 if (pre_process){
115 if (pre_process){
115 block = pre_process(block);
116 block = pre_process(block);
116 }
117 }
117 math.push(block);
118 math.push(block);
118 return blocks;
119 return blocks;
119 };
120 };
120
121
121 // Break up the text into its component parts and search
122 // Break up the text into its component parts and search
122 // through them for math delimiters, braces, linebreaks, etc.
123 // through them for math delimiters, braces, linebreaks, etc.
123 // Math delimiters must match and braces must balance.
124 // Math delimiters must match and braces must balance.
124 // Don't allow math to pass through a double linebreak
125 // Don't allow math to pass through a double linebreak
125 // (which will be a paragraph).
126 // (which will be a paragraph).
126 //
127 //
127 var remove_math = function (text) {
128 var remove_math = function (text) {
128 var math = []; // stores math strings for later
129 var math = []; // stores math strings for later
129 var start;
130 var start;
130 var end;
131 var end;
131 var last;
132 var last;
132 var braces;
133 var braces;
133
134
134 // Except for extreme edge cases, this should catch precisely those pieces of the markdown
135 // Except for extreme edge cases, this should catch precisely those pieces of the markdown
135 // source that will later be turned into code spans. While MathJax will not TeXify code spans,
136 // source that will later be turned into code spans. While MathJax will not TeXify code spans,
136 // we still have to consider them at this point; the following issue has happened several times:
137 // we still have to consider them at this point; the following issue has happened several times:
137 //
138 //
138 // `$foo` and `$bar` are varibales. --> <code>$foo ` and `$bar</code> are variables.
139 // `$foo` and `$bar` are varibales. --> <code>$foo ` and `$bar</code> are variables.
139
140
140 var hasCodeSpans = /`/.test(text),
141 var hasCodeSpans = /`/.test(text),
141 de_tilde;
142 de_tilde;
142 if (hasCodeSpans) {
143 if (hasCodeSpans) {
143 text = text.replace(/~/g, "~T").replace(/(^|[^\\])(`+)([^\n]*?[^`\n])\2(?!`)/gm, function (wholematch) {
144 text = text.replace(/~/g, "~T").replace(/(^|[^\\])(`+)([^\n]*?[^`\n])\2(?!`)/gm, function (wholematch) {
144 return wholematch.replace(/\$/g, "~D");
145 return wholematch.replace(/\$/g, "~D");
145 });
146 });
146 de_tilde = function (text) {
147 de_tilde = function (text) {
147 return text.replace(/~([TD])/g, function (wholematch, character) {
148 return text.replace(/~([TD])/g, function (wholematch, character) {
148 return { T: "~", D: "$" }[character];
149 return { T: "~", D: "$" }[character];
149 });
150 });
150 };
151 };
151 } else {
152 } else {
152 de_tilde = function (text) { return text; };
153 de_tilde = function (text) { return text; };
153 }
154 }
154
155
155 var blocks = utils.regex_split(text.replace(/\r\n?/g, "\n"),MATHSPLIT);
156 var blocks = utils.regex_split(text.replace(/\r\n?/g, "\n"),MATHSPLIT);
156
157
157 for (var i = 1, m = blocks.length; i < m; i += 2) {
158 for (var i = 1, m = blocks.length; i < m; i += 2) {
158 var block = blocks[i];
159 var block = blocks[i];
159 if (block.charAt(0) === "@") {
160 if (block.charAt(0) === "@") {
160 //
161 //
161 // Things that look like our math markers will get
162 // Things that look like our math markers will get
162 // stored and then retrieved along with the math.
163 // stored and then retrieved along with the math.
163 //
164 //
164 blocks[i] = "@@" + math.length + "@@";
165 blocks[i] = "@@" + math.length + "@@";
165 math.push(block);
166 math.push(block);
166 }
167 }
167 else if (start) {
168 else if (start) {
168 //
169 //
169 // If we are in math, look for the end delimiter,
170 // If we are in math, look for the end delimiter,
170 // but don't go past double line breaks, and
171 // but don't go past double line breaks, and
171 // and balance braces within the math.
172 // and balance braces within the math.
172 //
173 //
173 if (block === end) {
174 if (block === end) {
174 if (braces) {
175 if (braces) {
175 last = i;
176 last = i;
176 }
177 }
177 else {
178 else {
178 blocks = process_math(start, i, de_tilde, math, blocks);
179 blocks = process_math(start, i, de_tilde, math, blocks);
179 start = null;
180 start = null;
180 end = null;
181 end = null;
181 last = null;
182 last = null;
182 }
183 }
183 }
184 }
184 else if (block.match(/\n.*\n/)) {
185 else if (block.match(/\n.*\n/)) {
185 if (last) {
186 if (last) {
186 i = last;
187 i = last;
187 blocks = process_math(start, i, de_tilde, math, blocks);
188 blocks = process_math(start, i, de_tilde, math, blocks);
188 }
189 }
189 start = null;
190 start = null;
190 end = null;
191 end = null;
191 last = null;
192 last = null;
192 braces = 0;
193 braces = 0;
193 }
194 }
194 else if (block === "{") {
195 else if (block === "{") {
195 braces++;
196 braces++;
196 }
197 }
197 else if (block === "}" && braces) {
198 else if (block === "}" && braces) {
198 braces--;
199 braces--;
199 }
200 }
200 }
201 }
201 else {
202 else {
202 //
203 //
203 // Look for math start delimiters and when
204 // Look for math start delimiters and when
204 // found, set up the end delimiter.
205 // found, set up the end delimiter.
205 //
206 //
206 if (block === inline || block === "$$") {
207 if (block === inline || block === "$$") {
207 start = i;
208 start = i;
208 end = block;
209 end = block;
209 braces = 0;
210 braces = 0;
210 }
211 }
211 else if (block.substr(1, 5) === "begin") {
212 else if (block.substr(1, 5) === "begin") {
212 start = i;
213 start = i;
213 end = "\\end" + block.substr(6);
214 end = "\\end" + block.substr(6);
214 braces = 0;
215 braces = 0;
215 }
216 }
216 }
217 }
217 }
218 }
218 if (last) {
219 if (last) {
219 blocks = process_math(start, last, de_tilde, math, blocks);
220 blocks = process_math(start, last, de_tilde, math, blocks);
220 start = null;
221 start = null;
221 end = null;
222 end = null;
222 last = null;
223 last = null;
223 }
224 }
224 return [de_tilde(blocks.join("")), math];
225 return [de_tilde(blocks.join("")), math];
225 };
226 };
226
227
227 //
228 //
228 // Put back the math strings that were saved,
229 // Put back the math strings that were saved,
229 // and clear the math array (no need to keep it around).
230 // and clear the math array (no need to keep it around).
230 //
231 //
231 var replace_math = function (text, math) {
232 var replace_math = function (text, math) {
232 text = text.replace(/@@(\d+)@@/g, function (match, n) {
233 text = text.replace(/@@(\d+)@@/g, function (match, n) {
233 return math[n];
234 return math[n];
234 });
235 });
235 return text;
236 return text;
236 };
237 };
237
238
238 return {
239 var mathjaxutils = {
239 init : init,
240 init : init,
240 remove_math : remove_math,
241 remove_math : remove_math,
241 replace_math : replace_math
242 replace_math : replace_math
242 };
243 };
244
245 IPython.mathjaxutils = mathjaxutils;
246
247 return mathjaxutils;
243 });
248 });
@@ -1,347 +1,348 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',
10 ], function(IPython, $, utils, tour) {
9 ], function(IPython, $, utils, tour) {
11 "use strict";
10 "use strict";
12
11
13 /**
14 * A MenuBar Class to generate the menubar of IPython notebook
15 * @Class MenuBar
16 *
17 * @constructor
18 *
19 *
20 * @param selector {string} selector for the menubar element in DOM
21 * @param {object} [options]
22 * @param [options.base_url] {String} String to use for the
23 * base project url. Default is to inspect
24 * $('body').data('baseUrl');
25 * does not support change for now is set through this option
26 */
27 var MenuBar = function (selector, options) {
12 var MenuBar = function (selector, options) {
13 // Constructor
14 //
15 // A MenuBar Class to generate the menubar of IPython notebook
16 //
17 // Parameters:
18 // selector: string
19 // options: dictionary
20 // Dictionary of keyword arguments.
21 // notebook: Notebook instance
22 // layout_manager: LayoutManager instance
23 // events: $(Events) instance
24 // save_widget: SaveWidget instance
25 // quick_help: QuickHelp instance
26 // base_url : string
27 // notebook_path : string
28 // notebook_name : string
28 options = options || {};
29 options = options || {};
29 this.base_url = options.base_url || utils.get_body_data("baseUrl");
30 this.base_url = options.base_url || utils.get_body_data("baseUrl");
30 this.selector = selector;
31 this.selector = selector;
31 this.notebook = options.notebook;
32 this.notebook = options.notebook;
32 this.layout_manager = options.layout_manager;
33 this.layout_manager = options.layout_manager;
33 this.events = options.events;
34 this.events = options.events;
34 this.save_widget = options.save_widget;
35 this.save_widget = options.save_widget;
35 this.quick_help = options.quick_help;
36 this.quick_help = options.quick_help;
36
37
37 try {
38 try {
38 this.tour = new tour.Tour(this.notebook, this.events);
39 this.tour = new tour.Tour(this.notebook, this.events);
39 } catch (e) {
40 } catch (e) {
40 this.tour = undefined;
41 this.tour = undefined;
41 console.log("Failed to instantiate Notebook Tour", e);
42 console.log("Failed to instantiate Notebook Tour", e);
42 }
43 }
43
44
44 if (this.selector !== undefined) {
45 if (this.selector !== undefined) {
45 this.element = $(selector);
46 this.element = $(selector);
46 this.style();
47 this.style();
47 this.bind_events();
48 this.bind_events();
48 }
49 }
49 };
50 };
50
51
51 MenuBar.prototype.style = function () {
52 MenuBar.prototype.style = function () {
52 var that = this;
53 var that = this;
53 this.element.addClass('border-box-sizing');
54 this.element.addClass('border-box-sizing');
54 this.element.find("li").click(function (event, ui) {
55 this.element.find("li").click(function (event, ui) {
55 // The selected cell loses focus when the menu is entered, so we
56 // The selected cell loses focus when the menu is entered, so we
56 // re-select it upon selection.
57 // re-select it upon selection.
57 var i = that.notebook.get_selected_index();
58 var i = that.notebook.get_selected_index();
58 that.notebook.select(i);
59 that.notebook.select(i);
59 }
60 }
60 );
61 );
61 };
62 };
62
63
63 MenuBar.prototype._nbconvert = function (format, download) {
64 MenuBar.prototype._nbconvert = function (format, download) {
64 download = download || false;
65 download = download || false;
65 var notebook_path = this.notebook.notebook_path;
66 var notebook_path = this.notebook.notebook_path;
66 var notebook_name = this.notebook.notebook_name;
67 var notebook_name = this.notebook.notebook_name;
67 if (this.notebook.dirty) {
68 if (this.notebook.dirty) {
68 this.notebook.save_notebook({async : false});
69 this.notebook.save_notebook({async : false});
69 }
70 }
70 var url = utils.url_join_encode(
71 var url = utils.url_join_encode(
71 this.base_url,
72 this.base_url,
72 'nbconvert',
73 'nbconvert',
73 format,
74 format,
74 notebook_path,
75 notebook_path,
75 notebook_name
76 notebook_name
76 ) + "?download=" + download.toString();
77 ) + "?download=" + download.toString();
77
78
78 window.open(url);
79 window.open(url);
79 };
80 };
80
81
81 MenuBar.prototype.bind_events = function () {
82 MenuBar.prototype.bind_events = function () {
82 // File
83 // File
83 var that = this;
84 var that = this;
84 this.element.find('#new_notebook').click(function () {
85 this.element.find('#new_notebook').click(function () {
85 that.notebook.new_notebook();
86 that.notebook.new_notebook();
86 });
87 });
87 this.element.find('#open_notebook').click(function () {
88 this.element.find('#open_notebook').click(function () {
88 window.open(utils.url_join_encode(
89 window.open(utils.url_join_encode(
89 that.notebook.base_url,
90 that.notebook.base_url,
90 'tree',
91 'tree',
91 that.notebook.notebook_path
92 that.notebook.notebook_path
92 ));
93 ));
93 });
94 });
94 this.element.find('#copy_notebook').click(function () {
95 this.element.find('#copy_notebook').click(function () {
95 that.notebook.copy_notebook();
96 that.notebook.copy_notebook();
96 return false;
97 return false;
97 });
98 });
98 this.element.find('#download_ipynb').click(function () {
99 this.element.find('#download_ipynb').click(function () {
99 var base_url = that.notebook.base_url;
100 var base_url = that.notebook.base_url;
100 var notebook_path = that.notebook.notebook_path;
101 var notebook_path = that.notebook.notebook_path;
101 var notebook_name = that.notebook.notebook_name;
102 var notebook_name = that.notebook.notebook_name;
102 if (that.notebook.dirty) {
103 if (that.notebook.dirty) {
103 that.notebook.save_notebook({async : false});
104 that.notebook.save_notebook({async : false});
104 }
105 }
105
106
106 var url = utils.url_join_encode(
107 var url = utils.url_join_encode(
107 base_url,
108 base_url,
108 'files',
109 'files',
109 notebook_path,
110 notebook_path,
110 notebook_name
111 notebook_name
111 );
112 );
112 window.location.assign(url);
113 window.location.assign(url);
113 });
114 });
114
115
115 this.element.find('#print_preview').click(function () {
116 this.element.find('#print_preview').click(function () {
116 that._nbconvert('html', false);
117 that._nbconvert('html', false);
117 });
118 });
118
119
119 this.element.find('#download_py').click(function () {
120 this.element.find('#download_py').click(function () {
120 that._nbconvert('python', true);
121 that._nbconvert('python', true);
121 });
122 });
122
123
123 this.element.find('#download_html').click(function () {
124 this.element.find('#download_html').click(function () {
124 that._nbconvert('html', true);
125 that._nbconvert('html', true);
125 });
126 });
126
127
127 this.element.find('#download_rst').click(function () {
128 this.element.find('#download_rst').click(function () {
128 that._nbconvert('rst', true);
129 that._nbconvert('rst', true);
129 });
130 });
130
131
131 this.element.find('#download_pdf').click(function () {
132 this.element.find('#download_pdf').click(function () {
132 that._nbconvert('pdf', true);
133 that._nbconvert('pdf', true);
133 });
134 });
134
135
135 this.element.find('#rename_notebook').click(function () {
136 this.element.find('#rename_notebook').click(function () {
136 that.save_widget.rename_notebook();
137 that.save_widget.rename_notebook();
137 });
138 });
138 this.element.find('#save_checkpoint').click(function () {
139 this.element.find('#save_checkpoint').click(function () {
139 that.notebook.save_checkpoint();
140 that.notebook.save_checkpoint();
140 });
141 });
141 this.element.find('#restore_checkpoint').click(function () {
142 this.element.find('#restore_checkpoint').click(function () {
142 });
143 });
143 this.element.find('#trust_notebook').click(function () {
144 this.element.find('#trust_notebook').click(function () {
144 that.notebook.trust_notebook();
145 that.notebook.trust_notebook();
145 });
146 });
146 this.events.on('trust_changed.Notebook', function (event, trusted) {
147 this.events.on('trust_changed.Notebook', function (event, trusted) {
147 if (trusted) {
148 if (trusted) {
148 that.element.find('#trust_notebook')
149 that.element.find('#trust_notebook')
149 .addClass("disabled")
150 .addClass("disabled")
150 .find("a").text("Trusted Notebook");
151 .find("a").text("Trusted Notebook");
151 } else {
152 } else {
152 that.element.find('#trust_notebook')
153 that.element.find('#trust_notebook')
153 .removeClass("disabled")
154 .removeClass("disabled")
154 .find("a").text("Trust Notebook");
155 .find("a").text("Trust Notebook");
155 }
156 }
156 });
157 });
157 this.element.find('#kill_and_exit').click(function () {
158 this.element.find('#kill_and_exit').click(function () {
158 that.notebook.session.delete();
159 that.notebook.session.delete();
159 setTimeout(function(){
160 setTimeout(function(){
160 // allow closing of new tabs in Chromium, impossible in FF
161 // allow closing of new tabs in Chromium, impossible in FF
161 window.open('', '_self', '');
162 window.open('', '_self', '');
162 window.close();
163 window.close();
163 }, 500);
164 }, 500);
164 });
165 });
165 // Edit
166 // Edit
166 this.element.find('#cut_cell').click(function () {
167 this.element.find('#cut_cell').click(function () {
167 that.notebook.cut_cell();
168 that.notebook.cut_cell();
168 });
169 });
169 this.element.find('#copy_cell').click(function () {
170 this.element.find('#copy_cell').click(function () {
170 that.notebook.copy_cell();
171 that.notebook.copy_cell();
171 });
172 });
172 this.element.find('#delete_cell').click(function () {
173 this.element.find('#delete_cell').click(function () {
173 that.notebook.delete_cell();
174 that.notebook.delete_cell();
174 });
175 });
175 this.element.find('#undelete_cell').click(function () {
176 this.element.find('#undelete_cell').click(function () {
176 that.notebook.undelete_cell();
177 that.notebook.undelete_cell();
177 });
178 });
178 this.element.find('#split_cell').click(function () {
179 this.element.find('#split_cell').click(function () {
179 that.notebook.split_cell();
180 that.notebook.split_cell();
180 });
181 });
181 this.element.find('#merge_cell_above').click(function () {
182 this.element.find('#merge_cell_above').click(function () {
182 that.notebook.merge_cell_above();
183 that.notebook.merge_cell_above();
183 });
184 });
184 this.element.find('#merge_cell_below').click(function () {
185 this.element.find('#merge_cell_below').click(function () {
185 that.notebook.merge_cell_below();
186 that.notebook.merge_cell_below();
186 });
187 });
187 this.element.find('#move_cell_up').click(function () {
188 this.element.find('#move_cell_up').click(function () {
188 that.notebook.move_cell_up();
189 that.notebook.move_cell_up();
189 });
190 });
190 this.element.find('#move_cell_down').click(function () {
191 this.element.find('#move_cell_down').click(function () {
191 that.notebook.move_cell_down();
192 that.notebook.move_cell_down();
192 });
193 });
193 this.element.find('#edit_nb_metadata').click(function () {
194 this.element.find('#edit_nb_metadata').click(function () {
194 that.notebook.edit_metadata();
195 that.notebook.edit_metadata();
195 });
196 });
196
197
197 // View
198 // View
198 this.element.find('#toggle_header').click(function () {
199 this.element.find('#toggle_header').click(function () {
199 $('div#header').toggle();
200 $('div#header').toggle();
200 that.layout_manager.do_resize();
201 that.layout_manager.do_resize();
201 });
202 });
202 this.element.find('#toggle_toolbar').click(function () {
203 this.element.find('#toggle_toolbar').click(function () {
203 $('div#maintoolbar').toggle();
204 $('div#maintoolbar').toggle();
204 that.layout_manager.do_resize();
205 that.layout_manager.do_resize();
205 });
206 });
206 // Insert
207 // Insert
207 this.element.find('#insert_cell_above').click(function () {
208 this.element.find('#insert_cell_above').click(function () {
208 that.notebook.insert_cell_above('code');
209 that.notebook.insert_cell_above('code');
209 that.notebook.select_prev();
210 that.notebook.select_prev();
210 });
211 });
211 this.element.find('#insert_cell_below').click(function () {
212 this.element.find('#insert_cell_below').click(function () {
212 that.notebook.insert_cell_below('code');
213 that.notebook.insert_cell_below('code');
213 that.notebook.select_next();
214 that.notebook.select_next();
214 });
215 });
215 // Cell
216 // Cell
216 this.element.find('#run_cell').click(function () {
217 this.element.find('#run_cell').click(function () {
217 that.notebook.execute_cell();
218 that.notebook.execute_cell();
218 });
219 });
219 this.element.find('#run_cell_select_below').click(function () {
220 this.element.find('#run_cell_select_below').click(function () {
220 that.notebook.execute_cell_and_select_below();
221 that.notebook.execute_cell_and_select_below();
221 });
222 });
222 this.element.find('#run_cell_insert_below').click(function () {
223 this.element.find('#run_cell_insert_below').click(function () {
223 that.notebook.execute_cell_and_insert_below();
224 that.notebook.execute_cell_and_insert_below();
224 });
225 });
225 this.element.find('#run_all_cells').click(function () {
226 this.element.find('#run_all_cells').click(function () {
226 that.notebook.execute_all_cells();
227 that.notebook.execute_all_cells();
227 });
228 });
228 this.element.find('#run_all_cells_above').click(function () {
229 this.element.find('#run_all_cells_above').click(function () {
229 that.notebook.execute_cells_above();
230 that.notebook.execute_cells_above();
230 });
231 });
231 this.element.find('#run_all_cells_below').click(function () {
232 this.element.find('#run_all_cells_below').click(function () {
232 that.notebook.execute_cells_below();
233 that.notebook.execute_cells_below();
233 });
234 });
234 this.element.find('#to_code').click(function () {
235 this.element.find('#to_code').click(function () {
235 that.notebook.to_code();
236 that.notebook.to_code();
236 });
237 });
237 this.element.find('#to_markdown').click(function () {
238 this.element.find('#to_markdown').click(function () {
238 that.notebook.to_markdown();
239 that.notebook.to_markdown();
239 });
240 });
240 this.element.find('#to_raw').click(function () {
241 this.element.find('#to_raw').click(function () {
241 that.notebook.to_raw();
242 that.notebook.to_raw();
242 });
243 });
243 this.element.find('#to_heading1').click(function () {
244 this.element.find('#to_heading1').click(function () {
244 that.notebook.to_heading(undefined, 1);
245 that.notebook.to_heading(undefined, 1);
245 });
246 });
246 this.element.find('#to_heading2').click(function () {
247 this.element.find('#to_heading2').click(function () {
247 that.notebook.to_heading(undefined, 2);
248 that.notebook.to_heading(undefined, 2);
248 });
249 });
249 this.element.find('#to_heading3').click(function () {
250 this.element.find('#to_heading3').click(function () {
250 that.notebook.to_heading(undefined, 3);
251 that.notebook.to_heading(undefined, 3);
251 });
252 });
252 this.element.find('#to_heading4').click(function () {
253 this.element.find('#to_heading4').click(function () {
253 that.notebook.to_heading(undefined, 4);
254 that.notebook.to_heading(undefined, 4);
254 });
255 });
255 this.element.find('#to_heading5').click(function () {
256 this.element.find('#to_heading5').click(function () {
256 that.notebook.to_heading(undefined, 5);
257 that.notebook.to_heading(undefined, 5);
257 });
258 });
258 this.element.find('#to_heading6').click(function () {
259 this.element.find('#to_heading6').click(function () {
259 that.notebook.to_heading(undefined, 6);
260 that.notebook.to_heading(undefined, 6);
260 });
261 });
261
262
262 this.element.find('#toggle_current_output').click(function () {
263 this.element.find('#toggle_current_output').click(function () {
263 that.notebook.toggle_output();
264 that.notebook.toggle_output();
264 });
265 });
265 this.element.find('#toggle_current_output_scroll').click(function () {
266 this.element.find('#toggle_current_output_scroll').click(function () {
266 that.notebook.toggle_output_scroll();
267 that.notebook.toggle_output_scroll();
267 });
268 });
268 this.element.find('#clear_current_output').click(function () {
269 this.element.find('#clear_current_output').click(function () {
269 that.notebook.clear_output();
270 that.notebook.clear_output();
270 });
271 });
271
272
272 this.element.find('#toggle_all_output').click(function () {
273 this.element.find('#toggle_all_output').click(function () {
273 that.notebook.toggle_all_output();
274 that.notebook.toggle_all_output();
274 });
275 });
275 this.element.find('#toggle_all_output_scroll').click(function () {
276 this.element.find('#toggle_all_output_scroll').click(function () {
276 that.notebook.toggle_all_output_scroll();
277 that.notebook.toggle_all_output_scroll();
277 });
278 });
278 this.element.find('#clear_all_output').click(function () {
279 this.element.find('#clear_all_output').click(function () {
279 that.notebook.clear_all_output();
280 that.notebook.clear_all_output();
280 });
281 });
281
282
282 // Kernel
283 // Kernel
283 this.element.find('#int_kernel').click(function () {
284 this.element.find('#int_kernel').click(function () {
284 that.notebook.session.interrupt_kernel();
285 that.notebook.session.interrupt_kernel();
285 });
286 });
286 this.element.find('#restart_kernel').click(function () {
287 this.element.find('#restart_kernel').click(function () {
287 that.notebook.restart_kernel();
288 that.notebook.restart_kernel();
288 });
289 });
289 // Help
290 // Help
290 if (this.tour) {
291 if (this.tour) {
291 this.element.find('#notebook_tour').click(function () {
292 this.element.find('#notebook_tour').click(function () {
292 that.tour.start();
293 that.tour.start();
293 });
294 });
294 } else {
295 } else {
295 this.element.find('#notebook_tour').addClass("disabled");
296 this.element.find('#notebook_tour').addClass("disabled");
296 }
297 }
297 this.element.find('#keyboard_shortcuts').click(function () {
298 this.element.find('#keyboard_shortcuts').click(function () {
298 that.quick_help.show_keyboard_shortcuts();
299 that.quick_help.show_keyboard_shortcuts();
299 });
300 });
300
301
301 this.update_restore_checkpoint(null);
302 this.update_restore_checkpoint(null);
302
303
303 this.events.on('checkpoints_listed.Notebook', function (event, data) {
304 this.events.on('checkpoints_listed.Notebook', function (event, data) {
304 that.update_restore_checkpoint(that.notebook.checkpoints);
305 that.update_restore_checkpoint(that.notebook.checkpoints);
305 });
306 });
306
307
307 this.events.on('checkpoint_created.Notebook', function (event, data) {
308 this.events.on('checkpoint_created.Notebook', function (event, data) {
308 that.update_restore_checkpoint(that.notebook.checkpoints);
309 that.update_restore_checkpoint(that.notebook.checkpoints);
309 });
310 });
310 };
311 };
311
312
312 MenuBar.prototype.update_restore_checkpoint = function(checkpoints) {
313 MenuBar.prototype.update_restore_checkpoint = function(checkpoints) {
313 var ul = this.element.find("#restore_checkpoint").find("ul");
314 var ul = this.element.find("#restore_checkpoint").find("ul");
314 ul.empty();
315 ul.empty();
315 if (!checkpoints || checkpoints.length === 0) {
316 if (!checkpoints || checkpoints.length === 0) {
316 ul.append(
317 ul.append(
317 $("<li/>")
318 $("<li/>")
318 .addClass("disabled")
319 .addClass("disabled")
319 .append(
320 .append(
320 $("<a/>")
321 $("<a/>")
321 .text("No checkpoints")
322 .text("No checkpoints")
322 )
323 )
323 );
324 );
324 return;
325 return;
325 }
326 }
326
327
327 var that = this;
328 var that = this;
328 checkpoints.map(function (checkpoint) {
329 checkpoints.map(function (checkpoint) {
329 var d = new Date(checkpoint.last_modified);
330 var d = new Date(checkpoint.last_modified);
330 ul.append(
331 ul.append(
331 $("<li/>").append(
332 $("<li/>").append(
332 $("<a/>")
333 $("<a/>")
333 .attr("href", "#")
334 .attr("href", "#")
334 .text(d.format("mmm dd HH:MM:ss"))
335 .text(d.format("mmm dd HH:MM:ss"))
335 .click(function () {
336 .click(function () {
336 that.notebook.restore_checkpoint_dialog(checkpoint);
337 that.notebook.restore_checkpoint_dialog(checkpoint);
337 })
338 })
338 )
339 )
339 );
340 );
340 });
341 });
341 };
342 };
342
343
343 // Backwards compatability.
344 // Backwards compatability.
344 IPython.MenuBar = MenuBar;
345 IPython.MenuBar = MenuBar;
345
346
346 return {'MenuBar': MenuBar};
347 return {'MenuBar': MenuBar};
347 });
348 });
@@ -1,2493 +1,2496 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 textcell,
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 /**
33 * A notebook contains and manages cells.
34 *
35 * @class Notebook
36 * @constructor
37 * @param {String} selector A jQuery selector for the notebook's DOM element
38 * @param {Object} [options] A config object
39 * @param {Object} [events] An events object
40 */
41 var Notebook = function (selector, options) {
32 var Notebook = function (selector, options) {
33 // Constructor
34 //
35 // A notebook contains and manages cells.
36 //
37 // Parameters:
38 // selector: string
39 // options: dictionary
40 // Dictionary of keyword arguments.
41 // events: $(Events) instance
42 // keyboard_manager: KeyboardManager instance
43 // save_widget: SaveWidget instance
44 // config: dictionary
45 // base_url : string
46 // notebook_path : string
47 // notebook_name : string
42 this.config = options.config || {};
48 this.config = options.config || {};
43 this.base_url = options.base_url;
49 this.base_url = options.base_url;
44 this.notebook_path = options.notebook_path;
50 this.notebook_path = options.notebook_path;
45 this.notebook_name = options.notebook_name;
51 this.notebook_name = options.notebook_name;
46 this.events = options.events;
52 this.events = options.events;
47 this.keyboard_manager = options.keyboard_manager;
53 this.keyboard_manager = options.keyboard_manager;
48 this.save_widget = options.save_widget;
54 this.save_widget = options.save_widget;
49 // TODO: This code smells (and the other `= this` line a couple lines down)
55 // TODO: This code smells (and the other `= this` line a couple lines down)
50 // We need a better way to deal with circular instance references.
56 // We need a better way to deal with circular instance references.
51 this.keyboard_manager.notebook = this;
57 this.keyboard_manager.notebook = this;
52 this.save_widget.notebook = this;
58 this.save_widget.notebook = this;
53
59
54 mathjaxutils.init();
60 mathjaxutils.init();
55
61
56 if (marked) {
62 if (marked) {
57 marked.setOptions({
63 marked.setOptions({
58 gfm : true,
64 gfm : true,
59 tables: true,
65 tables: true,
60 langPrefix: "language-",
66 langPrefix: "language-",
61 highlight: function(code, lang) {
67 highlight: function(code, lang) {
62 if (!lang) {
68 if (!lang) {
63 // no language, no highlight
69 // no language, no highlight
64 return code;
70 return code;
65 }
71 }
66 var highlighted;
72 var highlighted;
67 try {
73 try {
68 highlighted = hljs.highlight(lang, code, false);
74 highlighted = hljs.highlight(lang, code, false);
69 } catch(err) {
75 } catch(err) {
70 highlighted = hljs.highlightAuto(code);
76 highlighted = hljs.highlightAuto(code);
71 }
77 }
72 return highlighted.value;
78 return highlighted.value;
73 }
79 }
74 });
80 });
75 }
81 }
76
82
77 // Backwards compatability.
83 // Backwards compatability.
78 IPython.keyboard_manager = this.keyboard_manager;
84 IPython.keyboard_manager = this.keyboard_manager;
79 IPython.save_widget = this.save_widget;
85 IPython.save_widget = this.save_widget;
80 IPython.keyboard = this.keyboard;
86 IPython.keyboard = this.keyboard;
81
87
82 this.element = $(selector);
88 this.element = $(selector);
83 this.element.scroll();
89 this.element.scroll();
84 this.element.data("notebook", this);
90 this.element.data("notebook", this);
85 this.next_prompt_number = 1;
91 this.next_prompt_number = 1;
86 this.session = null;
92 this.session = null;
87 this.kernel = null;
93 this.kernel = null;
88 this.clipboard = null;
94 this.clipboard = null;
89 this.undelete_backup = null;
95 this.undelete_backup = null;
90 this.undelete_index = null;
96 this.undelete_index = null;
91 this.undelete_below = false;
97 this.undelete_below = false;
92 this.paste_enabled = false;
98 this.paste_enabled = false;
93 // It is important to start out in command mode to match the intial mode
99 // It is important to start out in command mode to match the intial mode
94 // of the KeyboardManager.
100 // of the KeyboardManager.
95 this.mode = 'command';
101 this.mode = 'command';
96 this.set_dirty(false);
102 this.set_dirty(false);
97 this.metadata = {};
103 this.metadata = {};
98 this._checkpoint_after_save = false;
104 this._checkpoint_after_save = false;
99 this.last_checkpoint = null;
105 this.last_checkpoint = null;
100 this.checkpoints = [];
106 this.checkpoints = [];
101 this.autosave_interval = 0;
107 this.autosave_interval = 0;
102 this.autosave_timer = null;
108 this.autosave_timer = null;
103 // autosave *at most* every two minutes
109 // autosave *at most* every two minutes
104 this.minimum_autosave_interval = 120000;
110 this.minimum_autosave_interval = 120000;
105 // single worksheet for now
111 // single worksheet for now
106 this.worksheet_metadata = {};
112 this.worksheet_metadata = {};
107 this.notebook_name_blacklist_re = /[\/\\:]/;
113 this.notebook_name_blacklist_re = /[\/\\:]/;
108 this.nbformat = 3; // Increment this when changing the nbformat
114 this.nbformat = 3; // Increment this when changing the nbformat
109 this.nbformat_minor = 0; // Increment this when changing the nbformat
115 this.nbformat_minor = 0; // Increment this when changing the nbformat
110 this.style();
116 this.style();
111 this.create_elements();
117 this.create_elements();
112 this.bind_events();
118 this.bind_events();
113 this.save_notebook = function() { // don't allow save until notebook_loaded
119 this.save_notebook = function() { // don't allow save until notebook_loaded
114 this.save_notebook_error(null, null, "Load failed, save is disabled");
120 this.save_notebook_error(null, null, "Load failed, save is disabled");
115 };
121 };
116 };
122 };
117
123
118 /**
124 /**
119 * Tweak the notebook's CSS style.
125 * Tweak the notebook's CSS style.
120 *
126 *
121 * @method style
127 * @method style
122 */
128 */
123 Notebook.prototype.style = function () {
129 Notebook.prototype.style = function () {
124 $('div#notebook').addClass('border-box-sizing');
130 $('div#notebook').addClass('border-box-sizing');
125 };
131 };
126
132
127 /**
133 /**
128 * Create an HTML and CSS representation of the notebook.
134 * Create an HTML and CSS representation of the notebook.
129 *
135 *
130 * @method create_elements
136 * @method create_elements
131 */
137 */
132 Notebook.prototype.create_elements = function () {
138 Notebook.prototype.create_elements = function () {
133 var that = this;
139 var that = this;
134 this.element.attr('tabindex','-1');
140 this.element.attr('tabindex','-1');
135 this.container = $("<div/>").addClass("container").attr("id", "notebook-container");
141 this.container = $("<div/>").addClass("container").attr("id", "notebook-container");
136 // We add this end_space div to the end of the notebook div to:
142 // We add this end_space div to the end of the notebook div to:
137 // i) provide a margin between the last cell and the end of the notebook
143 // i) provide a margin between the last cell and the end of the notebook
138 // ii) to prevent the div from scrolling up when the last cell is being
144 // ii) to prevent the div from scrolling up when the last cell is being
139 // edited, but is too low on the page, which browsers will do automatically.
145 // edited, but is too low on the page, which browsers will do automatically.
140 var end_space = $('<div/>').addClass('end_space');
146 var end_space = $('<div/>').addClass('end_space');
141 end_space.dblclick(function (e) {
147 end_space.dblclick(function (e) {
142 var ncells = that.ncells();
148 var ncells = that.ncells();
143 that.insert_cell_below('code',ncells-1);
149 that.insert_cell_below('code',ncells-1);
144 });
150 });
145 this.element.append(this.container);
151 this.element.append(this.container);
146 this.container.append(end_space);
152 this.container.append(end_space);
147 };
153 };
148
154
149 /**
155 /**
150 * Bind JavaScript events: key presses and custom IPython events.
156 * Bind JavaScript events: key presses and custom IPython events.
151 *
157 *
152 * @method bind_events
158 * @method bind_events
153 */
159 */
154 Notebook.prototype.bind_events = function () {
160 Notebook.prototype.bind_events = function () {
155 var that = this;
161 var that = this;
156
162
157 this.events.on('set_next_input.Notebook', function (event, data) {
163 this.events.on('set_next_input.Notebook', function (event, data) {
158 var index = that.find_cell_index(data.cell);
164 var index = that.find_cell_index(data.cell);
159 var new_cell = that.insert_cell_below('code',index);
165 var new_cell = that.insert_cell_below('code',index);
160 new_cell.set_text(data.text);
166 new_cell.set_text(data.text);
161 that.dirty = true;
167 that.dirty = true;
162 });
168 });
163
169
164 this.events.on('set_dirty.Notebook', function (event, data) {
170 this.events.on('set_dirty.Notebook', function (event, data) {
165 that.dirty = data.value;
171 that.dirty = data.value;
166 });
172 });
167
173
168 this.events.on('trust_changed.Notebook', function (event, data) {
174 this.events.on('trust_changed.Notebook', function (event, data) {
169 that.trusted = data.value;
175 that.trusted = data.value;
170 });
176 });
171
177
172 this.events.on('select.Cell', function (event, data) {
178 this.events.on('select.Cell', function (event, data) {
173 var index = that.find_cell_index(data.cell);
179 var index = that.find_cell_index(data.cell);
174 that.select(index);
180 that.select(index);
175 });
181 });
176
182
177 this.events.on('edit_mode.Cell', function (event, data) {
183 this.events.on('edit_mode.Cell', function (event, data) {
178 that.handle_edit_mode(data.cell);
184 that.handle_edit_mode(data.cell);
179 });
185 });
180
186
181 this.events.on('command_mode.Cell', function (event, data) {
187 this.events.on('command_mode.Cell', function (event, data) {
182 that.handle_command_mode(data.cell);
188 that.handle_command_mode(data.cell);
183 });
189 });
184
190
185 this.events.on('status_autorestarting.Kernel', function () {
191 this.events.on('status_autorestarting.Kernel', function () {
186 dialog.modal({
192 dialog.modal({
187 title: "Kernel Restarting",
193 title: "Kernel Restarting",
188 body: "The kernel appears to have died. It will restart automatically.",
194 body: "The kernel appears to have died. It will restart automatically.",
189 buttons: {
195 buttons: {
190 OK : {
196 OK : {
191 class : "btn-primary"
197 class : "btn-primary"
192 }
198 }
193 }
199 }
194 });
200 });
195 });
201 });
196
202
197 var collapse_time = function (time) {
203 var collapse_time = function (time) {
198 var app_height = $('#ipython-main-app').height(); // content height
204 var app_height = $('#ipython-main-app').height(); // content height
199 var splitter_height = $('div#pager_splitter').outerHeight(true);
205 var splitter_height = $('div#pager_splitter').outerHeight(true);
200 var new_height = app_height - splitter_height;
206 var new_height = app_height - splitter_height;
201 that.element.animate({height : new_height + 'px'}, time);
207 that.element.animate({height : new_height + 'px'}, time);
202 };
208 };
203
209
204 this.element.bind('collapse_pager', function (event, extrap) {
210 this.element.bind('collapse_pager', function (event, extrap) {
205 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
211 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
206 collapse_time(time);
212 collapse_time(time);
207 });
213 });
208
214
209 var expand_time = function (time) {
215 var expand_time = function (time) {
210 var app_height = $('#ipython-main-app').height(); // content height
216 var app_height = $('#ipython-main-app').height(); // content height
211 var splitter_height = $('div#pager_splitter').outerHeight(true);
217 var splitter_height = $('div#pager_splitter').outerHeight(true);
212 var pager_height = $('div#pager').outerHeight(true);
218 var pager_height = $('div#pager').outerHeight(true);
213 var new_height = app_height - pager_height - splitter_height;
219 var new_height = app_height - pager_height - splitter_height;
214 that.element.animate({height : new_height + 'px'}, time);
220 that.element.animate({height : new_height + 'px'}, time);
215 };
221 };
216
222
217 this.element.bind('expand_pager', function (event, extrap) {
223 this.element.bind('expand_pager', function (event, extrap) {
218 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
224 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
219 expand_time(time);
225 expand_time(time);
220 });
226 });
221
227
222 // Firefox 22 broke $(window).on("beforeunload")
228 // Firefox 22 broke $(window).on("beforeunload")
223 // I'm not sure why or how.
229 // I'm not sure why or how.
224 window.onbeforeunload = function (e) {
230 window.onbeforeunload = function (e) {
225 // TODO: Make killing the kernel configurable.
231 // TODO: Make killing the kernel configurable.
226 var kill_kernel = false;
232 var kill_kernel = false;
227 if (kill_kernel) {
233 if (kill_kernel) {
228 that.session.kill_kernel();
234 that.session.kill_kernel();
229 }
235 }
230 // if we are autosaving, trigger an autosave on nav-away.
236 // if we are autosaving, trigger an autosave on nav-away.
231 // still warn, because if we don't the autosave may fail.
237 // still warn, because if we don't the autosave may fail.
232 if (that.dirty) {
238 if (that.dirty) {
233 if ( that.autosave_interval ) {
239 if ( that.autosave_interval ) {
234 // schedule autosave in a timeout
240 // schedule autosave in a timeout
235 // this gives you a chance to forcefully discard changes
241 // this gives you a chance to forcefully discard changes
236 // by reloading the page if you *really* want to.
242 // by reloading the page if you *really* want to.
237 // the timer doesn't start until you *dismiss* the dialog.
243 // the timer doesn't start until you *dismiss* the dialog.
238 setTimeout(function () {
244 setTimeout(function () {
239 if (that.dirty) {
245 if (that.dirty) {
240 that.save_notebook();
246 that.save_notebook();
241 }
247 }
242 }, 1000);
248 }, 1000);
243 return "Autosave in progress, latest changes may be lost.";
249 return "Autosave in progress, latest changes may be lost.";
244 } else {
250 } else {
245 return "Unsaved changes will be lost.";
251 return "Unsaved changes will be lost.";
246 }
252 }
247 }
253 }
248 // Null is the *only* return value that will make the browser not
254 // Null is the *only* return value that will make the browser not
249 // pop up the "don't leave" dialog.
255 // pop up the "don't leave" dialog.
250 return null;
256 return null;
251 };
257 };
252 };
258 };
253
259
254 /**
260 /**
255 * Set the dirty flag, and trigger the set_dirty.Notebook event
261 * Set the dirty flag, and trigger the set_dirty.Notebook event
256 *
262 *
257 * @method set_dirty
263 * @method set_dirty
258 */
264 */
259 Notebook.prototype.set_dirty = function (value) {
265 Notebook.prototype.set_dirty = function (value) {
260 if (value === undefined) {
266 if (value === undefined) {
261 value = true;
267 value = true;
262 }
268 }
263 if (this.dirty == value) {
269 if (this.dirty == value) {
264 return;
270 return;
265 }
271 }
266 this.events.trigger('set_dirty.Notebook', {value: value});
272 this.events.trigger('set_dirty.Notebook', {value: value});
267 };
273 };
268
274
269 /**
275 /**
270 * Scroll the top of the page to a given cell.
276 * Scroll the top of the page to a given cell.
271 *
277 *
272 * @method scroll_to_cell
278 * @method scroll_to_cell
273 * @param {Number} cell_number An index of the cell to view
279 * @param {Number} cell_number An index of the cell to view
274 * @param {Number} time Animation time in milliseconds
280 * @param {Number} time Animation time in milliseconds
275 * @return {Number} Pixel offset from the top of the container
281 * @return {Number} Pixel offset from the top of the container
276 */
282 */
277 Notebook.prototype.scroll_to_cell = function (cell_number, time) {
283 Notebook.prototype.scroll_to_cell = function (cell_number, time) {
278 var cells = this.get_cells();
284 var cells = this.get_cells();
279 time = time || 0;
285 time = time || 0;
280 cell_number = Math.min(cells.length-1,cell_number);
286 cell_number = Math.min(cells.length-1,cell_number);
281 cell_number = Math.max(0 ,cell_number);
287 cell_number = Math.max(0 ,cell_number);
282 var scroll_value = cells[cell_number].element.position().top-cells[0].element.position().top ;
288 var scroll_value = cells[cell_number].element.position().top-cells[0].element.position().top ;
283 this.element.animate({scrollTop:scroll_value}, time);
289 this.element.animate({scrollTop:scroll_value}, time);
284 return scroll_value;
290 return scroll_value;
285 };
291 };
286
292
287 /**
293 /**
288 * Scroll to the bottom of the page.
294 * Scroll to the bottom of the page.
289 *
295 *
290 * @method scroll_to_bottom
296 * @method scroll_to_bottom
291 */
297 */
292 Notebook.prototype.scroll_to_bottom = function () {
298 Notebook.prototype.scroll_to_bottom = function () {
293 this.element.animate({scrollTop:this.element.get(0).scrollHeight}, 0);
299 this.element.animate({scrollTop:this.element.get(0).scrollHeight}, 0);
294 };
300 };
295
301
296 /**
302 /**
297 * Scroll to the top of the page.
303 * Scroll to the top of the page.
298 *
304 *
299 * @method scroll_to_top
305 * @method scroll_to_top
300 */
306 */
301 Notebook.prototype.scroll_to_top = function () {
307 Notebook.prototype.scroll_to_top = function () {
302 this.element.animate({scrollTop:0}, 0);
308 this.element.animate({scrollTop:0}, 0);
303 };
309 };
304
310
305 // Edit Notebook metadata
311 // Edit Notebook metadata
306
312
307 Notebook.prototype.edit_metadata = function () {
313 Notebook.prototype.edit_metadata = function () {
308 var that = this;
314 var that = this;
309 dialog.edit_metadata(this.metadata, function (md) {
315 dialog.edit_metadata(this.metadata, function (md) {
310 that.metadata = md;
316 that.metadata = md;
311 }, 'Notebook');
317 }, 'Notebook');
312 };
318 };
313
319
314 // Cell indexing, retrieval, etc.
320 // Cell indexing, retrieval, etc.
315
321
316 /**
322 /**
317 * Get all cell elements in the notebook.
323 * Get all cell elements in the notebook.
318 *
324 *
319 * @method get_cell_elements
325 * @method get_cell_elements
320 * @return {jQuery} A selector of all cell elements
326 * @return {jQuery} A selector of all cell elements
321 */
327 */
322 Notebook.prototype.get_cell_elements = function () {
328 Notebook.prototype.get_cell_elements = function () {
323 return this.container.children("div.cell");
329 return this.container.children("div.cell");
324 };
330 };
325
331
326 /**
332 /**
327 * Get a particular cell element.
333 * Get a particular cell element.
328 *
334 *
329 * @method get_cell_element
335 * @method get_cell_element
330 * @param {Number} index An index of a cell to select
336 * @param {Number} index An index of a cell to select
331 * @return {jQuery} A selector of the given cell.
337 * @return {jQuery} A selector of the given cell.
332 */
338 */
333 Notebook.prototype.get_cell_element = function (index) {
339 Notebook.prototype.get_cell_element = function (index) {
334 var result = null;
340 var result = null;
335 var e = this.get_cell_elements().eq(index);
341 var e = this.get_cell_elements().eq(index);
336 if (e.length !== 0) {
342 if (e.length !== 0) {
337 result = e;
343 result = e;
338 }
344 }
339 return result;
345 return result;
340 };
346 };
341
347
342 /**
348 /**
343 * Try to get a particular cell by msg_id.
349 * Try to get a particular cell by msg_id.
344 *
350 *
345 * @method get_msg_cell
351 * @method get_msg_cell
346 * @param {String} msg_id A message UUID
352 * @param {String} msg_id A message UUID
347 * @return {Cell} Cell or null if no cell was found.
353 * @return {Cell} Cell or null if no cell was found.
348 */
354 */
349 Notebook.prototype.get_msg_cell = function (msg_id) {
355 Notebook.prototype.get_msg_cell = function (msg_id) {
350 return codecell.CodeCell.msg_cells[msg_id] || null;
356 return codecell.CodeCell.msg_cells[msg_id] || null;
351 };
357 };
352
358
353 /**
359 /**
354 * Count the cells in this notebook.
360 * Count the cells in this notebook.
355 *
361 *
356 * @method ncells
362 * @method ncells
357 * @return {Number} The number of cells in this notebook
363 * @return {Number} The number of cells in this notebook
358 */
364 */
359 Notebook.prototype.ncells = function () {
365 Notebook.prototype.ncells = function () {
360 return this.get_cell_elements().length;
366 return this.get_cell_elements().length;
361 };
367 };
362
368
363 /**
369 /**
364 * Get all Cell objects in this notebook.
370 * Get all Cell objects in this notebook.
365 *
371 *
366 * @method get_cells
372 * @method get_cells
367 * @return {Array} This notebook's Cell objects
373 * @return {Array} This notebook's Cell objects
368 */
374 */
369 // TODO: we are often calling cells as cells()[i], which we should optimize
375 // TODO: we are often calling cells as cells()[i], which we should optimize
370 // to cells(i) or a new method.
376 // to cells(i) or a new method.
371 Notebook.prototype.get_cells = function () {
377 Notebook.prototype.get_cells = function () {
372 return this.get_cell_elements().toArray().map(function (e) {
378 return this.get_cell_elements().toArray().map(function (e) {
373 return $(e).data("cell");
379 return $(e).data("cell");
374 });
380 });
375 };
381 };
376
382
377 /**
383 /**
378 * Get a Cell object from this notebook.
384 * Get a Cell object from this notebook.
379 *
385 *
380 * @method get_cell
386 * @method get_cell
381 * @param {Number} index An index of a cell to retrieve
387 * @param {Number} index An index of a cell to retrieve
382 * @return {Cell} A particular cell
388 * @return {Cell} A particular cell
383 */
389 */
384 Notebook.prototype.get_cell = function (index) {
390 Notebook.prototype.get_cell = function (index) {
385 var result = null;
391 var result = null;
386 var ce = this.get_cell_element(index);
392 var ce = this.get_cell_element(index);
387 if (ce !== null) {
393 if (ce !== null) {
388 result = ce.data('cell');
394 result = ce.data('cell');
389 }
395 }
390 return result;
396 return result;
391 };
397 };
392
398
393 /**
399 /**
394 * Get the cell below a given cell.
400 * Get the cell below a given cell.
395 *
401 *
396 * @method get_next_cell
402 * @method get_next_cell
397 * @param {Cell} cell The provided cell
403 * @param {Cell} cell The provided cell
398 * @return {Cell} The next cell
404 * @return {Cell} The next cell
399 */
405 */
400 Notebook.prototype.get_next_cell = function (cell) {
406 Notebook.prototype.get_next_cell = function (cell) {
401 var result = null;
407 var result = null;
402 var index = this.find_cell_index(cell);
408 var index = this.find_cell_index(cell);
403 if (this.is_valid_cell_index(index+1)) {
409 if (this.is_valid_cell_index(index+1)) {
404 result = this.get_cell(index+1);
410 result = this.get_cell(index+1);
405 }
411 }
406 return result;
412 return result;
407 };
413 };
408
414
409 /**
415 /**
410 * Get the cell above a given cell.
416 * Get the cell above a given cell.
411 *
417 *
412 * @method get_prev_cell
418 * @method get_prev_cell
413 * @param {Cell} cell The provided cell
419 * @param {Cell} cell The provided cell
414 * @return {Cell} The previous cell
420 * @return {Cell} The previous cell
415 */
421 */
416 Notebook.prototype.get_prev_cell = function (cell) {
422 Notebook.prototype.get_prev_cell = function (cell) {
417 // TODO: off-by-one
423 // TODO: off-by-one
418 // nb.get_prev_cell(nb.get_cell(1)) is null
424 // nb.get_prev_cell(nb.get_cell(1)) is null
419 var result = null;
425 var result = null;
420 var index = this.find_cell_index(cell);
426 var index = this.find_cell_index(cell);
421 if (index !== null && index > 1) {
427 if (index !== null && index > 1) {
422 result = this.get_cell(index-1);
428 result = this.get_cell(index-1);
423 }
429 }
424 return result;
430 return result;
425 };
431 };
426
432
427 /**
433 /**
428 * Get the numeric index of a given cell.
434 * Get the numeric index of a given cell.
429 *
435 *
430 * @method find_cell_index
436 * @method find_cell_index
431 * @param {Cell} cell The provided cell
437 * @param {Cell} cell The provided cell
432 * @return {Number} The cell's numeric index
438 * @return {Number} The cell's numeric index
433 */
439 */
434 Notebook.prototype.find_cell_index = function (cell) {
440 Notebook.prototype.find_cell_index = function (cell) {
435 var result = null;
441 var result = null;
436 this.get_cell_elements().filter(function (index) {
442 this.get_cell_elements().filter(function (index) {
437 if ($(this).data("cell") === cell) {
443 if ($(this).data("cell") === cell) {
438 result = index;
444 result = index;
439 }
445 }
440 });
446 });
441 return result;
447 return result;
442 };
448 };
443
449
444 /**
450 /**
445 * Get a given index , or the selected index if none is provided.
451 * Get a given index , or the selected index if none is provided.
446 *
452 *
447 * @method index_or_selected
453 * @method index_or_selected
448 * @param {Number} index A cell's index
454 * @param {Number} index A cell's index
449 * @return {Number} The given index, or selected index if none is provided.
455 * @return {Number} The given index, or selected index if none is provided.
450 */
456 */
451 Notebook.prototype.index_or_selected = function (index) {
457 Notebook.prototype.index_or_selected = function (index) {
452 var i;
458 var i;
453 if (index === undefined || index === null) {
459 if (index === undefined || index === null) {
454 i = this.get_selected_index();
460 i = this.get_selected_index();
455 if (i === null) {
461 if (i === null) {
456 i = 0;
462 i = 0;
457 }
463 }
458 } else {
464 } else {
459 i = index;
465 i = index;
460 }
466 }
461 return i;
467 return i;
462 };
468 };
463
469
464 /**
470 /**
465 * Get the currently selected cell.
471 * Get the currently selected cell.
466 * @method get_selected_cell
472 * @method get_selected_cell
467 * @return {Cell} The selected cell
473 * @return {Cell} The selected cell
468 */
474 */
469 Notebook.prototype.get_selected_cell = function () {
475 Notebook.prototype.get_selected_cell = function () {
470 var index = this.get_selected_index();
476 var index = this.get_selected_index();
471 return this.get_cell(index);
477 return this.get_cell(index);
472 };
478 };
473
479
474 /**
480 /**
475 * Check whether a cell index is valid.
481 * Check whether a cell index is valid.
476 *
482 *
477 * @method is_valid_cell_index
483 * @method is_valid_cell_index
478 * @param {Number} index A cell index
484 * @param {Number} index A cell index
479 * @return True if the index is valid, false otherwise
485 * @return True if the index is valid, false otherwise
480 */
486 */
481 Notebook.prototype.is_valid_cell_index = function (index) {
487 Notebook.prototype.is_valid_cell_index = function (index) {
482 if (index !== null && index >= 0 && index < this.ncells()) {
488 if (index !== null && index >= 0 && index < this.ncells()) {
483 return true;
489 return true;
484 } else {
490 } else {
485 return false;
491 return false;
486 }
492 }
487 };
493 };
488
494
489 /**
495 /**
490 * Get the index of the currently selected cell.
496 * Get the index of the currently selected cell.
491
497
492 * @method get_selected_index
498 * @method get_selected_index
493 * @return {Number} The selected cell's numeric index
499 * @return {Number} The selected cell's numeric index
494 */
500 */
495 Notebook.prototype.get_selected_index = function () {
501 Notebook.prototype.get_selected_index = function () {
496 var result = null;
502 var result = null;
497 this.get_cell_elements().filter(function (index) {
503 this.get_cell_elements().filter(function (index) {
498 if ($(this).data("cell").selected === true) {
504 if ($(this).data("cell").selected === true) {
499 result = index;
505 result = index;
500 }
506 }
501 });
507 });
502 return result;
508 return result;
503 };
509 };
504
510
505
511
506 // Cell selection.
512 // Cell selection.
507
513
508 /**
514 /**
509 * Programmatically select a cell.
515 * Programmatically select a cell.
510 *
516 *
511 * @method select
517 * @method select
512 * @param {Number} index A cell's index
518 * @param {Number} index A cell's index
513 * @return {Notebook} This notebook
519 * @return {Notebook} This notebook
514 */
520 */
515 Notebook.prototype.select = function (index) {
521 Notebook.prototype.select = function (index) {
516 if (this.is_valid_cell_index(index)) {
522 if (this.is_valid_cell_index(index)) {
517 var sindex = this.get_selected_index();
523 var sindex = this.get_selected_index();
518 if (sindex !== null && index !== sindex) {
524 if (sindex !== null && index !== sindex) {
519 // If we are about to select a different cell, make sure we are
525 // If we are about to select a different cell, make sure we are
520 // first in command mode.
526 // first in command mode.
521 if (this.mode !== 'command') {
527 if (this.mode !== 'command') {
522 this.command_mode();
528 this.command_mode();
523 }
529 }
524 this.get_cell(sindex).unselect();
530 this.get_cell(sindex).unselect();
525 }
531 }
526 var cell = this.get_cell(index);
532 var cell = this.get_cell(index);
527 cell.select();
533 cell.select();
528 if (cell.cell_type === 'heading') {
534 if (cell.cell_type === 'heading') {
529 this.events.trigger('selected_cell_type_changed.Notebook',
535 this.events.trigger('selected_cell_type_changed.Notebook',
530 {'cell_type':cell.cell_type,level:cell.level}
536 {'cell_type':cell.cell_type,level:cell.level}
531 );
537 );
532 } else {
538 } else {
533 this.events.trigger('selected_cell_type_changed.Notebook',
539 this.events.trigger('selected_cell_type_changed.Notebook',
534 {'cell_type':cell.cell_type}
540 {'cell_type':cell.cell_type}
535 );
541 );
536 }
542 }
537 }
543 }
538 return this;
544 return this;
539 };
545 };
540
546
541 /**
547 /**
542 * Programmatically select the next cell.
548 * Programmatically select the next cell.
543 *
549 *
544 * @method select_next
550 * @method select_next
545 * @return {Notebook} This notebook
551 * @return {Notebook} This notebook
546 */
552 */
547 Notebook.prototype.select_next = function () {
553 Notebook.prototype.select_next = function () {
548 var index = this.get_selected_index();
554 var index = this.get_selected_index();
549 this.select(index+1);
555 this.select(index+1);
550 return this;
556 return this;
551 };
557 };
552
558
553 /**
559 /**
554 * Programmatically select the previous cell.
560 * Programmatically select the previous cell.
555 *
561 *
556 * @method select_prev
562 * @method select_prev
557 * @return {Notebook} This notebook
563 * @return {Notebook} This notebook
558 */
564 */
559 Notebook.prototype.select_prev = function () {
565 Notebook.prototype.select_prev = function () {
560 var index = this.get_selected_index();
566 var index = this.get_selected_index();
561 this.select(index-1);
567 this.select(index-1);
562 return this;
568 return this;
563 };
569 };
564
570
565
571
566 // Edit/Command mode
572 // Edit/Command mode
567
573
568 /**
574 /**
569 * Gets the index of the cell that is in edit mode.
575 * Gets the index of the cell that is in edit mode.
570 *
576 *
571 * @method get_edit_index
577 * @method get_edit_index
572 *
578 *
573 * @return index {int}
579 * @return index {int}
574 **/
580 **/
575 Notebook.prototype.get_edit_index = function () {
581 Notebook.prototype.get_edit_index = function () {
576 var result = null;
582 var result = null;
577 this.get_cell_elements().filter(function (index) {
583 this.get_cell_elements().filter(function (index) {
578 if ($(this).data("cell").mode === 'edit') {
584 if ($(this).data("cell").mode === 'edit') {
579 result = index;
585 result = index;
580 }
586 }
581 });
587 });
582 return result;
588 return result;
583 };
589 };
584
590
585 /**
591 /**
586 * Handle when a a cell blurs and the notebook should enter command mode.
592 * Handle when a a cell blurs and the notebook should enter command mode.
587 *
593 *
588 * @method handle_command_mode
594 * @method handle_command_mode
589 * @param [cell] {Cell} Cell to enter command mode on.
595 * @param [cell] {Cell} Cell to enter command mode on.
590 **/
596 **/
591 Notebook.prototype.handle_command_mode = function (cell) {
597 Notebook.prototype.handle_command_mode = function (cell) {
592 if (this.mode !== 'command') {
598 if (this.mode !== 'command') {
593 cell.command_mode();
599 cell.command_mode();
594 this.mode = 'command';
600 this.mode = 'command';
595 this.events.trigger('command_mode.Notebook');
601 this.events.trigger('command_mode.Notebook');
596 this.keyboard_manager.command_mode();
602 this.keyboard_manager.command_mode();
597 }
603 }
598 };
604 };
599
605
600 /**
606 /**
601 * Make the notebook enter command mode.
607 * Make the notebook enter command mode.
602 *
608 *
603 * @method command_mode
609 * @method command_mode
604 **/
610 **/
605 Notebook.prototype.command_mode = function () {
611 Notebook.prototype.command_mode = function () {
606 var cell = this.get_cell(this.get_edit_index());
612 var cell = this.get_cell(this.get_edit_index());
607 if (cell && this.mode !== 'command') {
613 if (cell && this.mode !== 'command') {
608 // We don't call cell.command_mode, but rather call cell.focus_cell()
614 // We don't call cell.command_mode, but rather call cell.focus_cell()
609 // which will blur and CM editor and trigger the call to
615 // which will blur and CM editor and trigger the call to
610 // handle_command_mode.
616 // handle_command_mode.
611 cell.focus_cell();
617 cell.focus_cell();
612 }
618 }
613 };
619 };
614
620
615 /**
621 /**
616 * Handle when a cell fires it's edit_mode event.
622 * Handle when a cell fires it's edit_mode event.
617 *
623 *
618 * @method handle_edit_mode
624 * @method handle_edit_mode
619 * @param [cell] {Cell} Cell to enter edit mode on.
625 * @param [cell] {Cell} Cell to enter edit mode on.
620 **/
626 **/
621 Notebook.prototype.handle_edit_mode = function (cell) {
627 Notebook.prototype.handle_edit_mode = function (cell) {
622 if (cell && this.mode !== 'edit') {
628 if (cell && this.mode !== 'edit') {
623 cell.edit_mode();
629 cell.edit_mode();
624 this.mode = 'edit';
630 this.mode = 'edit';
625 this.events.trigger('edit_mode.Notebook');
631 this.events.trigger('edit_mode.Notebook');
626 this.keyboard_manager.edit_mode();
632 this.keyboard_manager.edit_mode();
627 }
633 }
628 };
634 };
629
635
630 /**
636 /**
631 * Make a cell enter edit mode.
637 * Make a cell enter edit mode.
632 *
638 *
633 * @method edit_mode
639 * @method edit_mode
634 **/
640 **/
635 Notebook.prototype.edit_mode = function () {
641 Notebook.prototype.edit_mode = function () {
636 var cell = this.get_selected_cell();
642 var cell = this.get_selected_cell();
637 if (cell && this.mode !== 'edit') {
643 if (cell && this.mode !== 'edit') {
638 cell.unrender();
644 cell.unrender();
639 cell.focus_editor();
645 cell.focus_editor();
640 }
646 }
641 };
647 };
642
648
643 /**
649 /**
644 * Focus the currently selected cell.
650 * Focus the currently selected cell.
645 *
651 *
646 * @method focus_cell
652 * @method focus_cell
647 **/
653 **/
648 Notebook.prototype.focus_cell = function () {
654 Notebook.prototype.focus_cell = function () {
649 var cell = this.get_selected_cell();
655 var cell = this.get_selected_cell();
650 if (cell === null) {return;} // No cell is selected
656 if (cell === null) {return;} // No cell is selected
651 cell.focus_cell();
657 cell.focus_cell();
652 };
658 };
653
659
654 // Cell movement
660 // Cell movement
655
661
656 /**
662 /**
657 * Move given (or selected) cell up and select it.
663 * Move given (or selected) cell up and select it.
658 *
664 *
659 * @method move_cell_up
665 * @method move_cell_up
660 * @param [index] {integer} cell index
666 * @param [index] {integer} cell index
661 * @return {Notebook} This notebook
667 * @return {Notebook} This notebook
662 **/
668 **/
663 Notebook.prototype.move_cell_up = function (index) {
669 Notebook.prototype.move_cell_up = function (index) {
664 var i = this.index_or_selected(index);
670 var i = this.index_or_selected(index);
665 if (this.is_valid_cell_index(i) && i > 0) {
671 if (this.is_valid_cell_index(i) && i > 0) {
666 var pivot = this.get_cell_element(i-1);
672 var pivot = this.get_cell_element(i-1);
667 var tomove = this.get_cell_element(i);
673 var tomove = this.get_cell_element(i);
668 if (pivot !== null && tomove !== null) {
674 if (pivot !== null && tomove !== null) {
669 tomove.detach();
675 tomove.detach();
670 pivot.before(tomove);
676 pivot.before(tomove);
671 this.select(i-1);
677 this.select(i-1);
672 var cell = this.get_selected_cell();
678 var cell = this.get_selected_cell();
673 cell.focus_cell();
679 cell.focus_cell();
674 }
680 }
675 this.set_dirty(true);
681 this.set_dirty(true);
676 }
682 }
677 return this;
683 return this;
678 };
684 };
679
685
680
686
681 /**
687 /**
682 * Move given (or selected) cell down and select it
688 * Move given (or selected) cell down and select it
683 *
689 *
684 * @method move_cell_down
690 * @method move_cell_down
685 * @param [index] {integer} cell index
691 * @param [index] {integer} cell index
686 * @return {Notebook} This notebook
692 * @return {Notebook} This notebook
687 **/
693 **/
688 Notebook.prototype.move_cell_down = function (index) {
694 Notebook.prototype.move_cell_down = function (index) {
689 var i = this.index_or_selected(index);
695 var i = this.index_or_selected(index);
690 if (this.is_valid_cell_index(i) && this.is_valid_cell_index(i+1)) {
696 if (this.is_valid_cell_index(i) && this.is_valid_cell_index(i+1)) {
691 var pivot = this.get_cell_element(i+1);
697 var pivot = this.get_cell_element(i+1);
692 var tomove = this.get_cell_element(i);
698 var tomove = this.get_cell_element(i);
693 if (pivot !== null && tomove !== null) {
699 if (pivot !== null && tomove !== null) {
694 tomove.detach();
700 tomove.detach();
695 pivot.after(tomove);
701 pivot.after(tomove);
696 this.select(i+1);
702 this.select(i+1);
697 var cell = this.get_selected_cell();
703 var cell = this.get_selected_cell();
698 cell.focus_cell();
704 cell.focus_cell();
699 }
705 }
700 }
706 }
701 this.set_dirty();
707 this.set_dirty();
702 return this;
708 return this;
703 };
709 };
704
710
705
711
706 // Insertion, deletion.
712 // Insertion, deletion.
707
713
708 /**
714 /**
709 * Delete a cell from the notebook.
715 * Delete a cell from the notebook.
710 *
716 *
711 * @method delete_cell
717 * @method delete_cell
712 * @param [index] A cell's numeric index
718 * @param [index] A cell's numeric index
713 * @return {Notebook} This notebook
719 * @return {Notebook} This notebook
714 */
720 */
715 Notebook.prototype.delete_cell = function (index) {
721 Notebook.prototype.delete_cell = function (index) {
716 var i = this.index_or_selected(index);
722 var i = this.index_or_selected(index);
717 var cell = this.get_selected_cell();
723 var cell = this.get_selected_cell();
718 this.undelete_backup = cell.toJSON();
724 this.undelete_backup = cell.toJSON();
719 $('#undelete_cell').removeClass('disabled');
725 $('#undelete_cell').removeClass('disabled');
720 if (this.is_valid_cell_index(i)) {
726 if (this.is_valid_cell_index(i)) {
721 var old_ncells = this.ncells();
727 var old_ncells = this.ncells();
722 var ce = this.get_cell_element(i);
728 var ce = this.get_cell_element(i);
723 ce.remove();
729 ce.remove();
724 if (i === 0) {
730 if (i === 0) {
725 // Always make sure we have at least one cell.
731 // Always make sure we have at least one cell.
726 if (old_ncells === 1) {
732 if (old_ncells === 1) {
727 this.insert_cell_below('code');
733 this.insert_cell_below('code');
728 }
734 }
729 this.select(0);
735 this.select(0);
730 this.undelete_index = 0;
736 this.undelete_index = 0;
731 this.undelete_below = false;
737 this.undelete_below = false;
732 } else if (i === old_ncells-1 && i !== 0) {
738 } else if (i === old_ncells-1 && i !== 0) {
733 this.select(i-1);
739 this.select(i-1);
734 this.undelete_index = i - 1;
740 this.undelete_index = i - 1;
735 this.undelete_below = true;
741 this.undelete_below = true;
736 } else {
742 } else {
737 this.select(i);
743 this.select(i);
738 this.undelete_index = i;
744 this.undelete_index = i;
739 this.undelete_below = false;
745 this.undelete_below = false;
740 }
746 }
741 this.events.trigger('delete.Cell', {'cell': cell, 'index': i});
747 this.events.trigger('delete.Cell', {'cell': cell, 'index': i});
742 this.set_dirty(true);
748 this.set_dirty(true);
743 }
749 }
744 return this;
750 return this;
745 };
751 };
746
752
747 /**
753 /**
748 * Restore the most recently deleted cell.
754 * Restore the most recently deleted cell.
749 *
755 *
750 * @method undelete
756 * @method undelete
751 */
757 */
752 Notebook.prototype.undelete_cell = function() {
758 Notebook.prototype.undelete_cell = function() {
753 if (this.undelete_backup !== null && this.undelete_index !== null) {
759 if (this.undelete_backup !== null && this.undelete_index !== null) {
754 var current_index = this.get_selected_index();
760 var current_index = this.get_selected_index();
755 if (this.undelete_index < current_index) {
761 if (this.undelete_index < current_index) {
756 current_index = current_index + 1;
762 current_index = current_index + 1;
757 }
763 }
758 if (this.undelete_index >= this.ncells()) {
764 if (this.undelete_index >= this.ncells()) {
759 this.select(this.ncells() - 1);
765 this.select(this.ncells() - 1);
760 }
766 }
761 else {
767 else {
762 this.select(this.undelete_index);
768 this.select(this.undelete_index);
763 }
769 }
764 var cell_data = this.undelete_backup;
770 var cell_data = this.undelete_backup;
765 var new_cell = null;
771 var new_cell = null;
766 if (this.undelete_below) {
772 if (this.undelete_below) {
767 new_cell = this.insert_cell_below(cell_data.cell_type);
773 new_cell = this.insert_cell_below(cell_data.cell_type);
768 } else {
774 } else {
769 new_cell = this.insert_cell_above(cell_data.cell_type);
775 new_cell = this.insert_cell_above(cell_data.cell_type);
770 }
776 }
771 new_cell.fromJSON(cell_data);
777 new_cell.fromJSON(cell_data);
772 if (this.undelete_below) {
778 if (this.undelete_below) {
773 this.select(current_index+1);
779 this.select(current_index+1);
774 } else {
780 } else {
775 this.select(current_index);
781 this.select(current_index);
776 }
782 }
777 this.undelete_backup = null;
783 this.undelete_backup = null;
778 this.undelete_index = null;
784 this.undelete_index = null;
779 }
785 }
780 $('#undelete_cell').addClass('disabled');
786 $('#undelete_cell').addClass('disabled');
781 };
787 };
782
788
783 /**
789 /**
784 * Insert a cell so that after insertion the cell is at given index.
790 * Insert a cell so that after insertion the cell is at given index.
785 *
791 *
786 * If cell type is not provided, it will default to the type of the
792 * If cell type is not provided, it will default to the type of the
787 * currently active cell.
793 * currently active cell.
788 *
794 *
789 * Similar to insert_above, but index parameter is mandatory
795 * Similar to insert_above, but index parameter is mandatory
790 *
796 *
791 * Index will be brought back into the accessible range [0,n]
797 * Index will be brought back into the accessible range [0,n]
792 *
798 *
793 * @method insert_cell_at_index
799 * @method insert_cell_at_index
794 * @param [type] {string} in ['code','markdown','heading'], defaults to 'code'
800 * @param [type] {string} in ['code','markdown','heading'], defaults to 'code'
795 * @param [index] {int} a valid index where to insert cell
801 * @param [index] {int} a valid index where to insert cell
796 *
802 *
797 * @return cell {cell|null} created cell or null
803 * @return cell {cell|null} created cell or null
798 **/
804 **/
799 Notebook.prototype.insert_cell_at_index = function(type, index){
805 Notebook.prototype.insert_cell_at_index = function(type, index){
800
806
801 var ncells = this.ncells();
807 var ncells = this.ncells();
802 index = Math.min(index,ncells);
808 index = Math.min(index,ncells);
803 index = Math.max(index,0);
809 index = Math.max(index,0);
804 var cell = null;
810 var cell = null;
805 type = type || this.get_selected_cell().cell_type;
811 type = type || this.get_selected_cell().cell_type;
806
812
807 if (ncells === 0 || this.is_valid_cell_index(index) || index === ncells) {
813 if (ncells === 0 || this.is_valid_cell_index(index) || index === ncells) {
808 var cell_options = {
814 var cell_options = {
809 base_url: base_url,
810 notebook_path: notebook_path,
811 notebook_name: notebook_name,
812 events: this.events,
815 events: this.events,
813 config: this.config,
816 config: this.config,
814 keyboard_manager: this.keyboard_manager,
817 keyboard_manager: this.keyboard_manager,
815 notebook: this
818 notebook: this
816 };
819 };
817 if (type === 'code') {
820 if (type === 'code') {
818 cell = new codecell.CodeCell(this.kernel, cell_options);
821 cell = new codecell.CodeCell(this.kernel, cell_options);
819 cell.set_input_prompt();
822 cell.set_input_prompt();
820 } else if (type === 'markdown') {
823 } else if (type === 'markdown') {
821 cell = new cells.MarkdownCell(cell_options);
824 cell = new textcell.MarkdownCell(cell_options);
822 } else if (type === 'raw') {
825 } else if (type === 'raw') {
823 cell = new cells.RawCell(cell_options);
826 cell = new textcell.RawCell(cell_options);
824 } else if (type === 'heading') {
827 } else if (type === 'heading') {
825 cell = new cells.HeadingCell(cell_options);
828 cell = new textcell.HeadingCell(cell_options);
826 }
829 }
827
830
828 if(this._insert_element_at_index(cell.element,index)) {
831 if(this._insert_element_at_index(cell.element,index)) {
829 cell.render();
832 cell.render();
830 this.events.trigger('create.Cell', {'cell': cell, 'index': index});
833 this.events.trigger('create.Cell', {'cell': cell, 'index': index});
831 cell.refresh();
834 cell.refresh();
832 // We used to select the cell after we refresh it, but there
835 // We used to select the cell after we refresh it, but there
833 // are now cases were this method is called where select is
836 // are now cases were this method is called where select is
834 // not appropriate. The selection logic should be handled by the
837 // not appropriate. The selection logic should be handled by the
835 // caller of the the top level insert_cell methods.
838 // caller of the the top level insert_cell methods.
836 this.set_dirty(true);
839 this.set_dirty(true);
837 }
840 }
838 }
841 }
839 return cell;
842 return cell;
840
843
841 };
844 };
842
845
843 /**
846 /**
844 * Insert an element at given cell index.
847 * Insert an element at given cell index.
845 *
848 *
846 * @method _insert_element_at_index
849 * @method _insert_element_at_index
847 * @param element {dom element} a cell element
850 * @param element {dom element} a cell element
848 * @param [index] {int} a valid index where to inser cell
851 * @param [index] {int} a valid index where to inser cell
849 * @private
852 * @private
850 *
853 *
851 * return true if everything whent fine.
854 * return true if everything whent fine.
852 **/
855 **/
853 Notebook.prototype._insert_element_at_index = function(element, index){
856 Notebook.prototype._insert_element_at_index = function(element, index){
854 if (element === undefined){
857 if (element === undefined){
855 return false;
858 return false;
856 }
859 }
857
860
858 var ncells = this.ncells();
861 var ncells = this.ncells();
859
862
860 if (ncells === 0) {
863 if (ncells === 0) {
861 // special case append if empty
864 // special case append if empty
862 this.element.find('div.end_space').before(element);
865 this.element.find('div.end_space').before(element);
863 } else if ( ncells === index ) {
866 } else if ( ncells === index ) {
864 // special case append it the end, but not empty
867 // special case append it the end, but not empty
865 this.get_cell_element(index-1).after(element);
868 this.get_cell_element(index-1).after(element);
866 } else if (this.is_valid_cell_index(index)) {
869 } else if (this.is_valid_cell_index(index)) {
867 // otherwise always somewhere to append to
870 // otherwise always somewhere to append to
868 this.get_cell_element(index).before(element);
871 this.get_cell_element(index).before(element);
869 } else {
872 } else {
870 return false;
873 return false;
871 }
874 }
872
875
873 if (this.undelete_index !== null && index <= this.undelete_index) {
876 if (this.undelete_index !== null && index <= this.undelete_index) {
874 this.undelete_index = this.undelete_index + 1;
877 this.undelete_index = this.undelete_index + 1;
875 this.set_dirty(true);
878 this.set_dirty(true);
876 }
879 }
877 return true;
880 return true;
878 };
881 };
879
882
880 /**
883 /**
881 * Insert a cell of given type above given index, or at top
884 * Insert a cell of given type above given index, or at top
882 * of notebook if index smaller than 0.
885 * of notebook if index smaller than 0.
883 *
886 *
884 * default index value is the one of currently selected cell
887 * default index value is the one of currently selected cell
885 *
888 *
886 * @method insert_cell_above
889 * @method insert_cell_above
887 * @param [type] {string} cell type
890 * @param [type] {string} cell type
888 * @param [index] {integer}
891 * @param [index] {integer}
889 *
892 *
890 * @return handle to created cell or null
893 * @return handle to created cell or null
891 **/
894 **/
892 Notebook.prototype.insert_cell_above = function (type, index) {
895 Notebook.prototype.insert_cell_above = function (type, index) {
893 index = this.index_or_selected(index);
896 index = this.index_or_selected(index);
894 return this.insert_cell_at_index(type, index);
897 return this.insert_cell_at_index(type, index);
895 };
898 };
896
899
897 /**
900 /**
898 * Insert a cell of given type below given index, or at bottom
901 * Insert a cell of given type below given index, or at bottom
899 * of notebook if index greater than number of cells
902 * of notebook if index greater than number of cells
900 *
903 *
901 * default index value is the one of currently selected cell
904 * default index value is the one of currently selected cell
902 *
905 *
903 * @method insert_cell_below
906 * @method insert_cell_below
904 * @param [type] {string} cell type
907 * @param [type] {string} cell type
905 * @param [index] {integer}
908 * @param [index] {integer}
906 *
909 *
907 * @return handle to created cell or null
910 * @return handle to created cell or null
908 *
911 *
909 **/
912 **/
910 Notebook.prototype.insert_cell_below = function (type, index) {
913 Notebook.prototype.insert_cell_below = function (type, index) {
911 index = this.index_or_selected(index);
914 index = this.index_or_selected(index);
912 return this.insert_cell_at_index(type, index+1);
915 return this.insert_cell_at_index(type, index+1);
913 };
916 };
914
917
915
918
916 /**
919 /**
917 * Insert cell at end of notebook
920 * Insert cell at end of notebook
918 *
921 *
919 * @method insert_cell_at_bottom
922 * @method insert_cell_at_bottom
920 * @param {String} type cell type
923 * @param {String} type cell type
921 *
924 *
922 * @return the added cell; or null
925 * @return the added cell; or null
923 **/
926 **/
924 Notebook.prototype.insert_cell_at_bottom = function (type){
927 Notebook.prototype.insert_cell_at_bottom = function (type){
925 var len = this.ncells();
928 var len = this.ncells();
926 return this.insert_cell_below(type,len-1);
929 return this.insert_cell_below(type,len-1);
927 };
930 };
928
931
929 /**
932 /**
930 * Turn a cell into a code cell.
933 * Turn a cell into a code cell.
931 *
934 *
932 * @method to_code
935 * @method to_code
933 * @param {Number} [index] A cell's index
936 * @param {Number} [index] A cell's index
934 */
937 */
935 Notebook.prototype.to_code = function (index) {
938 Notebook.prototype.to_code = function (index) {
936 var i = this.index_or_selected(index);
939 var i = this.index_or_selected(index);
937 if (this.is_valid_cell_index(i)) {
940 if (this.is_valid_cell_index(i)) {
938 var source_element = this.get_cell_element(i);
941 var source_element = this.get_cell_element(i);
939 var source_cell = source_element.data("cell");
942 var source_cell = source_element.data("cell");
940 if (!(source_cell instanceof codecell.CodeCell)) {
943 if (!(source_cell instanceof codecell.CodeCell)) {
941 var target_cell = this.insert_cell_below('code',i);
944 var target_cell = this.insert_cell_below('code',i);
942 var text = source_cell.get_text();
945 var text = source_cell.get_text();
943 if (text === source_cell.placeholder) {
946 if (text === source_cell.placeholder) {
944 text = '';
947 text = '';
945 }
948 }
946 target_cell.set_text(text);
949 target_cell.set_text(text);
947 // make this value the starting point, so that we can only undo
950 // make this value the starting point, so that we can only undo
948 // to this state, instead of a blank cell
951 // to this state, instead of a blank cell
949 target_cell.code_mirror.clearHistory();
952 target_cell.code_mirror.clearHistory();
950 source_element.remove();
953 source_element.remove();
951 this.select(i);
954 this.select(i);
952 var cursor = source_cell.code_mirror.getCursor();
955 var cursor = source_cell.code_mirror.getCursor();
953 target_cell.code_mirror.setCursor(cursor);
956 target_cell.code_mirror.setCursor(cursor);
954 this.set_dirty(true);
957 this.set_dirty(true);
955 }
958 }
956 }
959 }
957 };
960 };
958
961
959 /**
962 /**
960 * Turn a cell into a Markdown cell.
963 * Turn a cell into a Markdown cell.
961 *
964 *
962 * @method to_markdown
965 * @method to_markdown
963 * @param {Number} [index] A cell's index
966 * @param {Number} [index] A cell's index
964 */
967 */
965 Notebook.prototype.to_markdown = function (index) {
968 Notebook.prototype.to_markdown = function (index) {
966 var i = this.index_or_selected(index);
969 var i = this.index_or_selected(index);
967 if (this.is_valid_cell_index(i)) {
970 if (this.is_valid_cell_index(i)) {
968 var source_element = this.get_cell_element(i);
971 var source_element = this.get_cell_element(i);
969 var source_cell = source_element.data("cell");
972 var source_cell = source_element.data("cell");
970 if (!(source_cell instanceof cells.MarkdownCell)) {
973 if (!(source_cell instanceof textcell.MarkdownCell)) {
971 var target_cell = this.insert_cell_below('markdown',i);
974 var target_cell = this.insert_cell_below('markdown',i);
972 var text = source_cell.get_text();
975 var text = source_cell.get_text();
973 if (text === source_cell.placeholder) {
976 if (text === source_cell.placeholder) {
974 text = '';
977 text = '';
975 }
978 }
976 // We must show the editor before setting its contents
979 // We must show the editor before setting its contents
977 target_cell.unrender();
980 target_cell.unrender();
978 target_cell.set_text(text);
981 target_cell.set_text(text);
979 // make this value the starting point, so that we can only undo
982 // make this value the starting point, so that we can only undo
980 // to this state, instead of a blank cell
983 // to this state, instead of a blank cell
981 target_cell.code_mirror.clearHistory();
984 target_cell.code_mirror.clearHistory();
982 source_element.remove();
985 source_element.remove();
983 this.select(i);
986 this.select(i);
984 if ((source_cell instanceof cells.TextCell) && source_cell.rendered) {
987 if ((source_cell instanceof textcell.TextCell) && source_cell.rendered) {
985 target_cell.render();
988 target_cell.render();
986 }
989 }
987 var cursor = source_cell.code_mirror.getCursor();
990 var cursor = source_cell.code_mirror.getCursor();
988 target_cell.code_mirror.setCursor(cursor);
991 target_cell.code_mirror.setCursor(cursor);
989 this.set_dirty(true);
992 this.set_dirty(true);
990 }
993 }
991 }
994 }
992 };
995 };
993
996
994 /**
997 /**
995 * Turn a cell into a raw text cell.
998 * Turn a cell into a raw text cell.
996 *
999 *
997 * @method to_raw
1000 * @method to_raw
998 * @param {Number} [index] A cell's index
1001 * @param {Number} [index] A cell's index
999 */
1002 */
1000 Notebook.prototype.to_raw = function (index) {
1003 Notebook.prototype.to_raw = function (index) {
1001 var i = this.index_or_selected(index);
1004 var i = this.index_or_selected(index);
1002 if (this.is_valid_cell_index(i)) {
1005 if (this.is_valid_cell_index(i)) {
1003 var source_element = this.get_cell_element(i);
1006 var source_element = this.get_cell_element(i);
1004 var source_cell = source_element.data("cell");
1007 var source_cell = source_element.data("cell");
1005 var target_cell = null;
1008 var target_cell = null;
1006 if (!(source_cell instanceof cells.RawCell)) {
1009 if (!(source_cell instanceof textcell.RawCell)) {
1007 target_cell = this.insert_cell_below('raw',i);
1010 target_cell = this.insert_cell_below('raw',i);
1008 var text = source_cell.get_text();
1011 var text = source_cell.get_text();
1009 if (text === source_cell.placeholder) {
1012 if (text === source_cell.placeholder) {
1010 text = '';
1013 text = '';
1011 }
1014 }
1012 // We must show the editor before setting its contents
1015 // We must show the editor before setting its contents
1013 target_cell.unrender();
1016 target_cell.unrender();
1014 target_cell.set_text(text);
1017 target_cell.set_text(text);
1015 // make this value the starting point, so that we can only undo
1018 // make this value the starting point, so that we can only undo
1016 // to this state, instead of a blank cell
1019 // to this state, instead of a blank cell
1017 target_cell.code_mirror.clearHistory();
1020 target_cell.code_mirror.clearHistory();
1018 source_element.remove();
1021 source_element.remove();
1019 this.select(i);
1022 this.select(i);
1020 var cursor = source_cell.code_mirror.getCursor();
1023 var cursor = source_cell.code_mirror.getCursor();
1021 target_cell.code_mirror.setCursor(cursor);
1024 target_cell.code_mirror.setCursor(cursor);
1022 this.set_dirty(true);
1025 this.set_dirty(true);
1023 }
1026 }
1024 }
1027 }
1025 };
1028 };
1026
1029
1027 /**
1030 /**
1028 * Turn a cell into a heading cell.
1031 * Turn a cell into a heading cell.
1029 *
1032 *
1030 * @method to_heading
1033 * @method to_heading
1031 * @param {Number} [index] A cell's index
1034 * @param {Number} [index] A cell's index
1032 * @param {Number} [level] A heading level (e.g., 1 becomes &lt;h1&gt;)
1035 * @param {Number} [level] A heading level (e.g., 1 becomes &lt;h1&gt;)
1033 */
1036 */
1034 Notebook.prototype.to_heading = function (index, level) {
1037 Notebook.prototype.to_heading = function (index, level) {
1035 level = level || 1;
1038 level = level || 1;
1036 var i = this.index_or_selected(index);
1039 var i = this.index_or_selected(index);
1037 if (this.is_valid_cell_index(i)) {
1040 if (this.is_valid_cell_index(i)) {
1038 var source_element = this.get_cell_element(i);
1041 var source_element = this.get_cell_element(i);
1039 var source_cell = source_element.data("cell");
1042 var source_cell = source_element.data("cell");
1040 var target_cell = null;
1043 var target_cell = null;
1041 if (source_cell instanceof cells.HeadingCell) {
1044 if (source_cell instanceof textcell.HeadingCell) {
1042 source_cell.set_level(level);
1045 source_cell.set_level(level);
1043 } else {
1046 } else {
1044 target_cell = this.insert_cell_below('heading',i);
1047 target_cell = this.insert_cell_below('heading',i);
1045 var text = source_cell.get_text();
1048 var text = source_cell.get_text();
1046 if (text === source_cell.placeholder) {
1049 if (text === source_cell.placeholder) {
1047 text = '';
1050 text = '';
1048 }
1051 }
1049 // We must show the editor before setting its contents
1052 // We must show the editor before setting its contents
1050 target_cell.set_level(level);
1053 target_cell.set_level(level);
1051 target_cell.unrender();
1054 target_cell.unrender();
1052 target_cell.set_text(text);
1055 target_cell.set_text(text);
1053 // make this value the starting point, so that we can only undo
1056 // make this value the starting point, so that we can only undo
1054 // to this state, instead of a blank cell
1057 // to this state, instead of a blank cell
1055 target_cell.code_mirror.clearHistory();
1058 target_cell.code_mirror.clearHistory();
1056 source_element.remove();
1059 source_element.remove();
1057 this.select(i);
1060 this.select(i);
1058 var cursor = source_cell.code_mirror.getCursor();
1061 var cursor = source_cell.code_mirror.getCursor();
1059 target_cell.code_mirror.setCursor(cursor);
1062 target_cell.code_mirror.setCursor(cursor);
1060 if ((source_cell instanceof cells.TextCell) && source_cell.rendered) {
1063 if ((source_cell instanceof textcell.TextCell) && source_cell.rendered) {
1061 target_cell.render();
1064 target_cell.render();
1062 }
1065 }
1063 }
1066 }
1064 this.set_dirty(true);
1067 this.set_dirty(true);
1065 this.events.trigger('selected_cell_type_changed.Notebook',
1068 this.events.trigger('selected_cell_type_changed.Notebook',
1066 {'cell_type':'heading',level:level}
1069 {'cell_type':'heading',level:level}
1067 );
1070 );
1068 }
1071 }
1069 };
1072 };
1070
1073
1071
1074
1072 // Cut/Copy/Paste
1075 // Cut/Copy/Paste
1073
1076
1074 /**
1077 /**
1075 * Enable UI elements for pasting cells.
1078 * Enable UI elements for pasting cells.
1076 *
1079 *
1077 * @method enable_paste
1080 * @method enable_paste
1078 */
1081 */
1079 Notebook.prototype.enable_paste = function () {
1082 Notebook.prototype.enable_paste = function () {
1080 var that = this;
1083 var that = this;
1081 if (!this.paste_enabled) {
1084 if (!this.paste_enabled) {
1082 $('#paste_cell_replace').removeClass('disabled')
1085 $('#paste_cell_replace').removeClass('disabled')
1083 .on('click', function () {that.paste_cell_replace();});
1086 .on('click', function () {that.paste_cell_replace();});
1084 $('#paste_cell_above').removeClass('disabled')
1087 $('#paste_cell_above').removeClass('disabled')
1085 .on('click', function () {that.paste_cell_above();});
1088 .on('click', function () {that.paste_cell_above();});
1086 $('#paste_cell_below').removeClass('disabled')
1089 $('#paste_cell_below').removeClass('disabled')
1087 .on('click', function () {that.paste_cell_below();});
1090 .on('click', function () {that.paste_cell_below();});
1088 this.paste_enabled = true;
1091 this.paste_enabled = true;
1089 }
1092 }
1090 };
1093 };
1091
1094
1092 /**
1095 /**
1093 * Disable UI elements for pasting cells.
1096 * Disable UI elements for pasting cells.
1094 *
1097 *
1095 * @method disable_paste
1098 * @method disable_paste
1096 */
1099 */
1097 Notebook.prototype.disable_paste = function () {
1100 Notebook.prototype.disable_paste = function () {
1098 if (this.paste_enabled) {
1101 if (this.paste_enabled) {
1099 $('#paste_cell_replace').addClass('disabled').off('click');
1102 $('#paste_cell_replace').addClass('disabled').off('click');
1100 $('#paste_cell_above').addClass('disabled').off('click');
1103 $('#paste_cell_above').addClass('disabled').off('click');
1101 $('#paste_cell_below').addClass('disabled').off('click');
1104 $('#paste_cell_below').addClass('disabled').off('click');
1102 this.paste_enabled = false;
1105 this.paste_enabled = false;
1103 }
1106 }
1104 };
1107 };
1105
1108
1106 /**
1109 /**
1107 * Cut a cell.
1110 * Cut a cell.
1108 *
1111 *
1109 * @method cut_cell
1112 * @method cut_cell
1110 */
1113 */
1111 Notebook.prototype.cut_cell = function () {
1114 Notebook.prototype.cut_cell = function () {
1112 this.copy_cell();
1115 this.copy_cell();
1113 this.delete_cell();
1116 this.delete_cell();
1114 };
1117 };
1115
1118
1116 /**
1119 /**
1117 * Copy a cell.
1120 * Copy a cell.
1118 *
1121 *
1119 * @method copy_cell
1122 * @method copy_cell
1120 */
1123 */
1121 Notebook.prototype.copy_cell = function () {
1124 Notebook.prototype.copy_cell = function () {
1122 var cell = this.get_selected_cell();
1125 var cell = this.get_selected_cell();
1123 this.clipboard = cell.toJSON();
1126 this.clipboard = cell.toJSON();
1124 this.enable_paste();
1127 this.enable_paste();
1125 };
1128 };
1126
1129
1127 /**
1130 /**
1128 * Replace the selected cell with a cell in the clipboard.
1131 * Replace the selected cell with a cell in the clipboard.
1129 *
1132 *
1130 * @method paste_cell_replace
1133 * @method paste_cell_replace
1131 */
1134 */
1132 Notebook.prototype.paste_cell_replace = function () {
1135 Notebook.prototype.paste_cell_replace = function () {
1133 if (this.clipboard !== null && this.paste_enabled) {
1136 if (this.clipboard !== null && this.paste_enabled) {
1134 var cell_data = this.clipboard;
1137 var cell_data = this.clipboard;
1135 var new_cell = this.insert_cell_above(cell_data.cell_type);
1138 var new_cell = this.insert_cell_above(cell_data.cell_type);
1136 new_cell.fromJSON(cell_data);
1139 new_cell.fromJSON(cell_data);
1137 var old_cell = this.get_next_cell(new_cell);
1140 var old_cell = this.get_next_cell(new_cell);
1138 this.delete_cell(this.find_cell_index(old_cell));
1141 this.delete_cell(this.find_cell_index(old_cell));
1139 this.select(this.find_cell_index(new_cell));
1142 this.select(this.find_cell_index(new_cell));
1140 }
1143 }
1141 };
1144 };
1142
1145
1143 /**
1146 /**
1144 * Paste a cell from the clipboard above the selected cell.
1147 * Paste a cell from the clipboard above the selected cell.
1145 *
1148 *
1146 * @method paste_cell_above
1149 * @method paste_cell_above
1147 */
1150 */
1148 Notebook.prototype.paste_cell_above = function () {
1151 Notebook.prototype.paste_cell_above = function () {
1149 if (this.clipboard !== null && this.paste_enabled) {
1152 if (this.clipboard !== null && this.paste_enabled) {
1150 var cell_data = this.clipboard;
1153 var cell_data = this.clipboard;
1151 var new_cell = this.insert_cell_above(cell_data.cell_type);
1154 var new_cell = this.insert_cell_above(cell_data.cell_type);
1152 new_cell.fromJSON(cell_data);
1155 new_cell.fromJSON(cell_data);
1153 new_cell.focus_cell();
1156 new_cell.focus_cell();
1154 }
1157 }
1155 };
1158 };
1156
1159
1157 /**
1160 /**
1158 * Paste a cell from the clipboard below the selected cell.
1161 * Paste a cell from the clipboard below the selected cell.
1159 *
1162 *
1160 * @method paste_cell_below
1163 * @method paste_cell_below
1161 */
1164 */
1162 Notebook.prototype.paste_cell_below = function () {
1165 Notebook.prototype.paste_cell_below = function () {
1163 if (this.clipboard !== null && this.paste_enabled) {
1166 if (this.clipboard !== null && this.paste_enabled) {
1164 var cell_data = this.clipboard;
1167 var cell_data = this.clipboard;
1165 var new_cell = this.insert_cell_below(cell_data.cell_type);
1168 var new_cell = this.insert_cell_below(cell_data.cell_type);
1166 new_cell.fromJSON(cell_data);
1169 new_cell.fromJSON(cell_data);
1167 new_cell.focus_cell();
1170 new_cell.focus_cell();
1168 }
1171 }
1169 };
1172 };
1170
1173
1171 // Split/merge
1174 // Split/merge
1172
1175
1173 /**
1176 /**
1174 * Split the selected cell into two, at the cursor.
1177 * Split the selected cell into two, at the cursor.
1175 *
1178 *
1176 * @method split_cell
1179 * @method split_cell
1177 */
1180 */
1178 Notebook.prototype.split_cell = function () {
1181 Notebook.prototype.split_cell = function () {
1179 var mdc = cells.MarkdownCell;
1182 var mdc = textcell.MarkdownCell;
1180 var rc = cells.RawCell;
1183 var rc = textcell.RawCell;
1181 var cell = this.get_selected_cell();
1184 var cell = this.get_selected_cell();
1182 if (cell.is_splittable()) {
1185 if (cell.is_splittable()) {
1183 var texta = cell.get_pre_cursor();
1186 var texta = cell.get_pre_cursor();
1184 var textb = cell.get_post_cursor();
1187 var textb = cell.get_post_cursor();
1185 if (cell instanceof codecell.CodeCell) {
1188 if (cell instanceof codecell.CodeCell) {
1186 // In this case the operations keep the notebook in its existing mode
1189 // In this case the operations keep the notebook in its existing mode
1187 // so we don't need to do any post-op mode changes.
1190 // so we don't need to do any post-op mode changes.
1188 cell.set_text(textb);
1191 cell.set_text(textb);
1189 var new_cell = this.insert_cell_above('code');
1192 var new_cell = this.insert_cell_above('code');
1190 new_cell.set_text(texta);
1193 new_cell.set_text(texta);
1191 } else if ((cell instanceof mdc && !cell.rendered) || (cell instanceof rc)) {
1194 } else if ((cell instanceof mdc && !cell.rendered) || (cell instanceof rc)) {
1192 // We know cell is !rendered so we can use set_text.
1195 // We know cell is !rendered so we can use set_text.
1193 cell.set_text(textb);
1196 cell.set_text(textb);
1194 var new_cell = this.insert_cell_above(cell.cell_type);
1197 var new_cell = this.insert_cell_above(cell.cell_type);
1195 // Unrender the new cell so we can call set_text.
1198 // Unrender the new cell so we can call set_text.
1196 new_cell.unrender();
1199 new_cell.unrender();
1197 new_cell.set_text(texta);
1200 new_cell.set_text(texta);
1198 }
1201 }
1199 }
1202 }
1200 };
1203 };
1201
1204
1202 /**
1205 /**
1203 * Combine the selected cell into the cell above it.
1206 * Combine the selected cell into the cell above it.
1204 *
1207 *
1205 * @method merge_cell_above
1208 * @method merge_cell_above
1206 */
1209 */
1207 Notebook.prototype.merge_cell_above = function () {
1210 Notebook.prototype.merge_cell_above = function () {
1208 var mdc = cells.MarkdownCell;
1211 var mdc = textcell.MarkdownCell;
1209 var rc = cells.RawCell;
1212 var rc = textcell.RawCell;
1210 var index = this.get_selected_index();
1213 var index = this.get_selected_index();
1211 var cell = this.get_cell(index);
1214 var cell = this.get_cell(index);
1212 var render = cell.rendered;
1215 var render = cell.rendered;
1213 if (!cell.is_mergeable()) {
1216 if (!cell.is_mergeable()) {
1214 return;
1217 return;
1215 }
1218 }
1216 if (index > 0) {
1219 if (index > 0) {
1217 var upper_cell = this.get_cell(index-1);
1220 var upper_cell = this.get_cell(index-1);
1218 if (!upper_cell.is_mergeable()) {
1221 if (!upper_cell.is_mergeable()) {
1219 return;
1222 return;
1220 }
1223 }
1221 var upper_text = upper_cell.get_text();
1224 var upper_text = upper_cell.get_text();
1222 var text = cell.get_text();
1225 var text = cell.get_text();
1223 if (cell instanceof codecell.CodeCell) {
1226 if (cell instanceof codecell.CodeCell) {
1224 cell.set_text(upper_text+'\n'+text);
1227 cell.set_text(upper_text+'\n'+text);
1225 } else if ((cell instanceof mdc) || (cell instanceof rc)) {
1228 } else if ((cell instanceof mdc) || (cell instanceof rc)) {
1226 cell.unrender(); // Must unrender before we set_text.
1229 cell.unrender(); // Must unrender before we set_text.
1227 cell.set_text(upper_text+'\n\n'+text);
1230 cell.set_text(upper_text+'\n\n'+text);
1228 if (render) {
1231 if (render) {
1229 // The rendered state of the final cell should match
1232 // The rendered state of the final cell should match
1230 // that of the original selected cell;
1233 // that of the original selected cell;
1231 cell.render();
1234 cell.render();
1232 }
1235 }
1233 }
1236 }
1234 this.delete_cell(index-1);
1237 this.delete_cell(index-1);
1235 this.select(this.find_cell_index(cell));
1238 this.select(this.find_cell_index(cell));
1236 }
1239 }
1237 };
1240 };
1238
1241
1239 /**
1242 /**
1240 * Combine the selected cell into the cell below it.
1243 * Combine the selected cell into the cell below it.
1241 *
1244 *
1242 * @method merge_cell_below
1245 * @method merge_cell_below
1243 */
1246 */
1244 Notebook.prototype.merge_cell_below = function () {
1247 Notebook.prototype.merge_cell_below = function () {
1245 var mdc = cells.MarkdownCell;
1248 var mdc = textcell.MarkdownCell;
1246 var rc = cells.RawCell;
1249 var rc = textcell.RawCell;
1247 var index = this.get_selected_index();
1250 var index = this.get_selected_index();
1248 var cell = this.get_cell(index);
1251 var cell = this.get_cell(index);
1249 var render = cell.rendered;
1252 var render = cell.rendered;
1250 if (!cell.is_mergeable()) {
1253 if (!cell.is_mergeable()) {
1251 return;
1254 return;
1252 }
1255 }
1253 if (index < this.ncells()-1) {
1256 if (index < this.ncells()-1) {
1254 var lower_cell = this.get_cell(index+1);
1257 var lower_cell = this.get_cell(index+1);
1255 if (!lower_cell.is_mergeable()) {
1258 if (!lower_cell.is_mergeable()) {
1256 return;
1259 return;
1257 }
1260 }
1258 var lower_text = lower_cell.get_text();
1261 var lower_text = lower_cell.get_text();
1259 var text = cell.get_text();
1262 var text = cell.get_text();
1260 if (cell instanceof codecell.CodeCell) {
1263 if (cell instanceof codecell.CodeCell) {
1261 cell.set_text(text+'\n'+lower_text);
1264 cell.set_text(text+'\n'+lower_text);
1262 } else if ((cell instanceof mdc) || (cell instanceof rc)) {
1265 } else if ((cell instanceof mdc) || (cell instanceof rc)) {
1263 cell.unrender(); // Must unrender before we set_text.
1266 cell.unrender(); // Must unrender before we set_text.
1264 cell.set_text(text+'\n\n'+lower_text);
1267 cell.set_text(text+'\n\n'+lower_text);
1265 if (render) {
1268 if (render) {
1266 // The rendered state of the final cell should match
1269 // The rendered state of the final cell should match
1267 // that of the original selected cell;
1270 // that of the original selected cell;
1268 cell.render();
1271 cell.render();
1269 }
1272 }
1270 }
1273 }
1271 this.delete_cell(index+1);
1274 this.delete_cell(index+1);
1272 this.select(this.find_cell_index(cell));
1275 this.select(this.find_cell_index(cell));
1273 }
1276 }
1274 };
1277 };
1275
1278
1276
1279
1277 // Cell collapsing and output clearing
1280 // Cell collapsing and output clearing
1278
1281
1279 /**
1282 /**
1280 * Hide a cell's output.
1283 * Hide a cell's output.
1281 *
1284 *
1282 * @method collapse_output
1285 * @method collapse_output
1283 * @param {Number} index A cell's numeric index
1286 * @param {Number} index A cell's numeric index
1284 */
1287 */
1285 Notebook.prototype.collapse_output = function (index) {
1288 Notebook.prototype.collapse_output = function (index) {
1286 var i = this.index_or_selected(index);
1289 var i = this.index_or_selected(index);
1287 var cell = this.get_cell(i);
1290 var cell = this.get_cell(i);
1288 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1291 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1289 cell.collapse_output();
1292 cell.collapse_output();
1290 this.set_dirty(true);
1293 this.set_dirty(true);
1291 }
1294 }
1292 };
1295 };
1293
1296
1294 /**
1297 /**
1295 * Hide each code cell's output area.
1298 * Hide each code cell's output area.
1296 *
1299 *
1297 * @method collapse_all_output
1300 * @method collapse_all_output
1298 */
1301 */
1299 Notebook.prototype.collapse_all_output = function () {
1302 Notebook.prototype.collapse_all_output = function () {
1300 $.map(this.get_cells(), function (cell, i) {
1303 $.map(this.get_cells(), function (cell, i) {
1301 if (cell instanceof codecell.CodeCell) {
1304 if (cell instanceof codecell.CodeCell) {
1302 cell.collapse_output();
1305 cell.collapse_output();
1303 }
1306 }
1304 });
1307 });
1305 // this should not be set if the `collapse` key is removed from nbformat
1308 // this should not be set if the `collapse` key is removed from nbformat
1306 this.set_dirty(true);
1309 this.set_dirty(true);
1307 };
1310 };
1308
1311
1309 /**
1312 /**
1310 * Show a cell's output.
1313 * Show a cell's output.
1311 *
1314 *
1312 * @method expand_output
1315 * @method expand_output
1313 * @param {Number} index A cell's numeric index
1316 * @param {Number} index A cell's numeric index
1314 */
1317 */
1315 Notebook.prototype.expand_output = function (index) {
1318 Notebook.prototype.expand_output = function (index) {
1316 var i = this.index_or_selected(index);
1319 var i = this.index_or_selected(index);
1317 var cell = this.get_cell(i);
1320 var cell = this.get_cell(i);
1318 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1321 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1319 cell.expand_output();
1322 cell.expand_output();
1320 this.set_dirty(true);
1323 this.set_dirty(true);
1321 }
1324 }
1322 };
1325 };
1323
1326
1324 /**
1327 /**
1325 * Expand each code cell's output area, and remove scrollbars.
1328 * Expand each code cell's output area, and remove scrollbars.
1326 *
1329 *
1327 * @method expand_all_output
1330 * @method expand_all_output
1328 */
1331 */
1329 Notebook.prototype.expand_all_output = function () {
1332 Notebook.prototype.expand_all_output = function () {
1330 $.map(this.get_cells(), function (cell, i) {
1333 $.map(this.get_cells(), function (cell, i) {
1331 if (cell instanceof codecell.CodeCell) {
1334 if (cell instanceof codecell.CodeCell) {
1332 cell.expand_output();
1335 cell.expand_output();
1333 }
1336 }
1334 });
1337 });
1335 // this should not be set if the `collapse` key is removed from nbformat
1338 // this should not be set if the `collapse` key is removed from nbformat
1336 this.set_dirty(true);
1339 this.set_dirty(true);
1337 };
1340 };
1338
1341
1339 /**
1342 /**
1340 * Clear the selected CodeCell's output area.
1343 * Clear the selected CodeCell's output area.
1341 *
1344 *
1342 * @method clear_output
1345 * @method clear_output
1343 * @param {Number} index A cell's numeric index
1346 * @param {Number} index A cell's numeric index
1344 */
1347 */
1345 Notebook.prototype.clear_output = function (index) {
1348 Notebook.prototype.clear_output = function (index) {
1346 var i = this.index_or_selected(index);
1349 var i = this.index_or_selected(index);
1347 var cell = this.get_cell(i);
1350 var cell = this.get_cell(i);
1348 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1351 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1349 cell.clear_output();
1352 cell.clear_output();
1350 this.set_dirty(true);
1353 this.set_dirty(true);
1351 }
1354 }
1352 };
1355 };
1353
1356
1354 /**
1357 /**
1355 * Clear each code cell's output area.
1358 * Clear each code cell's output area.
1356 *
1359 *
1357 * @method clear_all_output
1360 * @method clear_all_output
1358 */
1361 */
1359 Notebook.prototype.clear_all_output = function () {
1362 Notebook.prototype.clear_all_output = function () {
1360 $.map(this.get_cells(), function (cell, i) {
1363 $.map(this.get_cells(), function (cell, i) {
1361 if (cell instanceof codecell.CodeCell) {
1364 if (cell instanceof codecell.CodeCell) {
1362 cell.clear_output();
1365 cell.clear_output();
1363 }
1366 }
1364 });
1367 });
1365 this.set_dirty(true);
1368 this.set_dirty(true);
1366 };
1369 };
1367
1370
1368 /**
1371 /**
1369 * Scroll the selected CodeCell's output area.
1372 * Scroll the selected CodeCell's output area.
1370 *
1373 *
1371 * @method scroll_output
1374 * @method scroll_output
1372 * @param {Number} index A cell's numeric index
1375 * @param {Number} index A cell's numeric index
1373 */
1376 */
1374 Notebook.prototype.scroll_output = function (index) {
1377 Notebook.prototype.scroll_output = function (index) {
1375 var i = this.index_or_selected(index);
1378 var i = this.index_or_selected(index);
1376 var cell = this.get_cell(i);
1379 var cell = this.get_cell(i);
1377 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1380 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1378 cell.scroll_output();
1381 cell.scroll_output();
1379 this.set_dirty(true);
1382 this.set_dirty(true);
1380 }
1383 }
1381 };
1384 };
1382
1385
1383 /**
1386 /**
1384 * Expand each code cell's output area, and add a scrollbar for long output.
1387 * Expand each code cell's output area, and add a scrollbar for long output.
1385 *
1388 *
1386 * @method scroll_all_output
1389 * @method scroll_all_output
1387 */
1390 */
1388 Notebook.prototype.scroll_all_output = function () {
1391 Notebook.prototype.scroll_all_output = function () {
1389 $.map(this.get_cells(), function (cell, i) {
1392 $.map(this.get_cells(), function (cell, i) {
1390 if (cell instanceof codecell.CodeCell) {
1393 if (cell instanceof codecell.CodeCell) {
1391 cell.scroll_output();
1394 cell.scroll_output();
1392 }
1395 }
1393 });
1396 });
1394 // this should not be set if the `collapse` key is removed from nbformat
1397 // this should not be set if the `collapse` key is removed from nbformat
1395 this.set_dirty(true);
1398 this.set_dirty(true);
1396 };
1399 };
1397
1400
1398 /** Toggle whether a cell's output is collapsed or expanded.
1401 /** Toggle whether a cell's output is collapsed or expanded.
1399 *
1402 *
1400 * @method toggle_output
1403 * @method toggle_output
1401 * @param {Number} index A cell's numeric index
1404 * @param {Number} index A cell's numeric index
1402 */
1405 */
1403 Notebook.prototype.toggle_output = function (index) {
1406 Notebook.prototype.toggle_output = function (index) {
1404 var i = this.index_or_selected(index);
1407 var i = this.index_or_selected(index);
1405 var cell = this.get_cell(i);
1408 var cell = this.get_cell(i);
1406 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1409 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1407 cell.toggle_output();
1410 cell.toggle_output();
1408 this.set_dirty(true);
1411 this.set_dirty(true);
1409 }
1412 }
1410 };
1413 };
1411
1414
1412 /**
1415 /**
1413 * Hide/show the output of all cells.
1416 * Hide/show the output of all cells.
1414 *
1417 *
1415 * @method toggle_all_output
1418 * @method toggle_all_output
1416 */
1419 */
1417 Notebook.prototype.toggle_all_output = function () {
1420 Notebook.prototype.toggle_all_output = function () {
1418 $.map(this.get_cells(), function (cell, i) {
1421 $.map(this.get_cells(), function (cell, i) {
1419 if (cell instanceof codecell.CodeCell) {
1422 if (cell instanceof codecell.CodeCell) {
1420 cell.toggle_output();
1423 cell.toggle_output();
1421 }
1424 }
1422 });
1425 });
1423 // this should not be set if the `collapse` key is removed from nbformat
1426 // this should not be set if the `collapse` key is removed from nbformat
1424 this.set_dirty(true);
1427 this.set_dirty(true);
1425 };
1428 };
1426
1429
1427 /**
1430 /**
1428 * Toggle a scrollbar for long cell outputs.
1431 * Toggle a scrollbar for long cell outputs.
1429 *
1432 *
1430 * @method toggle_output_scroll
1433 * @method toggle_output_scroll
1431 * @param {Number} index A cell's numeric index
1434 * @param {Number} index A cell's numeric index
1432 */
1435 */
1433 Notebook.prototype.toggle_output_scroll = function (index) {
1436 Notebook.prototype.toggle_output_scroll = function (index) {
1434 var i = this.index_or_selected(index);
1437 var i = this.index_or_selected(index);
1435 var cell = this.get_cell(i);
1438 var cell = this.get_cell(i);
1436 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1439 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1437 cell.toggle_output_scroll();
1440 cell.toggle_output_scroll();
1438 this.set_dirty(true);
1441 this.set_dirty(true);
1439 }
1442 }
1440 };
1443 };
1441
1444
1442 /**
1445 /**
1443 * Toggle the scrolling of long output on all cells.
1446 * Toggle the scrolling of long output on all cells.
1444 *
1447 *
1445 * @method toggle_all_output_scrolling
1448 * @method toggle_all_output_scrolling
1446 */
1449 */
1447 Notebook.prototype.toggle_all_output_scroll = function () {
1450 Notebook.prototype.toggle_all_output_scroll = function () {
1448 $.map(this.get_cells(), function (cell, i) {
1451 $.map(this.get_cells(), function (cell, i) {
1449 if (cell instanceof codecell.CodeCell) {
1452 if (cell instanceof codecell.CodeCell) {
1450 cell.toggle_output_scroll();
1453 cell.toggle_output_scroll();
1451 }
1454 }
1452 });
1455 });
1453 // this should not be set if the `collapse` key is removed from nbformat
1456 // this should not be set if the `collapse` key is removed from nbformat
1454 this.set_dirty(true);
1457 this.set_dirty(true);
1455 };
1458 };
1456
1459
1457 // Other cell functions: line numbers, ...
1460 // Other cell functions: line numbers, ...
1458
1461
1459 /**
1462 /**
1460 * Toggle line numbers in the selected cell's input area.
1463 * Toggle line numbers in the selected cell's input area.
1461 *
1464 *
1462 * @method cell_toggle_line_numbers
1465 * @method cell_toggle_line_numbers
1463 */
1466 */
1464 Notebook.prototype.cell_toggle_line_numbers = function() {
1467 Notebook.prototype.cell_toggle_line_numbers = function() {
1465 this.get_selected_cell().toggle_line_numbers();
1468 this.get_selected_cell().toggle_line_numbers();
1466 };
1469 };
1467
1470
1468 // Session related things
1471 // Session related things
1469
1472
1470 /**
1473 /**
1471 * Start a new session and set it on each code cell.
1474 * Start a new session and set it on each code cell.
1472 *
1475 *
1473 * @method start_session
1476 * @method start_session
1474 */
1477 */
1475 Notebook.prototype.start_session = function () {
1478 Notebook.prototype.start_session = function () {
1476 this.session = new session.Session(this, {
1479 this.session = new session.Session(this, {
1477 base_url: base_url,
1480 base_url: base_url,
1478 notebook_path: notebook_path,
1481 notebook_path: notebook_path,
1479 notebook_name: notebook_name,
1482 notebook_name: notebook_name,
1480 notebook: this});
1483 notebook: this});
1481 this.session.start($.proxy(this._session_started, this));
1484 this.session.start($.proxy(this._session_started, this));
1482 };
1485 };
1483
1486
1484
1487
1485 /**
1488 /**
1486 * Once a session is started, link the code cells to the kernel and pass the
1489 * Once a session is started, link the code cells to the kernel and pass the
1487 * comm manager to the widget manager
1490 * comm manager to the widget manager
1488 *
1491 *
1489 */
1492 */
1490 Notebook.prototype._session_started = function(){
1493 Notebook.prototype._session_started = function(){
1491 this.kernel = this.session.kernel;
1494 this.kernel = this.session.kernel;
1492 var ncells = this.ncells();
1495 var ncells = this.ncells();
1493 for (var i=0; i<ncells; i++) {
1496 for (var i=0; i<ncells; i++) {
1494 var cell = this.get_cell(i);
1497 var cell = this.get_cell(i);
1495 if (cell instanceof codecell.CodeCell) {
1498 if (cell instanceof codecell.CodeCell) {
1496 cell.set_kernel(this.session.kernel);
1499 cell.set_kernel(this.session.kernel);
1497 }
1500 }
1498 }
1501 }
1499 };
1502 };
1500
1503
1501 /**
1504 /**
1502 * Prompt the user to restart the IPython kernel.
1505 * Prompt the user to restart the IPython kernel.
1503 *
1506 *
1504 * @method restart_kernel
1507 * @method restart_kernel
1505 */
1508 */
1506 Notebook.prototype.restart_kernel = function () {
1509 Notebook.prototype.restart_kernel = function () {
1507 var that = this;
1510 var that = this;
1508 dialog.modal({
1511 dialog.modal({
1509 title : "Restart kernel or continue running?",
1512 title : "Restart kernel or continue running?",
1510 body : $("<p/>").text(
1513 body : $("<p/>").text(
1511 'Do you want to restart the current kernel? You will lose all variables defined in it.'
1514 'Do you want to restart the current kernel? You will lose all variables defined in it.'
1512 ),
1515 ),
1513 buttons : {
1516 buttons : {
1514 "Continue running" : {},
1517 "Continue running" : {},
1515 "Restart" : {
1518 "Restart" : {
1516 "class" : "btn-danger",
1519 "class" : "btn-danger",
1517 "click" : function() {
1520 "click" : function() {
1518 that.session.restart_kernel();
1521 that.session.restart_kernel();
1519 }
1522 }
1520 }
1523 }
1521 }
1524 }
1522 });
1525 });
1523 };
1526 };
1524
1527
1525 /**
1528 /**
1526 * Execute or render cell outputs and go into command mode.
1529 * Execute or render cell outputs and go into command mode.
1527 *
1530 *
1528 * @method execute_cell
1531 * @method execute_cell
1529 */
1532 */
1530 Notebook.prototype.execute_cell = function () {
1533 Notebook.prototype.execute_cell = function () {
1531 // mode = shift, ctrl, alt
1534 // mode = shift, ctrl, alt
1532 var cell = this.get_selected_cell();
1535 var cell = this.get_selected_cell();
1533 var cell_index = this.find_cell_index(cell);
1536 var cell_index = this.find_cell_index(cell);
1534
1537
1535 cell.execute();
1538 cell.execute();
1536 this.command_mode();
1539 this.command_mode();
1537 this.set_dirty(true);
1540 this.set_dirty(true);
1538 };
1541 };
1539
1542
1540 /**
1543 /**
1541 * Execute or render cell outputs and insert a new cell below.
1544 * Execute or render cell outputs and insert a new cell below.
1542 *
1545 *
1543 * @method execute_cell_and_insert_below
1546 * @method execute_cell_and_insert_below
1544 */
1547 */
1545 Notebook.prototype.execute_cell_and_insert_below = function () {
1548 Notebook.prototype.execute_cell_and_insert_below = function () {
1546 var cell = this.get_selected_cell();
1549 var cell = this.get_selected_cell();
1547 var cell_index = this.find_cell_index(cell);
1550 var cell_index = this.find_cell_index(cell);
1548
1551
1549 cell.execute();
1552 cell.execute();
1550
1553
1551 // If we are at the end always insert a new cell and return
1554 // If we are at the end always insert a new cell and return
1552 if (cell_index === (this.ncells()-1)) {
1555 if (cell_index === (this.ncells()-1)) {
1553 this.command_mode();
1556 this.command_mode();
1554 this.insert_cell_below();
1557 this.insert_cell_below();
1555 this.select(cell_index+1);
1558 this.select(cell_index+1);
1556 this.edit_mode();
1559 this.edit_mode();
1557 this.scroll_to_bottom();
1560 this.scroll_to_bottom();
1558 this.set_dirty(true);
1561 this.set_dirty(true);
1559 return;
1562 return;
1560 }
1563 }
1561
1564
1562 this.command_mode();
1565 this.command_mode();
1563 this.insert_cell_below();
1566 this.insert_cell_below();
1564 this.select(cell_index+1);
1567 this.select(cell_index+1);
1565 this.edit_mode();
1568 this.edit_mode();
1566 this.set_dirty(true);
1569 this.set_dirty(true);
1567 };
1570 };
1568
1571
1569 /**
1572 /**
1570 * Execute or render cell outputs and select the next cell.
1573 * Execute or render cell outputs and select the next cell.
1571 *
1574 *
1572 * @method execute_cell_and_select_below
1575 * @method execute_cell_and_select_below
1573 */
1576 */
1574 Notebook.prototype.execute_cell_and_select_below = function () {
1577 Notebook.prototype.execute_cell_and_select_below = function () {
1575
1578
1576 var cell = this.get_selected_cell();
1579 var cell = this.get_selected_cell();
1577 var cell_index = this.find_cell_index(cell);
1580 var cell_index = this.find_cell_index(cell);
1578
1581
1579 cell.execute();
1582 cell.execute();
1580
1583
1581 // If we are at the end always insert a new cell and return
1584 // If we are at the end always insert a new cell and return
1582 if (cell_index === (this.ncells()-1)) {
1585 if (cell_index === (this.ncells()-1)) {
1583 this.command_mode();
1586 this.command_mode();
1584 this.insert_cell_below();
1587 this.insert_cell_below();
1585 this.select(cell_index+1);
1588 this.select(cell_index+1);
1586 this.edit_mode();
1589 this.edit_mode();
1587 this.scroll_to_bottom();
1590 this.scroll_to_bottom();
1588 this.set_dirty(true);
1591 this.set_dirty(true);
1589 return;
1592 return;
1590 }
1593 }
1591
1594
1592 this.command_mode();
1595 this.command_mode();
1593 this.select(cell_index+1);
1596 this.select(cell_index+1);
1594 this.focus_cell();
1597 this.focus_cell();
1595 this.set_dirty(true);
1598 this.set_dirty(true);
1596 };
1599 };
1597
1600
1598 /**
1601 /**
1599 * Execute all cells below the selected cell.
1602 * Execute all cells below the selected cell.
1600 *
1603 *
1601 * @method execute_cells_below
1604 * @method execute_cells_below
1602 */
1605 */
1603 Notebook.prototype.execute_cells_below = function () {
1606 Notebook.prototype.execute_cells_below = function () {
1604 this.execute_cell_range(this.get_selected_index(), this.ncells());
1607 this.execute_cell_range(this.get_selected_index(), this.ncells());
1605 this.scroll_to_bottom();
1608 this.scroll_to_bottom();
1606 };
1609 };
1607
1610
1608 /**
1611 /**
1609 * Execute all cells above the selected cell.
1612 * Execute all cells above the selected cell.
1610 *
1613 *
1611 * @method execute_cells_above
1614 * @method execute_cells_above
1612 */
1615 */
1613 Notebook.prototype.execute_cells_above = function () {
1616 Notebook.prototype.execute_cells_above = function () {
1614 this.execute_cell_range(0, this.get_selected_index());
1617 this.execute_cell_range(0, this.get_selected_index());
1615 };
1618 };
1616
1619
1617 /**
1620 /**
1618 * Execute all cells.
1621 * Execute all cells.
1619 *
1622 *
1620 * @method execute_all_cells
1623 * @method execute_all_cells
1621 */
1624 */
1622 Notebook.prototype.execute_all_cells = function () {
1625 Notebook.prototype.execute_all_cells = function () {
1623 this.execute_cell_range(0, this.ncells());
1626 this.execute_cell_range(0, this.ncells());
1624 this.scroll_to_bottom();
1627 this.scroll_to_bottom();
1625 };
1628 };
1626
1629
1627 /**
1630 /**
1628 * Execute a contiguous range of cells.
1631 * Execute a contiguous range of cells.
1629 *
1632 *
1630 * @method execute_cell_range
1633 * @method execute_cell_range
1631 * @param {Number} start Index of the first cell to execute (inclusive)
1634 * @param {Number} start Index of the first cell to execute (inclusive)
1632 * @param {Number} end Index of the last cell to execute (exclusive)
1635 * @param {Number} end Index of the last cell to execute (exclusive)
1633 */
1636 */
1634 Notebook.prototype.execute_cell_range = function (start, end) {
1637 Notebook.prototype.execute_cell_range = function (start, end) {
1635 this.command_mode();
1638 this.command_mode();
1636 for (var i=start; i<end; i++) {
1639 for (var i=start; i<end; i++) {
1637 this.select(i);
1640 this.select(i);
1638 this.execute_cell();
1641 this.execute_cell();
1639 }
1642 }
1640 };
1643 };
1641
1644
1642 // Persistance and loading
1645 // Persistance and loading
1643
1646
1644 /**
1647 /**
1645 * Getter method for this notebook's name.
1648 * Getter method for this notebook's name.
1646 *
1649 *
1647 * @method get_notebook_name
1650 * @method get_notebook_name
1648 * @return {String} This notebook's name (excluding file extension)
1651 * @return {String} This notebook's name (excluding file extension)
1649 */
1652 */
1650 Notebook.prototype.get_notebook_name = function () {
1653 Notebook.prototype.get_notebook_name = function () {
1651 var nbname = this.notebook_name.substring(0,this.notebook_name.length-6);
1654 var nbname = this.notebook_name.substring(0,this.notebook_name.length-6);
1652 return nbname;
1655 return nbname;
1653 };
1656 };
1654
1657
1655 /**
1658 /**
1656 * Setter method for this notebook's name.
1659 * Setter method for this notebook's name.
1657 *
1660 *
1658 * @method set_notebook_name
1661 * @method set_notebook_name
1659 * @param {String} name A new name for this notebook
1662 * @param {String} name A new name for this notebook
1660 */
1663 */
1661 Notebook.prototype.set_notebook_name = function (name) {
1664 Notebook.prototype.set_notebook_name = function (name) {
1662 this.notebook_name = name;
1665 this.notebook_name = name;
1663 };
1666 };
1664
1667
1665 /**
1668 /**
1666 * Check that a notebook's name is valid.
1669 * Check that a notebook's name is valid.
1667 *
1670 *
1668 * @method test_notebook_name
1671 * @method test_notebook_name
1669 * @param {String} nbname A name for this notebook
1672 * @param {String} nbname A name for this notebook
1670 * @return {Boolean} True if the name is valid, false if invalid
1673 * @return {Boolean} True if the name is valid, false if invalid
1671 */
1674 */
1672 Notebook.prototype.test_notebook_name = function (nbname) {
1675 Notebook.prototype.test_notebook_name = function (nbname) {
1673 nbname = nbname || '';
1676 nbname = nbname || '';
1674 if (nbname.length>0 && !this.notebook_name_blacklist_re.test(nbname)) {
1677 if (nbname.length>0 && !this.notebook_name_blacklist_re.test(nbname)) {
1675 return true;
1678 return true;
1676 } else {
1679 } else {
1677 return false;
1680 return false;
1678 }
1681 }
1679 };
1682 };
1680
1683
1681 /**
1684 /**
1682 * Load a notebook from JSON (.ipynb).
1685 * Load a notebook from JSON (.ipynb).
1683 *
1686 *
1684 * This currently handles one worksheet: others are deleted.
1687 * This currently handles one worksheet: others are deleted.
1685 *
1688 *
1686 * @method fromJSON
1689 * @method fromJSON
1687 * @param {Object} data JSON representation of a notebook
1690 * @param {Object} data JSON representation of a notebook
1688 */
1691 */
1689 Notebook.prototype.fromJSON = function (data) {
1692 Notebook.prototype.fromJSON = function (data) {
1690 var content = data.content;
1693 var content = data.content;
1691 var ncells = this.ncells();
1694 var ncells = this.ncells();
1692 var i;
1695 var i;
1693 for (i=0; i<ncells; i++) {
1696 for (i=0; i<ncells; i++) {
1694 // Always delete cell 0 as they get renumbered as they are deleted.
1697 // Always delete cell 0 as they get renumbered as they are deleted.
1695 this.delete_cell(0);
1698 this.delete_cell(0);
1696 }
1699 }
1697 // Save the metadata and name.
1700 // Save the metadata and name.
1698 this.metadata = content.metadata;
1701 this.metadata = content.metadata;
1699 this.notebook_name = data.name;
1702 this.notebook_name = data.name;
1700 var trusted = true;
1703 var trusted = true;
1701 // Only handle 1 worksheet for now.
1704 // Only handle 1 worksheet for now.
1702 var worksheet = content.worksheets[0];
1705 var worksheet = content.worksheets[0];
1703 if (worksheet !== undefined) {
1706 if (worksheet !== undefined) {
1704 if (worksheet.metadata) {
1707 if (worksheet.metadata) {
1705 this.worksheet_metadata = worksheet.metadata;
1708 this.worksheet_metadata = worksheet.metadata;
1706 }
1709 }
1707 var new_cells = worksheet.cells;
1710 var new_cells = worksheet.cells;
1708 ncells = new_cells.length;
1711 ncells = new_cells.length;
1709 var cell_data = null;
1712 var cell_data = null;
1710 var new_cell = null;
1713 var new_cell = null;
1711 for (i=0; i<ncells; i++) {
1714 for (i=0; i<ncells; i++) {
1712 cell_data = new_cells[i];
1715 cell_data = new_cells[i];
1713 // VERSIONHACK: plaintext -> raw
1716 // VERSIONHACK: plaintext -> raw
1714 // handle never-released plaintext name for raw cells
1717 // handle never-released plaintext name for raw cells
1715 if (cell_data.cell_type === 'plaintext'){
1718 if (cell_data.cell_type === 'plaintext'){
1716 cell_data.cell_type = 'raw';
1719 cell_data.cell_type = 'raw';
1717 }
1720 }
1718
1721
1719 new_cell = this.insert_cell_at_index(cell_data.cell_type, i);
1722 new_cell = this.insert_cell_at_index(cell_data.cell_type, i);
1720 new_cell.fromJSON(cell_data);
1723 new_cell.fromJSON(cell_data);
1721 if (new_cell.cell_type == 'code' && !new_cell.output_area.trusted) {
1724 if (new_cell.cell_type == 'code' && !new_cell.output_area.trusted) {
1722 trusted = false;
1725 trusted = false;
1723 }
1726 }
1724 }
1727 }
1725 }
1728 }
1726 if (trusted != this.trusted) {
1729 if (trusted != this.trusted) {
1727 this.trusted = trusted;
1730 this.trusted = trusted;
1728 this.events.trigger("trust_changed.Notebook", trusted);
1731 this.events.trigger("trust_changed.Notebook", trusted);
1729 }
1732 }
1730 if (content.worksheets.length > 1) {
1733 if (content.worksheets.length > 1) {
1731 dialog.modal({
1734 dialog.modal({
1732 title : "Multiple worksheets",
1735 title : "Multiple worksheets",
1733 body : "This notebook has " + data.worksheets.length + " worksheets, " +
1736 body : "This notebook has " + data.worksheets.length + " worksheets, " +
1734 "but this version of IPython can only handle the first. " +
1737 "but this version of IPython can only handle the first. " +
1735 "If you save this notebook, worksheets after the first will be lost.",
1738 "If you save this notebook, worksheets after the first will be lost.",
1736 buttons : {
1739 buttons : {
1737 OK : {
1740 OK : {
1738 class : "btn-danger"
1741 class : "btn-danger"
1739 }
1742 }
1740 }
1743 }
1741 });
1744 });
1742 }
1745 }
1743 };
1746 };
1744
1747
1745 /**
1748 /**
1746 * Dump this notebook into a JSON-friendly object.
1749 * Dump this notebook into a JSON-friendly object.
1747 *
1750 *
1748 * @method toJSON
1751 * @method toJSON
1749 * @return {Object} A JSON-friendly representation of this notebook.
1752 * @return {Object} A JSON-friendly representation of this notebook.
1750 */
1753 */
1751 Notebook.prototype.toJSON = function () {
1754 Notebook.prototype.toJSON = function () {
1752 var cells = this.get_cells();
1755 var cells = this.get_cells();
1753 var ncells = cells.length;
1756 var ncells = cells.length;
1754 var cell_array = new Array(ncells);
1757 var cell_array = new Array(ncells);
1755 var trusted = true;
1758 var trusted = true;
1756 for (var i=0; i<ncells; i++) {
1759 for (var i=0; i<ncells; i++) {
1757 var cell = cells[i];
1760 var cell = cells[i];
1758 if (cell.cell_type == 'code' && !cell.output_area.trusted) {
1761 if (cell.cell_type == 'code' && !cell.output_area.trusted) {
1759 trusted = false;
1762 trusted = false;
1760 }
1763 }
1761 cell_array[i] = cell.toJSON();
1764 cell_array[i] = cell.toJSON();
1762 }
1765 }
1763 var data = {
1766 var data = {
1764 // Only handle 1 worksheet for now.
1767 // Only handle 1 worksheet for now.
1765 worksheets : [{
1768 worksheets : [{
1766 cells: cell_array,
1769 cells: cell_array,
1767 metadata: this.worksheet_metadata
1770 metadata: this.worksheet_metadata
1768 }],
1771 }],
1769 metadata : this.metadata
1772 metadata : this.metadata
1770 };
1773 };
1771 if (trusted != this.trusted) {
1774 if (trusted != this.trusted) {
1772 this.trusted = trusted;
1775 this.trusted = trusted;
1773 this.events.trigger("trust_changed.Notebook", trusted);
1776 this.events.trigger("trust_changed.Notebook", trusted);
1774 }
1777 }
1775 return data;
1778 return data;
1776 };
1779 };
1777
1780
1778 /**
1781 /**
1779 * Start an autosave timer, for periodically saving the notebook.
1782 * Start an autosave timer, for periodically saving the notebook.
1780 *
1783 *
1781 * @method set_autosave_interval
1784 * @method set_autosave_interval
1782 * @param {Integer} interval the autosave interval in milliseconds
1785 * @param {Integer} interval the autosave interval in milliseconds
1783 */
1786 */
1784 Notebook.prototype.set_autosave_interval = function (interval) {
1787 Notebook.prototype.set_autosave_interval = function (interval) {
1785 var that = this;
1788 var that = this;
1786 // clear previous interval, so we don't get simultaneous timers
1789 // clear previous interval, so we don't get simultaneous timers
1787 if (this.autosave_timer) {
1790 if (this.autosave_timer) {
1788 clearInterval(this.autosave_timer);
1791 clearInterval(this.autosave_timer);
1789 }
1792 }
1790
1793
1791 this.autosave_interval = this.minimum_autosave_interval = interval;
1794 this.autosave_interval = this.minimum_autosave_interval = interval;
1792 if (interval) {
1795 if (interval) {
1793 this.autosave_timer = setInterval(function() {
1796 this.autosave_timer = setInterval(function() {
1794 if (that.dirty) {
1797 if (that.dirty) {
1795 that.save_notebook();
1798 that.save_notebook();
1796 }
1799 }
1797 }, interval);
1800 }, interval);
1798 this.events.trigger("autosave_enabled.Notebook", interval);
1801 this.events.trigger("autosave_enabled.Notebook", interval);
1799 } else {
1802 } else {
1800 this.autosave_timer = null;
1803 this.autosave_timer = null;
1801 this.events.trigger("autosave_disabled.Notebook");
1804 this.events.trigger("autosave_disabled.Notebook");
1802 }
1805 }
1803 };
1806 };
1804
1807
1805 /**
1808 /**
1806 * Save this notebook on the server. This becomes a notebook instance's
1809 * Save this notebook on the server. This becomes a notebook instance's
1807 * .save_notebook method *after* the entire notebook has been loaded.
1810 * .save_notebook method *after* the entire notebook has been loaded.
1808 *
1811 *
1809 * @method save_notebook
1812 * @method save_notebook
1810 */
1813 */
1811 Notebook.prototype.save_notebook = function (extra_settings) {
1814 Notebook.prototype.save_notebook = function (extra_settings) {
1812 // Create a JSON model to be sent to the server.
1815 // Create a JSON model to be sent to the server.
1813 var model = {};
1816 var model = {};
1814 model.name = this.notebook_name;
1817 model.name = this.notebook_name;
1815 model.path = this.notebook_path;
1818 model.path = this.notebook_path;
1816 model.content = this.toJSON();
1819 model.content = this.toJSON();
1817 model.content.nbformat = this.nbformat;
1820 model.content.nbformat = this.nbformat;
1818 model.content.nbformat_minor = this.nbformat_minor;
1821 model.content.nbformat_minor = this.nbformat_minor;
1819 // time the ajax call for autosave tuning purposes.
1822 // time the ajax call for autosave tuning purposes.
1820 var start = new Date().getTime();
1823 var start = new Date().getTime();
1821 // We do the call with settings so we can set cache to false.
1824 // We do the call with settings so we can set cache to false.
1822 var settings = {
1825 var settings = {
1823 processData : false,
1826 processData : false,
1824 cache : false,
1827 cache : false,
1825 type : "PUT",
1828 type : "PUT",
1826 data : JSON.stringify(model),
1829 data : JSON.stringify(model),
1827 headers : {'Content-Type': 'application/json'},
1830 headers : {'Content-Type': 'application/json'},
1828 success : $.proxy(this.save_notebook_success, this, start),
1831 success : $.proxy(this.save_notebook_success, this, start),
1829 error : $.proxy(this.save_notebook_error, this)
1832 error : $.proxy(this.save_notebook_error, this)
1830 };
1833 };
1831 if (extra_settings) {
1834 if (extra_settings) {
1832 for (var key in extra_settings) {
1835 for (var key in extra_settings) {
1833 settings[key] = extra_settings[key];
1836 settings[key] = extra_settings[key];
1834 }
1837 }
1835 }
1838 }
1836 this.events.trigger('notebook_saving.Notebook');
1839 this.events.trigger('notebook_saving.Notebook');
1837 var url = utils.url_join_encode(
1840 var url = utils.url_join_encode(
1838 this.base_url,
1841 this.base_url,
1839 'api/notebooks',
1842 'api/notebooks',
1840 this.notebook_path,
1843 this.notebook_path,
1841 this.notebook_name
1844 this.notebook_name
1842 );
1845 );
1843 $.ajax(url, settings);
1846 $.ajax(url, settings);
1844 };
1847 };
1845
1848
1846 /**
1849 /**
1847 * Success callback for saving a notebook.
1850 * Success callback for saving a notebook.
1848 *
1851 *
1849 * @method save_notebook_success
1852 * @method save_notebook_success
1850 * @param {Integer} start the time when the save request started
1853 * @param {Integer} start the time when the save request started
1851 * @param {Object} data JSON representation of a notebook
1854 * @param {Object} data JSON representation of a notebook
1852 * @param {String} status Description of response status
1855 * @param {String} status Description of response status
1853 * @param {jqXHR} xhr jQuery Ajax object
1856 * @param {jqXHR} xhr jQuery Ajax object
1854 */
1857 */
1855 Notebook.prototype.save_notebook_success = function (start, data, status, xhr) {
1858 Notebook.prototype.save_notebook_success = function (start, data, status, xhr) {
1856 this.set_dirty(false);
1859 this.set_dirty(false);
1857 this.events.trigger('notebook_saved.Notebook');
1860 this.events.trigger('notebook_saved.Notebook');
1858 this._update_autosave_interval(start);
1861 this._update_autosave_interval(start);
1859 if (this._checkpoint_after_save) {
1862 if (this._checkpoint_after_save) {
1860 this.create_checkpoint();
1863 this.create_checkpoint();
1861 this._checkpoint_after_save = false;
1864 this._checkpoint_after_save = false;
1862 }
1865 }
1863 };
1866 };
1864
1867
1865 /**
1868 /**
1866 * update the autosave interval based on how long the last save took
1869 * update the autosave interval based on how long the last save took
1867 *
1870 *
1868 * @method _update_autosave_interval
1871 * @method _update_autosave_interval
1869 * @param {Integer} timestamp when the save request started
1872 * @param {Integer} timestamp when the save request started
1870 */
1873 */
1871 Notebook.prototype._update_autosave_interval = function (start) {
1874 Notebook.prototype._update_autosave_interval = function (start) {
1872 var duration = (new Date().getTime() - start);
1875 var duration = (new Date().getTime() - start);
1873 if (this.autosave_interval) {
1876 if (this.autosave_interval) {
1874 // new save interval: higher of 10x save duration or parameter (default 30 seconds)
1877 // new save interval: higher of 10x save duration or parameter (default 30 seconds)
1875 var interval = Math.max(10 * duration, this.minimum_autosave_interval);
1878 var interval = Math.max(10 * duration, this.minimum_autosave_interval);
1876 // round to 10 seconds, otherwise we will be setting a new interval too often
1879 // round to 10 seconds, otherwise we will be setting a new interval too often
1877 interval = 10000 * Math.round(interval / 10000);
1880 interval = 10000 * Math.round(interval / 10000);
1878 // set new interval, if it's changed
1881 // set new interval, if it's changed
1879 if (interval != this.autosave_interval) {
1882 if (interval != this.autosave_interval) {
1880 this.set_autosave_interval(interval);
1883 this.set_autosave_interval(interval);
1881 }
1884 }
1882 }
1885 }
1883 };
1886 };
1884
1887
1885 /**
1888 /**
1886 * Failure callback for saving a notebook.
1889 * Failure callback for saving a notebook.
1887 *
1890 *
1888 * @method save_notebook_error
1891 * @method save_notebook_error
1889 * @param {jqXHR} xhr jQuery Ajax object
1892 * @param {jqXHR} xhr jQuery Ajax object
1890 * @param {String} status Description of response status
1893 * @param {String} status Description of response status
1891 * @param {String} error HTTP error message
1894 * @param {String} error HTTP error message
1892 */
1895 */
1893 Notebook.prototype.save_notebook_error = function (xhr, status, error) {
1896 Notebook.prototype.save_notebook_error = function (xhr, status, error) {
1894 this.events.trigger('notebook_save_failed.Notebook', [xhr, status, error]);
1897 this.events.trigger('notebook_save_failed.Notebook', [xhr, status, error]);
1895 };
1898 };
1896
1899
1897 /**
1900 /**
1898 * Explicitly trust the output of this notebook.
1901 * Explicitly trust the output of this notebook.
1899 *
1902 *
1900 * @method trust_notebook
1903 * @method trust_notebook
1901 */
1904 */
1902 Notebook.prototype.trust_notebook = function (extra_settings) {
1905 Notebook.prototype.trust_notebook = function (extra_settings) {
1903 var body = $("<div>").append($("<p>")
1906 var body = $("<div>").append($("<p>")
1904 .text("A trusted IPython notebook may execute hidden malicious code ")
1907 .text("A trusted IPython notebook may execute hidden malicious code ")
1905 .append($("<strong>")
1908 .append($("<strong>")
1906 .append(
1909 .append(
1907 $("<em>").text("when you open it")
1910 $("<em>").text("when you open it")
1908 )
1911 )
1909 ).append(".").append(
1912 ).append(".").append(
1910 " Selecting trust will immediately reload this notebook in a trusted state."
1913 " Selecting trust will immediately reload this notebook in a trusted state."
1911 ).append(
1914 ).append(
1912 " For more information, see the "
1915 " For more information, see the "
1913 ).append($("<a>").attr("href", "http://ipython.org/ipython-doc/2/notebook/security.html")
1916 ).append($("<a>").attr("href", "http://ipython.org/ipython-doc/2/notebook/security.html")
1914 .text("IPython security documentation")
1917 .text("IPython security documentation")
1915 ).append(".")
1918 ).append(".")
1916 );
1919 );
1917
1920
1918 var nb = this;
1921 var nb = this;
1919 dialog.modal({
1922 dialog.modal({
1920 title: "Trust this notebook?",
1923 title: "Trust this notebook?",
1921 body: body,
1924 body: body,
1922
1925
1923 buttons: {
1926 buttons: {
1924 Cancel : {},
1927 Cancel : {},
1925 Trust : {
1928 Trust : {
1926 class : "btn-danger",
1929 class : "btn-danger",
1927 click : function () {
1930 click : function () {
1928 var cells = nb.get_cells();
1931 var cells = nb.get_cells();
1929 for (var i = 0; i < cells.length; i++) {
1932 for (var i = 0; i < cells.length; i++) {
1930 var cell = cells[i];
1933 var cell = cells[i];
1931 if (cell.cell_type == 'code') {
1934 if (cell.cell_type == 'code') {
1932 cell.output_area.trusted = true;
1935 cell.output_area.trusted = true;
1933 }
1936 }
1934 }
1937 }
1935 this.events.on('notebook_saved.Notebook', function () {
1938 this.events.on('notebook_saved.Notebook', function () {
1936 window.location.reload();
1939 window.location.reload();
1937 });
1940 });
1938 nb.save_notebook();
1941 nb.save_notebook();
1939 }
1942 }
1940 }
1943 }
1941 }
1944 }
1942 });
1945 });
1943 };
1946 };
1944
1947
1945 Notebook.prototype.new_notebook = function(){
1948 Notebook.prototype.new_notebook = function(){
1946 var path = this.notebook_path;
1949 var path = this.notebook_path;
1947 var base_url = this.base_url;
1950 var base_url = this.base_url;
1948 var settings = {
1951 var settings = {
1949 processData : false,
1952 processData : false,
1950 cache : false,
1953 cache : false,
1951 type : "POST",
1954 type : "POST",
1952 dataType : "json",
1955 dataType : "json",
1953 async : false,
1956 async : false,
1954 success : function (data, status, xhr){
1957 success : function (data, status, xhr){
1955 var notebook_name = data.name;
1958 var notebook_name = data.name;
1956 window.open(
1959 window.open(
1957 utils.url_join_encode(
1960 utils.url_join_encode(
1958 base_url,
1961 base_url,
1959 'notebooks',
1962 'notebooks',
1960 path,
1963 path,
1961 notebook_name
1964 notebook_name
1962 ),
1965 ),
1963 '_blank'
1966 '_blank'
1964 );
1967 );
1965 },
1968 },
1966 error : utils.log_ajax_error,
1969 error : utils.log_ajax_error,
1967 };
1970 };
1968 var url = utils.url_join_encode(
1971 var url = utils.url_join_encode(
1969 base_url,
1972 base_url,
1970 'api/notebooks',
1973 'api/notebooks',
1971 path
1974 path
1972 );
1975 );
1973 $.ajax(url,settings);
1976 $.ajax(url,settings);
1974 };
1977 };
1975
1978
1976
1979
1977 Notebook.prototype.copy_notebook = function(){
1980 Notebook.prototype.copy_notebook = function(){
1978 var path = this.notebook_path;
1981 var path = this.notebook_path;
1979 var base_url = this.base_url;
1982 var base_url = this.base_url;
1980 var settings = {
1983 var settings = {
1981 processData : false,
1984 processData : false,
1982 cache : false,
1985 cache : false,
1983 type : "POST",
1986 type : "POST",
1984 dataType : "json",
1987 dataType : "json",
1985 data : JSON.stringify({copy_from : this.notebook_name}),
1988 data : JSON.stringify({copy_from : this.notebook_name}),
1986 async : false,
1989 async : false,
1987 success : function (data, status, xhr) {
1990 success : function (data, status, xhr) {
1988 window.open(utils.url_join_encode(
1991 window.open(utils.url_join_encode(
1989 base_url,
1992 base_url,
1990 'notebooks',
1993 'notebooks',
1991 data.path,
1994 data.path,
1992 data.name
1995 data.name
1993 ), '_blank');
1996 ), '_blank');
1994 },
1997 },
1995 error : utils.log_ajax_error,
1998 error : utils.log_ajax_error,
1996 };
1999 };
1997 var url = utils.url_join_encode(
2000 var url = utils.url_join_encode(
1998 base_url,
2001 base_url,
1999 'api/notebooks',
2002 'api/notebooks',
2000 path
2003 path
2001 );
2004 );
2002 $.ajax(url,settings);
2005 $.ajax(url,settings);
2003 };
2006 };
2004
2007
2005 Notebook.prototype.rename = function (nbname) {
2008 Notebook.prototype.rename = function (nbname) {
2006 var that = this;
2009 var that = this;
2007 if (!nbname.match(/\.ipynb$/)) {
2010 if (!nbname.match(/\.ipynb$/)) {
2008 nbname = nbname + ".ipynb";
2011 nbname = nbname + ".ipynb";
2009 }
2012 }
2010 var data = {name: nbname};
2013 var data = {name: nbname};
2011 var settings = {
2014 var settings = {
2012 processData : false,
2015 processData : false,
2013 cache : false,
2016 cache : false,
2014 type : "PATCH",
2017 type : "PATCH",
2015 data : JSON.stringify(data),
2018 data : JSON.stringify(data),
2016 dataType: "json",
2019 dataType: "json",
2017 headers : {'Content-Type': 'application/json'},
2020 headers : {'Content-Type': 'application/json'},
2018 success : $.proxy(that.rename_success, this),
2021 success : $.proxy(that.rename_success, this),
2019 error : $.proxy(that.rename_error, this)
2022 error : $.proxy(that.rename_error, this)
2020 };
2023 };
2021 this.events.trigger('rename_notebook.Notebook', data);
2024 this.events.trigger('rename_notebook.Notebook', data);
2022 var url = utils.url_join_encode(
2025 var url = utils.url_join_encode(
2023 this.base_url,
2026 this.base_url,
2024 'api/notebooks',
2027 'api/notebooks',
2025 this.notebook_path,
2028 this.notebook_path,
2026 this.notebook_name
2029 this.notebook_name
2027 );
2030 );
2028 $.ajax(url, settings);
2031 $.ajax(url, settings);
2029 };
2032 };
2030
2033
2031 Notebook.prototype.delete = function () {
2034 Notebook.prototype.delete = function () {
2032 var that = this;
2035 var that = this;
2033 var settings = {
2036 var settings = {
2034 processData : false,
2037 processData : false,
2035 cache : false,
2038 cache : false,
2036 type : "DELETE",
2039 type : "DELETE",
2037 dataType: "json",
2040 dataType: "json",
2038 error : utils.log_ajax_error,
2041 error : utils.log_ajax_error,
2039 };
2042 };
2040 var url = utils.url_join_encode(
2043 var url = utils.url_join_encode(
2041 this.base_url,
2044 this.base_url,
2042 'api/notebooks',
2045 'api/notebooks',
2043 this.notebook_path,
2046 this.notebook_path,
2044 this.notebook_name
2047 this.notebook_name
2045 );
2048 );
2046 $.ajax(url, settings);
2049 $.ajax(url, settings);
2047 };
2050 };
2048
2051
2049
2052
2050 Notebook.prototype.rename_success = function (json, status, xhr) {
2053 Notebook.prototype.rename_success = function (json, status, xhr) {
2051 var name = this.notebook_name = json.name;
2054 var name = this.notebook_name = json.name;
2052 var path = json.path;
2055 var path = json.path;
2053 this.session.rename_notebook(name, path);
2056 this.session.rename_notebook(name, path);
2054 this.events.trigger('notebook_renamed.Notebook', json);
2057 this.events.trigger('notebook_renamed.Notebook', json);
2055 };
2058 };
2056
2059
2057 Notebook.prototype.rename_error = function (xhr, status, error) {
2060 Notebook.prototype.rename_error = function (xhr, status, error) {
2058 var that = this;
2061 var that = this;
2059 var dialog_body = $('<div/>').append(
2062 var dialog_body = $('<div/>').append(
2060 $("<p/>").addClass("rename-message")
2063 $("<p/>").addClass("rename-message")
2061 .text('This notebook name already exists.')
2064 .text('This notebook name already exists.')
2062 );
2065 );
2063 this.events.trigger('notebook_rename_failed.Notebook', [xhr, status, error]);
2066 this.events.trigger('notebook_rename_failed.Notebook', [xhr, status, error]);
2064 dialog.modal({
2067 dialog.modal({
2065 title: "Notebook Rename Error!",
2068 title: "Notebook Rename Error!",
2066 body: dialog_body,
2069 body: dialog_body,
2067 buttons : {
2070 buttons : {
2068 "Cancel": {},
2071 "Cancel": {},
2069 "OK": {
2072 "OK": {
2070 class: "btn-primary",
2073 class: "btn-primary",
2071 click: function () {
2074 click: function () {
2072 this.save_widget.rename_notebook();
2075 this.save_widget.rename_notebook();
2073 }}
2076 }}
2074 },
2077 },
2075 open : function (event, ui) {
2078 open : function (event, ui) {
2076 var that = $(this);
2079 var that = $(this);
2077 // Upon ENTER, click the OK button.
2080 // Upon ENTER, click the OK button.
2078 that.find('input[type="text"]').keydown(function (event, ui) {
2081 that.find('input[type="text"]').keydown(function (event, ui) {
2079 if (event.which === this.keyboard.keycodes.enter) {
2082 if (event.which === this.keyboard.keycodes.enter) {
2080 that.find('.btn-primary').first().click();
2083 that.find('.btn-primary').first().click();
2081 }
2084 }
2082 });
2085 });
2083 that.find('input[type="text"]').focus();
2086 that.find('input[type="text"]').focus();
2084 }
2087 }
2085 });
2088 });
2086 };
2089 };
2087
2090
2088 /**
2091 /**
2089 * Request a notebook's data from the server.
2092 * Request a notebook's data from the server.
2090 *
2093 *
2091 * @method load_notebook
2094 * @method load_notebook
2092 * @param {String} notebook_name and path A notebook to load
2095 * @param {String} notebook_name and path A notebook to load
2093 */
2096 */
2094 Notebook.prototype.load_notebook = function (notebook_name, notebook_path) {
2097 Notebook.prototype.load_notebook = function (notebook_name, notebook_path) {
2095 var that = this;
2098 var that = this;
2096 this.notebook_name = notebook_name;
2099 this.notebook_name = notebook_name;
2097 this.notebook_path = notebook_path;
2100 this.notebook_path = notebook_path;
2098 // We do the call with settings so we can set cache to false.
2101 // We do the call with settings so we can set cache to false.
2099 var settings = {
2102 var settings = {
2100 processData : false,
2103 processData : false,
2101 cache : false,
2104 cache : false,
2102 type : "GET",
2105 type : "GET",
2103 dataType : "json",
2106 dataType : "json",
2104 success : $.proxy(this.load_notebook_success,this),
2107 success : $.proxy(this.load_notebook_success,this),
2105 error : $.proxy(this.load_notebook_error,this),
2108 error : $.proxy(this.load_notebook_error,this),
2106 };
2109 };
2107 this.events.trigger('notebook_loading.Notebook');
2110 this.events.trigger('notebook_loading.Notebook');
2108 var url = utils.url_join_encode(
2111 var url = utils.url_join_encode(
2109 this.base_url,
2112 this.base_url,
2110 'api/notebooks',
2113 'api/notebooks',
2111 this.notebook_path,
2114 this.notebook_path,
2112 this.notebook_name
2115 this.notebook_name
2113 );
2116 );
2114 $.ajax(url, settings);
2117 $.ajax(url, settings);
2115 };
2118 };
2116
2119
2117 /**
2120 /**
2118 * Success callback for loading a notebook from the server.
2121 * Success callback for loading a notebook from the server.
2119 *
2122 *
2120 * Load notebook data from the JSON response.
2123 * Load notebook data from the JSON response.
2121 *
2124 *
2122 * @method load_notebook_success
2125 * @method load_notebook_success
2123 * @param {Object} data JSON representation of a notebook
2126 * @param {Object} data JSON representation of a notebook
2124 * @param {String} status Description of response status
2127 * @param {String} status Description of response status
2125 * @param {jqXHR} xhr jQuery Ajax object
2128 * @param {jqXHR} xhr jQuery Ajax object
2126 */
2129 */
2127 Notebook.prototype.load_notebook_success = function (data, status, xhr) {
2130 Notebook.prototype.load_notebook_success = function (data, status, xhr) {
2128 this.fromJSON(data);
2131 this.fromJSON(data);
2129 if (this.ncells() === 0) {
2132 if (this.ncells() === 0) {
2130 this.insert_cell_below('code');
2133 this.insert_cell_below('code');
2131 this.edit_mode(0);
2134 this.edit_mode(0);
2132 } else {
2135 } else {
2133 this.select(0);
2136 this.select(0);
2134 this.handle_command_mode(this.get_cell(0));
2137 this.handle_command_mode(this.get_cell(0));
2135 }
2138 }
2136 this.set_dirty(false);
2139 this.set_dirty(false);
2137 this.scroll_to_top();
2140 this.scroll_to_top();
2138 if (data.orig_nbformat !== undefined && data.nbformat !== data.orig_nbformat) {
2141 if (data.orig_nbformat !== undefined && data.nbformat !== data.orig_nbformat) {
2139 var msg = "This notebook has been converted from an older " +
2142 var msg = "This notebook has been converted from an older " +
2140 "notebook format (v"+data.orig_nbformat+") to the current notebook " +
2143 "notebook format (v"+data.orig_nbformat+") to the current notebook " +
2141 "format (v"+data.nbformat+"). The next time you save this notebook, the " +
2144 "format (v"+data.nbformat+"). The next time you save this notebook, the " +
2142 "newer notebook format will be used and older versions of IPython " +
2145 "newer notebook format will be used and older versions of IPython " +
2143 "may not be able to read it. To keep the older version, close the " +
2146 "may not be able to read it. To keep the older version, close the " +
2144 "notebook without saving it.";
2147 "notebook without saving it.";
2145 dialog.modal({
2148 dialog.modal({
2146 title : "Notebook converted",
2149 title : "Notebook converted",
2147 body : msg,
2150 body : msg,
2148 buttons : {
2151 buttons : {
2149 OK : {
2152 OK : {
2150 class : "btn-primary"
2153 class : "btn-primary"
2151 }
2154 }
2152 }
2155 }
2153 });
2156 });
2154 } else if (data.orig_nbformat_minor !== undefined && data.nbformat_minor !== data.orig_nbformat_minor) {
2157 } else if (data.orig_nbformat_minor !== undefined && data.nbformat_minor !== data.orig_nbformat_minor) {
2155 var that = this;
2158 var that = this;
2156 var orig_vs = 'v' + data.nbformat + '.' + data.orig_nbformat_minor;
2159 var orig_vs = 'v' + data.nbformat + '.' + data.orig_nbformat_minor;
2157 var this_vs = 'v' + data.nbformat + '.' + this.nbformat_minor;
2160 var this_vs = 'v' + data.nbformat + '.' + this.nbformat_minor;
2158 var msg = "This notebook is version " + orig_vs + ", but we only fully support up to " +
2161 var msg = "This notebook is version " + orig_vs + ", but we only fully support up to " +
2159 this_vs + ". You can still work with this notebook, but some features " +
2162 this_vs + ". You can still work with this notebook, but some features " +
2160 "introduced in later notebook versions may not be available.";
2163 "introduced in later notebook versions may not be available.";
2161
2164
2162 dialog.modal({
2165 dialog.modal({
2163 title : "Newer Notebook",
2166 title : "Newer Notebook",
2164 body : msg,
2167 body : msg,
2165 buttons : {
2168 buttons : {
2166 OK : {
2169 OK : {
2167 class : "btn-danger"
2170 class : "btn-danger"
2168 }
2171 }
2169 }
2172 }
2170 });
2173 });
2171
2174
2172 }
2175 }
2173
2176
2174 // Create the session after the notebook is completely loaded to prevent
2177 // Create the session after the notebook is completely loaded to prevent
2175 // code execution upon loading, which is a security risk.
2178 // code execution upon loading, which is a security risk.
2176 if (this.session === null) {
2179 if (this.session === null) {
2177 this.start_session();
2180 this.start_session();
2178 }
2181 }
2179 // load our checkpoint list
2182 // load our checkpoint list
2180 this.list_checkpoints();
2183 this.list_checkpoints();
2181
2184
2182 // load toolbar state
2185 // load toolbar state
2183 if (this.metadata.celltoolbar) {
2186 if (this.metadata.celltoolbar) {
2184 celltoolbar.CellToolbar.global_show();
2187 celltoolbar.CellToolbar.global_show();
2185 celltoolbar.CellToolbar.activate_preset(this.metadata.celltoolbar);
2188 celltoolbar.CellToolbar.activate_preset(this.metadata.celltoolbar);
2186 } else {
2189 } else {
2187 celltoolbar.CellToolbar.global_hide();
2190 celltoolbar.CellToolbar.global_hide();
2188 }
2191 }
2189
2192
2190 // now that we're fully loaded, it is safe to restore save functionality
2193 // now that we're fully loaded, it is safe to restore save functionality
2191 delete(this.save_notebook);
2194 delete(this.save_notebook);
2192 this.events.trigger('notebook_loaded.Notebook');
2195 this.events.trigger('notebook_loaded.Notebook');
2193 };
2196 };
2194
2197
2195 /**
2198 /**
2196 * Failure callback for loading a notebook from the server.
2199 * Failure callback for loading a notebook from the server.
2197 *
2200 *
2198 * @method load_notebook_error
2201 * @method load_notebook_error
2199 * @param {jqXHR} xhr jQuery Ajax object
2202 * @param {jqXHR} xhr jQuery Ajax object
2200 * @param {String} status Description of response status
2203 * @param {String} status Description of response status
2201 * @param {String} error HTTP error message
2204 * @param {String} error HTTP error message
2202 */
2205 */
2203 Notebook.prototype.load_notebook_error = function (xhr, status, error) {
2206 Notebook.prototype.load_notebook_error = function (xhr, status, error) {
2204 this.events.trigger('notebook_load_failed.Notebook', [xhr, status, error]);
2207 this.events.trigger('notebook_load_failed.Notebook', [xhr, status, error]);
2205 var msg;
2208 var msg;
2206 if (xhr.status === 400) {
2209 if (xhr.status === 400) {
2207 msg = error;
2210 msg = error;
2208 } else if (xhr.status === 500) {
2211 } else if (xhr.status === 500) {
2209 msg = "An unknown error occurred while loading this notebook. " +
2212 msg = "An unknown error occurred while loading this notebook. " +
2210 "This version can load notebook formats " +
2213 "This version can load notebook formats " +
2211 "v" + this.nbformat + " or earlier.";
2214 "v" + this.nbformat + " or earlier.";
2212 }
2215 }
2213 dialog.modal({
2216 dialog.modal({
2214 title: "Error loading notebook",
2217 title: "Error loading notebook",
2215 body : msg,
2218 body : msg,
2216 buttons : {
2219 buttons : {
2217 "OK": {}
2220 "OK": {}
2218 }
2221 }
2219 });
2222 });
2220 };
2223 };
2221
2224
2222 /********************* checkpoint-related *********************/
2225 /********************* checkpoint-related *********************/
2223
2226
2224 /**
2227 /**
2225 * Save the notebook then immediately create a checkpoint.
2228 * Save the notebook then immediately create a checkpoint.
2226 *
2229 *
2227 * @method save_checkpoint
2230 * @method save_checkpoint
2228 */
2231 */
2229 Notebook.prototype.save_checkpoint = function () {
2232 Notebook.prototype.save_checkpoint = function () {
2230 this._checkpoint_after_save = true;
2233 this._checkpoint_after_save = true;
2231 this.save_notebook();
2234 this.save_notebook();
2232 };
2235 };
2233
2236
2234 /**
2237 /**
2235 * Add a checkpoint for this notebook.
2238 * Add a checkpoint for this notebook.
2236 * for use as a callback from checkpoint creation.
2239 * for use as a callback from checkpoint creation.
2237 *
2240 *
2238 * @method add_checkpoint
2241 * @method add_checkpoint
2239 */
2242 */
2240 Notebook.prototype.add_checkpoint = function (checkpoint) {
2243 Notebook.prototype.add_checkpoint = function (checkpoint) {
2241 var found = false;
2244 var found = false;
2242 for (var i = 0; i < this.checkpoints.length; i++) {
2245 for (var i = 0; i < this.checkpoints.length; i++) {
2243 var existing = this.checkpoints[i];
2246 var existing = this.checkpoints[i];
2244 if (existing.id == checkpoint.id) {
2247 if (existing.id == checkpoint.id) {
2245 found = true;
2248 found = true;
2246 this.checkpoints[i] = checkpoint;
2249 this.checkpoints[i] = checkpoint;
2247 break;
2250 break;
2248 }
2251 }
2249 }
2252 }
2250 if (!found) {
2253 if (!found) {
2251 this.checkpoints.push(checkpoint);
2254 this.checkpoints.push(checkpoint);
2252 }
2255 }
2253 this.last_checkpoint = this.checkpoints[this.checkpoints.length - 1];
2256 this.last_checkpoint = this.checkpoints[this.checkpoints.length - 1];
2254 };
2257 };
2255
2258
2256 /**
2259 /**
2257 * List checkpoints for this notebook.
2260 * List checkpoints for this notebook.
2258 *
2261 *
2259 * @method list_checkpoints
2262 * @method list_checkpoints
2260 */
2263 */
2261 Notebook.prototype.list_checkpoints = function () {
2264 Notebook.prototype.list_checkpoints = function () {
2262 var url = utils.url_join_encode(
2265 var url = utils.url_join_encode(
2263 this.base_url,
2266 this.base_url,
2264 'api/notebooks',
2267 'api/notebooks',
2265 this.notebook_path,
2268 this.notebook_path,
2266 this.notebook_name,
2269 this.notebook_name,
2267 'checkpoints'
2270 'checkpoints'
2268 );
2271 );
2269 $.get(url).done(
2272 $.get(url).done(
2270 $.proxy(this.list_checkpoints_success, this)
2273 $.proxy(this.list_checkpoints_success, this)
2271 ).fail(
2274 ).fail(
2272 $.proxy(this.list_checkpoints_error, this)
2275 $.proxy(this.list_checkpoints_error, this)
2273 );
2276 );
2274 };
2277 };
2275
2278
2276 /**
2279 /**
2277 * Success callback for listing checkpoints.
2280 * Success callback for listing checkpoints.
2278 *
2281 *
2279 * @method list_checkpoint_success
2282 * @method list_checkpoint_success
2280 * @param {Object} data JSON representation of a checkpoint
2283 * @param {Object} data JSON representation of a checkpoint
2281 * @param {String} status Description of response status
2284 * @param {String} status Description of response status
2282 * @param {jqXHR} xhr jQuery Ajax object
2285 * @param {jqXHR} xhr jQuery Ajax object
2283 */
2286 */
2284 Notebook.prototype.list_checkpoints_success = function (data, status, xhr) {
2287 Notebook.prototype.list_checkpoints_success = function (data, status, xhr) {
2285 data = $.parseJSON(data);
2288 data = $.parseJSON(data);
2286 this.checkpoints = data;
2289 this.checkpoints = data;
2287 if (data.length) {
2290 if (data.length) {
2288 this.last_checkpoint = data[data.length - 1];
2291 this.last_checkpoint = data[data.length - 1];
2289 } else {
2292 } else {
2290 this.last_checkpoint = null;
2293 this.last_checkpoint = null;
2291 }
2294 }
2292 this.events.trigger('checkpoints_listed.Notebook', [data]);
2295 this.events.trigger('checkpoints_listed.Notebook', [data]);
2293 };
2296 };
2294
2297
2295 /**
2298 /**
2296 * Failure callback for listing a checkpoint.
2299 * Failure callback for listing a checkpoint.
2297 *
2300 *
2298 * @method list_checkpoint_error
2301 * @method list_checkpoint_error
2299 * @param {jqXHR} xhr jQuery Ajax object
2302 * @param {jqXHR} xhr jQuery Ajax object
2300 * @param {String} status Description of response status
2303 * @param {String} status Description of response status
2301 * @param {String} error_msg HTTP error message
2304 * @param {String} error_msg HTTP error message
2302 */
2305 */
2303 Notebook.prototype.list_checkpoints_error = function (xhr, status, error_msg) {
2306 Notebook.prototype.list_checkpoints_error = function (xhr, status, error_msg) {
2304 this.events.trigger('list_checkpoints_failed.Notebook');
2307 this.events.trigger('list_checkpoints_failed.Notebook');
2305 };
2308 };
2306
2309
2307 /**
2310 /**
2308 * Create a checkpoint of this notebook on the server from the most recent save.
2311 * Create a checkpoint of this notebook on the server from the most recent save.
2309 *
2312 *
2310 * @method create_checkpoint
2313 * @method create_checkpoint
2311 */
2314 */
2312 Notebook.prototype.create_checkpoint = function () {
2315 Notebook.prototype.create_checkpoint = function () {
2313 var url = utils.url_join_encode(
2316 var url = utils.url_join_encode(
2314 this.base_url,
2317 this.base_url,
2315 'api/notebooks',
2318 'api/notebooks',
2316 this.notebook_path,
2319 this.notebook_path,
2317 this.notebook_name,
2320 this.notebook_name,
2318 'checkpoints'
2321 'checkpoints'
2319 );
2322 );
2320 $.post(url).done(
2323 $.post(url).done(
2321 $.proxy(this.create_checkpoint_success, this)
2324 $.proxy(this.create_checkpoint_success, this)
2322 ).fail(
2325 ).fail(
2323 $.proxy(this.create_checkpoint_error, this)
2326 $.proxy(this.create_checkpoint_error, this)
2324 );
2327 );
2325 };
2328 };
2326
2329
2327 /**
2330 /**
2328 * Success callback for creating a checkpoint.
2331 * Success callback for creating a checkpoint.
2329 *
2332 *
2330 * @method create_checkpoint_success
2333 * @method create_checkpoint_success
2331 * @param {Object} data JSON representation of a checkpoint
2334 * @param {Object} data JSON representation of a checkpoint
2332 * @param {String} status Description of response status
2335 * @param {String} status Description of response status
2333 * @param {jqXHR} xhr jQuery Ajax object
2336 * @param {jqXHR} xhr jQuery Ajax object
2334 */
2337 */
2335 Notebook.prototype.create_checkpoint_success = function (data, status, xhr) {
2338 Notebook.prototype.create_checkpoint_success = function (data, status, xhr) {
2336 data = $.parseJSON(data);
2339 data = $.parseJSON(data);
2337 this.add_checkpoint(data);
2340 this.add_checkpoint(data);
2338 this.events.trigger('checkpoint_created.Notebook', data);
2341 this.events.trigger('checkpoint_created.Notebook', data);
2339 };
2342 };
2340
2343
2341 /**
2344 /**
2342 * Failure callback for creating a checkpoint.
2345 * Failure callback for creating a checkpoint.
2343 *
2346 *
2344 * @method create_checkpoint_error
2347 * @method create_checkpoint_error
2345 * @param {jqXHR} xhr jQuery Ajax object
2348 * @param {jqXHR} xhr jQuery Ajax object
2346 * @param {String} status Description of response status
2349 * @param {String} status Description of response status
2347 * @param {String} error_msg HTTP error message
2350 * @param {String} error_msg HTTP error message
2348 */
2351 */
2349 Notebook.prototype.create_checkpoint_error = function (xhr, status, error_msg) {
2352 Notebook.prototype.create_checkpoint_error = function (xhr, status, error_msg) {
2350 this.events.trigger('checkpoint_failed.Notebook');
2353 this.events.trigger('checkpoint_failed.Notebook');
2351 };
2354 };
2352
2355
2353 Notebook.prototype.restore_checkpoint_dialog = function (checkpoint) {
2356 Notebook.prototype.restore_checkpoint_dialog = function (checkpoint) {
2354 var that = this;
2357 var that = this;
2355 checkpoint = checkpoint || this.last_checkpoint;
2358 checkpoint = checkpoint || this.last_checkpoint;
2356 if ( ! checkpoint ) {
2359 if ( ! checkpoint ) {
2357 console.log("restore dialog, but no checkpoint to restore to!");
2360 console.log("restore dialog, but no checkpoint to restore to!");
2358 return;
2361 return;
2359 }
2362 }
2360 var body = $('<div/>').append(
2363 var body = $('<div/>').append(
2361 $('<p/>').addClass("p-space").text(
2364 $('<p/>').addClass("p-space").text(
2362 "Are you sure you want to revert the notebook to " +
2365 "Are you sure you want to revert the notebook to " +
2363 "the latest checkpoint?"
2366 "the latest checkpoint?"
2364 ).append(
2367 ).append(
2365 $("<strong/>").text(
2368 $("<strong/>").text(
2366 " This cannot be undone."
2369 " This cannot be undone."
2367 )
2370 )
2368 )
2371 )
2369 ).append(
2372 ).append(
2370 $('<p/>').addClass("p-space").text("The checkpoint was last updated at:")
2373 $('<p/>').addClass("p-space").text("The checkpoint was last updated at:")
2371 ).append(
2374 ).append(
2372 $('<p/>').addClass("p-space").text(
2375 $('<p/>').addClass("p-space").text(
2373 Date(checkpoint.last_modified)
2376 Date(checkpoint.last_modified)
2374 ).css("text-align", "center")
2377 ).css("text-align", "center")
2375 );
2378 );
2376
2379
2377 dialog.modal({
2380 dialog.modal({
2378 title : "Revert notebook to checkpoint",
2381 title : "Revert notebook to checkpoint",
2379 body : body,
2382 body : body,
2380 buttons : {
2383 buttons : {
2381 Revert : {
2384 Revert : {
2382 class : "btn-danger",
2385 class : "btn-danger",
2383 click : function () {
2386 click : function () {
2384 that.restore_checkpoint(checkpoint.id);
2387 that.restore_checkpoint(checkpoint.id);
2385 }
2388 }
2386 },
2389 },
2387 Cancel : {}
2390 Cancel : {}
2388 }
2391 }
2389 });
2392 });
2390 };
2393 };
2391
2394
2392 /**
2395 /**
2393 * Restore the notebook to a checkpoint state.
2396 * Restore the notebook to a checkpoint state.
2394 *
2397 *
2395 * @method restore_checkpoint
2398 * @method restore_checkpoint
2396 * @param {String} checkpoint ID
2399 * @param {String} checkpoint ID
2397 */
2400 */
2398 Notebook.prototype.restore_checkpoint = function (checkpoint) {
2401 Notebook.prototype.restore_checkpoint = function (checkpoint) {
2399 this.events.trigger('notebook_restoring.Notebook', checkpoint);
2402 this.events.trigger('notebook_restoring.Notebook', checkpoint);
2400 var url = utils.url_join_encode(
2403 var url = utils.url_join_encode(
2401 this.base_url,
2404 this.base_url,
2402 'api/notebooks',
2405 'api/notebooks',
2403 this.notebook_path,
2406 this.notebook_path,
2404 this.notebook_name,
2407 this.notebook_name,
2405 'checkpoints',
2408 'checkpoints',
2406 checkpoint
2409 checkpoint
2407 );
2410 );
2408 $.post(url).done(
2411 $.post(url).done(
2409 $.proxy(this.restore_checkpoint_success, this)
2412 $.proxy(this.restore_checkpoint_success, this)
2410 ).fail(
2413 ).fail(
2411 $.proxy(this.restore_checkpoint_error, this)
2414 $.proxy(this.restore_checkpoint_error, this)
2412 );
2415 );
2413 };
2416 };
2414
2417
2415 /**
2418 /**
2416 * Success callback for restoring a notebook to a checkpoint.
2419 * Success callback for restoring a notebook to a checkpoint.
2417 *
2420 *
2418 * @method restore_checkpoint_success
2421 * @method restore_checkpoint_success
2419 * @param {Object} data (ignored, should be empty)
2422 * @param {Object} data (ignored, should be empty)
2420 * @param {String} status Description of response status
2423 * @param {String} status Description of response status
2421 * @param {jqXHR} xhr jQuery Ajax object
2424 * @param {jqXHR} xhr jQuery Ajax object
2422 */
2425 */
2423 Notebook.prototype.restore_checkpoint_success = function (data, status, xhr) {
2426 Notebook.prototype.restore_checkpoint_success = function (data, status, xhr) {
2424 this.events.trigger('checkpoint_restored.Notebook');
2427 this.events.trigger('checkpoint_restored.Notebook');
2425 this.load_notebook(this.notebook_name, this.notebook_path);
2428 this.load_notebook(this.notebook_name, this.notebook_path);
2426 };
2429 };
2427
2430
2428 /**
2431 /**
2429 * Failure callback for restoring a notebook to a checkpoint.
2432 * Failure callback for restoring a notebook to a checkpoint.
2430 *
2433 *
2431 * @method restore_checkpoint_error
2434 * @method restore_checkpoint_error
2432 * @param {jqXHR} xhr jQuery Ajax object
2435 * @param {jqXHR} xhr jQuery Ajax object
2433 * @param {String} status Description of response status
2436 * @param {String} status Description of response status
2434 * @param {String} error_msg HTTP error message
2437 * @param {String} error_msg HTTP error message
2435 */
2438 */
2436 Notebook.prototype.restore_checkpoint_error = function (xhr, status, error_msg) {
2439 Notebook.prototype.restore_checkpoint_error = function (xhr, status, error_msg) {
2437 this.events.trigger('checkpoint_restore_failed.Notebook');
2440 this.events.trigger('checkpoint_restore_failed.Notebook');
2438 };
2441 };
2439
2442
2440 /**
2443 /**
2441 * Delete a notebook checkpoint.
2444 * Delete a notebook checkpoint.
2442 *
2445 *
2443 * @method delete_checkpoint
2446 * @method delete_checkpoint
2444 * @param {String} checkpoint ID
2447 * @param {String} checkpoint ID
2445 */
2448 */
2446 Notebook.prototype.delete_checkpoint = function (checkpoint) {
2449 Notebook.prototype.delete_checkpoint = function (checkpoint) {
2447 this.events.trigger('notebook_restoring.Notebook', checkpoint);
2450 this.events.trigger('notebook_restoring.Notebook', checkpoint);
2448 var url = utils.url_join_encode(
2451 var url = utils.url_join_encode(
2449 this.base_url,
2452 this.base_url,
2450 'api/notebooks',
2453 'api/notebooks',
2451 this.notebook_path,
2454 this.notebook_path,
2452 this.notebook_name,
2455 this.notebook_name,
2453 'checkpoints',
2456 'checkpoints',
2454 checkpoint
2457 checkpoint
2455 );
2458 );
2456 $.ajax(url, {
2459 $.ajax(url, {
2457 type: 'DELETE',
2460 type: 'DELETE',
2458 success: $.proxy(this.delete_checkpoint_success, this),
2461 success: $.proxy(this.delete_checkpoint_success, this),
2459 error: $.proxy(this.delete_checkpoint_error, this)
2462 error: $.proxy(this.delete_checkpoint_error, this)
2460 });
2463 });
2461 };
2464 };
2462
2465
2463 /**
2466 /**
2464 * Success callback for deleting a notebook checkpoint
2467 * Success callback for deleting a notebook checkpoint
2465 *
2468 *
2466 * @method delete_checkpoint_success
2469 * @method delete_checkpoint_success
2467 * @param {Object} data (ignored, should be empty)
2470 * @param {Object} data (ignored, should be empty)
2468 * @param {String} status Description of response status
2471 * @param {String} status Description of response status
2469 * @param {jqXHR} xhr jQuery Ajax object
2472 * @param {jqXHR} xhr jQuery Ajax object
2470 */
2473 */
2471 Notebook.prototype.delete_checkpoint_success = function (data, status, xhr) {
2474 Notebook.prototype.delete_checkpoint_success = function (data, status, xhr) {
2472 this.events.trigger('checkpoint_deleted.Notebook', data);
2475 this.events.trigger('checkpoint_deleted.Notebook', data);
2473 this.load_notebook(this.notebook_name, this.notebook_path);
2476 this.load_notebook(this.notebook_name, this.notebook_path);
2474 };
2477 };
2475
2478
2476 /**
2479 /**
2477 * Failure callback for deleting a notebook checkpoint.
2480 * Failure callback for deleting a notebook checkpoint.
2478 *
2481 *
2479 * @method delete_checkpoint_error
2482 * @method delete_checkpoint_error
2480 * @param {jqXHR} xhr jQuery Ajax object
2483 * @param {jqXHR} xhr jQuery Ajax object
2481 * @param {String} status Description of response status
2484 * @param {String} status Description of response status
2482 * @param {String} error_msg HTTP error message
2485 * @param {String} error_msg HTTP error message
2483 */
2486 */
2484 Notebook.prototype.delete_checkpoint_error = function (xhr, status, error_msg) {
2487 Notebook.prototype.delete_checkpoint_error = function (xhr, status, error_msg) {
2485 this.events.trigger('checkpoint_delete_failed.Notebook');
2488 this.events.trigger('checkpoint_delete_failed.Notebook');
2486 };
2489 };
2487
2490
2488
2491
2489 // For backwards compatability.
2492 // For backwards compatability.
2490 IPython.Notebook = Notebook;
2493 IPython.Notebook = Notebook;
2491
2494
2492 return {'Notebook': Notebook};
2495 return {'Notebook': Notebook};
2493 });
2496 });
@@ -1,233 +1,242 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, options) {
13 var NotificationArea = function (selector, options) {
14 // Constructor
15 //
16 // Parameters:
17 // selector: string
18 // options: dictionary
19 // Dictionary of keyword arguments.
20 // notebook: Notebook instance
21 // events: $(Events) instance
22 // save_widget: SaveWidget instance
14 this.selector = selector;
23 this.selector = selector;
15 this.events = options.events;
24 this.events = options.events;
16 this.save_widget = options.save_widget;
25 this.save_widget = options.save_widget;
17 this.notebook = options.notebook;
26 this.notebook = options.notebook;
18 if (this.selector !== undefined) {
27 if (this.selector !== undefined) {
19 this.element = $(selector);
28 this.element = $(selector);
20 }
29 }
21 this.widget_dict = {};
30 this.widget_dict = {};
22 };
31 };
23
32
24 NotificationArea.prototype.temp_message = function (msg, timeout, css_class) {
33 NotificationArea.prototype.temp_message = function (msg, timeout, css_class) {
25 var uuid = utils.uuid();
34 var uuid = utils.uuid();
26 if( css_class == 'danger') {css_class = 'ui-state-error';}
35 if( css_class == 'danger') {css_class = 'ui-state-error';}
27 if( css_class == 'warning') {css_class = 'ui-state-highlight';}
36 if( css_class == 'warning') {css_class = 'ui-state-highlight';}
28 var tdiv = $('<div>')
37 var tdiv = $('<div>')
29 .attr('id',uuid)
38 .attr('id',uuid)
30 .addClass('notification_widget ui-widget ui-widget-content ui-corner-all')
39 .addClass('notification_widget ui-widget ui-widget-content ui-corner-all')
31 .addClass('border-box-sizing')
40 .addClass('border-box-sizing')
32 .addClass(css_class)
41 .addClass(css_class)
33 .hide()
42 .hide()
34 .text(msg);
43 .text(msg);
35
44
36 $(this.selector).append(tdiv);
45 $(this.selector).append(tdiv);
37 var tmout = Math.max(1500,(timeout||1500));
46 var tmout = Math.max(1500,(timeout||1500));
38 tdiv.fadeIn(100);
47 tdiv.fadeIn(100);
39
48
40 setTimeout(function () {
49 setTimeout(function () {
41 tdiv.fadeOut(100, function () {tdiv.remove();});
50 tdiv.fadeOut(100, function () {tdiv.remove();});
42 }, tmout);
51 }, tmout);
43 };
52 };
44
53
45 NotificationArea.prototype.widget = function(name) {
54 NotificationArea.prototype.widget = function(name) {
46 if(this.widget_dict[name] === undefined) {
55 if(this.widget_dict[name] === undefined) {
47 return this.new_notification_widget(name);
56 return this.new_notification_widget(name);
48 }
57 }
49 return this.get_widget(name);
58 return this.get_widget(name);
50 };
59 };
51
60
52 NotificationArea.prototype.get_widget = function(name) {
61 NotificationArea.prototype.get_widget = function(name) {
53 if(this.widget_dict[name] === undefined) {
62 if(this.widget_dict[name] === undefined) {
54 throw('no widgets with this name');
63 throw('no widgets with this name');
55 }
64 }
56 return this.widget_dict[name];
65 return this.widget_dict[name];
57 };
66 };
58
67
59 NotificationArea.prototype.new_notification_widget = function(name) {
68 NotificationArea.prototype.new_notification_widget = function(name) {
60 if(this.widget_dict[name] !== undefined) {
69 if(this.widget_dict[name] !== undefined) {
61 throw('widget with that name already exists ! ');
70 throw('widget with that name already exists ! ');
62 }
71 }
63 var div = $('<div/>').attr('id','notification_'+name);
72 var div = $('<div/>').attr('id','notification_'+name);
64 $(this.selector).append(div);
73 $(this.selector).append(div);
65 this.widget_dict[name] = new notificationwidget.NotificationWidget('#notification_'+name);
74 this.widget_dict[name] = new notificationwidget.NotificationWidget('#notification_'+name);
66 return this.widget_dict[name];
75 return this.widget_dict[name];
67 };
76 };
68
77
69 NotificationArea.prototype.init_notification_widgets = function() {
78 NotificationArea.prototype.init_notification_widgets = function() {
70 var that = this;
79 var that = this;
71 var knw = this.new_notification_widget('kernel');
80 var knw = this.new_notification_widget('kernel');
72 var $kernel_ind_icon = $("#kernel_indicator_icon");
81 var $kernel_ind_icon = $("#kernel_indicator_icon");
73 var $modal_ind_icon = $("#modal_indicator_icon");
82 var $modal_ind_icon = $("#modal_indicator_icon");
74
83
75 // Command/Edit mode
84 // Command/Edit mode
76 this.events.on('edit_mode.Notebook',function () {
85 this.events.on('edit_mode.Notebook',function () {
77 that.save_widget.update_document_title();
86 that.save_widget.update_document_title();
78 $modal_ind_icon.attr('class','edit_mode_icon').attr('title','Edit Mode');
87 $modal_ind_icon.attr('class','edit_mode_icon').attr('title','Edit Mode');
79 });
88 });
80
89
81 this.events.on('command_mode.Notebook',function () {
90 this.events.on('command_mode.Notebook',function () {
82 that.save_widget.update_document_title();
91 that.save_widget.update_document_title();
83 $modal_ind_icon.attr('class','command_mode_icon').attr('title','Command Mode');
92 $modal_ind_icon.attr('class','command_mode_icon').attr('title','Command Mode');
84 });
93 });
85
94
86 // Implicitly start off in Command mode, switching to Edit mode will trigger event
95 // 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');
96 $modal_ind_icon.attr('class','command-mode_icon').attr('title','Command Mode');
88
97
89 // Kernel events
98 // Kernel events
90 this.events.on('status_idle.Kernel',function () {
99 this.events.on('status_idle.Kernel',function () {
91 that.save_widget.update_document_title();
100 that.save_widget.update_document_title();
92 $kernel_ind_icon.attr('class','kernel_idle_icon').attr('title','Kernel Idle');
101 $kernel_ind_icon.attr('class','kernel_idle_icon').attr('title','Kernel Idle');
93 });
102 });
94
103
95 this.events.on('status_busy.Kernel',function () {
104 this.events.on('status_busy.Kernel',function () {
96 window.document.title='(Busy) '+window.document.title;
105 window.document.title='(Busy) '+window.document.title;
97 $kernel_ind_icon.attr('class','kernel_busy_icon').attr('title','Kernel Busy');
106 $kernel_ind_icon.attr('class','kernel_busy_icon').attr('title','Kernel Busy');
98 });
107 });
99
108
100 this.events.on('status_restarting.Kernel',function () {
109 this.events.on('status_restarting.Kernel',function () {
101 that.save_widget.update_document_title();
110 that.save_widget.update_document_title();
102 knw.set_message("Restarting kernel", 2000);
111 knw.set_message("Restarting kernel", 2000);
103 });
112 });
104
113
105 this.events.on('status_interrupting.Kernel',function () {
114 this.events.on('status_interrupting.Kernel',function () {
106 knw.set_message("Interrupting kernel", 2000);
115 knw.set_message("Interrupting kernel", 2000);
107 });
116 });
108
117
109 // Start the kernel indicator in the busy state, and send a kernel_info request.
118 // 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.
119 // When the kernel_info reply arrives, the kernel is idle.
111 $kernel_ind_icon.attr('class','kernel_busy_icon').attr('title','Kernel Busy');
120 $kernel_ind_icon.attr('class','kernel_busy_icon').attr('title','Kernel Busy');
112
121
113 this.events.on('status_started.Kernel', function (evt, data) {
122 this.events.on('status_started.Kernel', function (evt, data) {
114 data.kernel.kernel_info(function () {
123 data.kernel.kernel_info(function () {
115 that.events.trigger('status_idle.Kernel');
124 that.events.trigger('status_idle.Kernel');
116 });
125 });
117 });
126 });
118
127
119 this.events.on('status_dead.Kernel',function () {
128 this.events.on('status_dead.Kernel',function () {
120 var msg = 'The kernel has died, and the automatic restart has failed.' +
129 var msg = 'The kernel has died, and the automatic restart has failed.' +
121 ' It is possible the kernel cannot be restarted.' +
130 ' 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' +
131 ' 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' +
132 ' the notebook, but running code will no longer work until the notebook' +
124 ' is reopened.';
133 ' is reopened.';
125
134
126 dialog.modal({
135 dialog.modal({
127 title: "Dead kernel",
136 title: "Dead kernel",
128 body : msg,
137 body : msg,
129 buttons : {
138 buttons : {
130 "Manual Restart": {
139 "Manual Restart": {
131 class: "btn-danger",
140 class: "btn-danger",
132 click: function () {
141 click: function () {
133 that.events.trigger('status_restarting.Kernel');
142 that.events.trigger('status_restarting.Kernel');
134 that.notebook.start_kernel();
143 that.notebook.start_kernel();
135 }
144 }
136 },
145 },
137 "Don't restart": {}
146 "Don't restart": {}
138 }
147 }
139 });
148 });
140 });
149 });
141
150
142 this.events.on('websocket_closed.Kernel', function (event, data) {
151 this.events.on('websocket_closed.Kernel', function (event, data) {
143 var kernel = data.kernel;
152 var kernel = data.kernel;
144 var ws_url = data.ws_url;
153 var ws_url = data.ws_url;
145 var early = data.early;
154 var early = data.early;
146 var msg;
155 var msg;
147 if (!early) {
156 if (!early) {
148 knw.set_message('Reconnecting WebSockets', 1000);
157 knw.set_message('Reconnecting WebSockets', 1000);
149 setTimeout(function () {
158 setTimeout(function () {
150 kernel.start_channels();
159 kernel.start_channels();
151 }, 5000);
160 }, 5000);
152 return;
161 return;
153 }
162 }
154 console.log('WebSocket connection failed: ', ws_url);
163 console.log('WebSocket connection failed: ', ws_url);
155 msg = "A WebSocket connection could not be established." +
164 msg = "A WebSocket connection could not be established." +
156 " You will NOT be able to run code. Check your" +
165 " You will NOT be able to run code. Check your" +
157 " network connection or notebook server configuration.";
166 " network connection or notebook server configuration.";
158 dialog.modal({
167 dialog.modal({
159 title: "WebSocket connection failed",
168 title: "WebSocket connection failed",
160 body: msg,
169 body: msg,
161 buttons : {
170 buttons : {
162 "OK": {},
171 "OK": {},
163 "Reconnect": {
172 "Reconnect": {
164 click: function () {
173 click: function () {
165 knw.set_message('Reconnecting WebSockets', 1000);
174 knw.set_message('Reconnecting WebSockets', 1000);
166 setTimeout(function () {
175 setTimeout(function () {
167 kernel.start_channels();
176 kernel.start_channels();
168 }, 5000);
177 }, 5000);
169 }
178 }
170 }
179 }
171 }
180 }
172 });
181 });
173 });
182 });
174
183
175
184
176 var nnw = this.new_notification_widget('notebook');
185 var nnw = this.new_notification_widget('notebook');
177
186
178 // Notebook events
187 // Notebook events
179 this.events.on('notebook_loading.Notebook', function () {
188 this.events.on('notebook_loading.Notebook', function () {
180 nnw.set_message("Loading notebook",500);
189 nnw.set_message("Loading notebook",500);
181 });
190 });
182 this.events.on('notebook_loaded.Notebook', function () {
191 this.events.on('notebook_loaded.Notebook', function () {
183 nnw.set_message("Notebook loaded",500);
192 nnw.set_message("Notebook loaded",500);
184 });
193 });
185 this.events.on('notebook_saving.Notebook', function () {
194 this.events.on('notebook_saving.Notebook', function () {
186 nnw.set_message("Saving notebook",500);
195 nnw.set_message("Saving notebook",500);
187 });
196 });
188 this.events.on('notebook_saved.Notebook', function () {
197 this.events.on('notebook_saved.Notebook', function () {
189 nnw.set_message("Notebook saved",2000);
198 nnw.set_message("Notebook saved",2000);
190 });
199 });
191 this.events.on('notebook_save_failed.Notebook', function (evt, xhr, status, data) {
200 this.events.on('notebook_save_failed.Notebook', function (evt, xhr, status, data) {
192 nnw.set_message(data || "Notebook save failed");
201 nnw.set_message(data || "Notebook save failed");
193 });
202 });
194
203
195 // Checkpoint events
204 // Checkpoint events
196 this.events.on('checkpoint_created.Notebook', function (evt, data) {
205 this.events.on('checkpoint_created.Notebook', function (evt, data) {
197 var msg = "Checkpoint created";
206 var msg = "Checkpoint created";
198 if (data.last_modified) {
207 if (data.last_modified) {
199 var d = new Date(data.last_modified);
208 var d = new Date(data.last_modified);
200 msg = msg + ": " + d.format("HH:MM:ss");
209 msg = msg + ": " + d.format("HH:MM:ss");
201 }
210 }
202 nnw.set_message(msg, 2000);
211 nnw.set_message(msg, 2000);
203 });
212 });
204 this.events.on('checkpoint_failed.Notebook', function () {
213 this.events.on('checkpoint_failed.Notebook', function () {
205 nnw.set_message("Checkpoint failed");
214 nnw.set_message("Checkpoint failed");
206 });
215 });
207 this.events.on('checkpoint_deleted.Notebook', function () {
216 this.events.on('checkpoint_deleted.Notebook', function () {
208 nnw.set_message("Checkpoint deleted", 500);
217 nnw.set_message("Checkpoint deleted", 500);
209 });
218 });
210 this.events.on('checkpoint_delete_failed.Notebook', function () {
219 this.events.on('checkpoint_delete_failed.Notebook', function () {
211 nnw.set_message("Checkpoint delete failed");
220 nnw.set_message("Checkpoint delete failed");
212 });
221 });
213 this.events.on('checkpoint_restoring.Notebook', function () {
222 this.events.on('checkpoint_restoring.Notebook', function () {
214 nnw.set_message("Restoring to checkpoint...", 500);
223 nnw.set_message("Restoring to checkpoint...", 500);
215 });
224 });
216 this.events.on('checkpoint_restore_failed.Notebook', function () {
225 this.events.on('checkpoint_restore_failed.Notebook', function () {
217 nnw.set_message("Checkpoint restore failed");
226 nnw.set_message("Checkpoint restore failed");
218 });
227 });
219
228
220 // Autosave events
229 // Autosave events
221 this.events.on('autosave_disabled.Notebook', function () {
230 this.events.on('autosave_disabled.Notebook', function () {
222 nnw.set_message("Autosave disabled", 2000);
231 nnw.set_message("Autosave disabled", 2000);
223 });
232 });
224 this.events.on('autosave_enabled.Notebook', function (evt, interval) {
233 this.events.on('autosave_enabled.Notebook', function (evt, interval) {
225 nnw.set_message("Saving every " + interval / 1000 + "s", 1000);
234 nnw.set_message("Saving every " + interval / 1000 + "s", 1000);
226 });
235 });
227
236
228 };
237 };
229
238
230 IPython.NotificationArea = NotificationArea;
239 IPython.NotificationArea = NotificationArea;
231
240
232 return {'NotificationArea': NotificationArea};
241 return {'NotificationArea': NotificationArea};
233 });
242 });
@@ -1,178 +1,187 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, options) {
11 var Pager = function (pager_selector, pager_splitter_selector, options) {
12 // Constructor
13 //
14 // Parameters:
15 // pager_selector: string
16 // pager_splitter_selector: string
17 // options: dictionary
18 // Dictionary of keyword arguments.
19 // events: $(Events) instance
20 // layout_manager: LayoutManager instance
12 this.events = options.events;
21 this.events = options.events;
13 this.pager_element = $(pager_selector);
22 this.pager_element = $(pager_selector);
14 this.pager_button_area = $('#pager_button_area');
23 this.pager_button_area = $('#pager_button_area');
15 var that = this;
24 var that = this;
16 this.percentage_height = 0.40;
25 this.percentage_height = 0.40;
17 options.layout_manager.pager = this;
26 options.layout_manager.pager = this;
18 this.pager_splitter_element = $(pager_splitter_selector)
27 this.pager_splitter_element = $(pager_splitter_selector)
19 .draggable({
28 .draggable({
20 containment: 'window',
29 containment: 'window',
21 axis:'y',
30 axis:'y',
22 helper: null ,
31 helper: null ,
23 drag: function(event, ui) {
32 drag: function(event, ui) {
24 // recalculate the amount of space the pager should take
33 // recalculate the amount of space the pager should take
25 var pheight = ($(document.body).height()-event.clientY-4);
34 var pheight = ($(document.body).height()-event.clientY-4);
26 var downprct = pheight/options.layout_manager.app_height();
35 var downprct = pheight/options.layout_manager.app_height();
27 downprct = Math.min(0.9, downprct);
36 downprct = Math.min(0.9, downprct);
28 if (downprct < 0.1) {
37 if (downprct < 0.1) {
29 that.percentage_height = 0.1;
38 that.percentage_height = 0.1;
30 that.collapse({'duration':0});
39 that.collapse({'duration':0});
31 } else if (downprct > 0.2) {
40 } else if (downprct > 0.2) {
32 that.percentage_height = downprct;
41 that.percentage_height = downprct;
33 that.expand({'duration':0});
42 that.expand({'duration':0});
34 }
43 }
35 options.layout_manager.do_resize();
44 options.layout_manager.do_resize();
36 }
45 }
37 });
46 });
38 this.expanded = false;
47 this.expanded = false;
39 this.style();
48 this.style();
40 this.create_button_area();
49 this.create_button_area();
41 this.bind_events();
50 this.bind_events();
42 };
51 };
43
52
44 Pager.prototype.create_button_area = function(){
53 Pager.prototype.create_button_area = function(){
45 var that = this;
54 var that = this;
46 this.pager_button_area.append(
55 this.pager_button_area.append(
47 $('<a>').attr('role', "button")
56 $('<a>').attr('role', "button")
48 .attr('title',"Open the pager in an external window")
57 .attr('title',"Open the pager in an external window")
49 .addClass('ui-button')
58 .addClass('ui-button')
50 .click(function(){that.detach();})
59 .click(function(){that.detach();})
51 .attr('style','position: absolute; right: 20px;')
60 .attr('style','position: absolute; right: 20px;')
52 .append(
61 .append(
53 $('<span>').addClass("ui-icon ui-icon-extlink")
62 $('<span>').addClass("ui-icon ui-icon-extlink")
54 )
63 )
55 );
64 );
56 this.pager_button_area.append(
65 this.pager_button_area.append(
57 $('<a>').attr('role', "button")
66 $('<a>').attr('role', "button")
58 .attr('title',"Close the pager")
67 .attr('title',"Close the pager")
59 .addClass('ui-button')
68 .addClass('ui-button')
60 .click(function(){that.collapse();})
69 .click(function(){that.collapse();})
61 .attr('style','position: absolute; right: 5px;')
70 .attr('style','position: absolute; right: 5px;')
62 .append(
71 .append(
63 $('<span>').addClass("ui-icon ui-icon-close")
72 $('<span>').addClass("ui-icon ui-icon-close")
64 )
73 )
65 );
74 );
66 };
75 };
67
76
68 Pager.prototype.style = function () {
77 Pager.prototype.style = function () {
69 this.pager_splitter_element.addClass('border-box-sizing ui-widget ui-state-default');
78 this.pager_splitter_element.addClass('border-box-sizing ui-widget ui-state-default');
70 this.pager_element.addClass('border-box-sizing');
79 this.pager_element.addClass('border-box-sizing');
71 this.pager_element.find(".container").addClass('border-box-sizing');
80 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');
81 this.pager_splitter_element.attr('title', 'Click to Show/Hide pager area, drag to Resize');
73 };
82 };
74
83
75
84
76 Pager.prototype.bind_events = function () {
85 Pager.prototype.bind_events = function () {
77 var that = this;
86 var that = this;
78
87
79 this.pager_element.bind('collapse_pager', function (event, extrap) {
88 this.pager_element.bind('collapse_pager', function (event, extrap) {
80 var time = 'fast';
89 var time = 'fast';
81 if (extrap && extrap.duration) {
90 if (extrap && extrap.duration) {
82 time = extrap.duration;
91 time = extrap.duration;
83 }
92 }
84 that.pager_element.hide(time);
93 that.pager_element.hide(time);
85 });
94 });
86
95
87 this.pager_element.bind('expand_pager', function (event, extrap) {
96 this.pager_element.bind('expand_pager', function (event, extrap) {
88 var time = 'fast';
97 var time = 'fast';
89 if (extrap && extrap.duration) {
98 if (extrap && extrap.duration) {
90 time = extrap.duration;
99 time = extrap.duration;
91 }
100 }
92 that.pager_element.show(time);
101 that.pager_element.show(time);
93 });
102 });
94
103
95 this.pager_splitter_element.hover(
104 this.pager_splitter_element.hover(
96 function () {
105 function () {
97 that.pager_splitter_element.addClass('ui-state-hover');
106 that.pager_splitter_element.addClass('ui-state-hover');
98 },
107 },
99 function () {
108 function () {
100 that.pager_splitter_element.removeClass('ui-state-hover');
109 that.pager_splitter_element.removeClass('ui-state-hover');
101 }
110 }
102 );
111 );
103
112
104 this.pager_splitter_element.click(function () {
113 this.pager_splitter_element.click(function () {
105 that.toggle();
114 that.toggle();
106 });
115 });
107
116
108 this.events.on('open_with_text.Pager', function (event, payload) {
117 this.events.on('open_with_text.Pager', function (event, payload) {
109 // FIXME: support other mime types
118 // FIXME: support other mime types
110 if (payload.data['text/plain'] && payload.data['text/plain'] !== "") {
119 if (payload.data['text/plain'] && payload.data['text/plain'] !== "") {
111 that.clear();
120 that.clear();
112 that.expand();
121 that.expand();
113 that.append_text(payload.data['text/plain']);
122 that.append_text(payload.data['text/plain']);
114 }
123 }
115 });
124 });
116 };
125 };
117
126
118
127
119 Pager.prototype.collapse = function (extrap) {
128 Pager.prototype.collapse = function (extrap) {
120 if (this.expanded === true) {
129 if (this.expanded === true) {
121 this.expanded = false;
130 this.expanded = false;
122 this.pager_element.add($('div#notebook')).trigger('collapse_pager', extrap);
131 this.pager_element.add($('div#notebook')).trigger('collapse_pager', extrap);
123 }
132 }
124 };
133 };
125
134
126
135
127 Pager.prototype.expand = function (extrap) {
136 Pager.prototype.expand = function (extrap) {
128 if (this.expanded !== true) {
137 if (this.expanded !== true) {
129 this.expanded = true;
138 this.expanded = true;
130 this.pager_element.add($('div#notebook')).trigger('expand_pager', extrap);
139 this.pager_element.add($('div#notebook')).trigger('expand_pager', extrap);
131 }
140 }
132 };
141 };
133
142
134
143
135 Pager.prototype.toggle = function () {
144 Pager.prototype.toggle = function () {
136 if (this.expanded === true) {
145 if (this.expanded === true) {
137 this.collapse();
146 this.collapse();
138 } else {
147 } else {
139 this.expand();
148 this.expand();
140 }
149 }
141 };
150 };
142
151
143
152
144 Pager.prototype.clear = function (text) {
153 Pager.prototype.clear = function (text) {
145 this.pager_element.find(".container").empty();
154 this.pager_element.find(".container").empty();
146 };
155 };
147
156
148 Pager.prototype.detach = function(){
157 Pager.prototype.detach = function(){
149 var w = window.open("","_blank");
158 var w = window.open("","_blank");
150 $(w.document.head)
159 $(w.document.head)
151 .append(
160 .append(
152 $('<link>')
161 $('<link>')
153 .attr('rel',"stylesheet")
162 .attr('rel',"stylesheet")
154 .attr('href',"/static/css/notebook.css")
163 .attr('href',"/static/css/notebook.css")
155 .attr('type',"text/css")
164 .attr('type',"text/css")
156 )
165 )
157 .append(
166 .append(
158 $('<title>').text("IPython Pager")
167 $('<title>').text("IPython Pager")
159 );
168 );
160 var pager_body = $(w.document.body);
169 var pager_body = $(w.document.body);
161 pager_body.css('overflow','scroll');
170 pager_body.css('overflow','scroll');
162
171
163 pager_body.append(this.pager_element.clone().children());
172 pager_body.append(this.pager_element.clone().children());
164 w.document.close();
173 w.document.close();
165 this.collapse();
174 this.collapse();
166 };
175 };
167
176
168 Pager.prototype.append_text = function (text) {
177 Pager.prototype.append_text = function (text) {
169 // The only user content injected with this HTML call is escaped by
178 // The only user content injected with this HTML call is escaped by
170 // the fixConsole() method.
179 // the fixConsole() method.
171 this.pager_element.find(".container").append($('<pre/>').html(utils.fixCarriageReturn(utils.fixConsole(text))));
180 this.pager_element.find(".container").append($('<pre/>').html(utils.fixCarriageReturn(utils.fixConsole(text))));
172 };
181 };
173
182
174 // Backwards compatability.
183 // Backwards compatability.
175 IPython.Pager = Pager;
184 IPython.Pager = Pager;
176
185
177 return {'Pager': Pager};
186 return {'Pager': Pager};
178 });
187 });
@@ -1,173 +1,180 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 (options) {
13 var QuickHelp = function (options) {
14 // Constructor
15 //
16 // Parameters:
17 // options: dictionary
18 // Dictionary of keyword arguments.
19 // events: $(Events) instance
20 // keyboard_manager: KeyboardManager instance
14 this.keyboard_manager = options.keyboard_manager;
21 this.keyboard_manager = options.keyboard_manager;
15 this.keyboard_manager.quick_help = this;
22 this.keyboard_manager.quick_help = this;
16 this.events = options.events;
23 this.events = options.events;
17 };
24 };
18
25
19 var cmd_ctrl = 'Ctrl-';
26 var cmd_ctrl = 'Ctrl-';
20 var platform_specific;
27 var platform_specific;
21
28
22 if (platform === 'MacOS') {
29 if (platform === 'MacOS') {
23 // Mac OS X specific
30 // Mac OS X specific
24 cmd_ctrl = 'Cmd-';
31 cmd_ctrl = 'Cmd-';
25 platform_specific = [
32 platform_specific = [
26 { shortcut: "Cmd-Up", help:"go to cell start" },
33 { shortcut: "Cmd-Up", help:"go to cell start" },
27 { shortcut: "Cmd-Down", help:"go to cell end" },
34 { shortcut: "Cmd-Down", help:"go to cell end" },
28 { shortcut: "Opt-Left", help:"go one word left" },
35 { shortcut: "Opt-Left", help:"go one word left" },
29 { shortcut: "Opt-Right", help:"go one word right" },
36 { shortcut: "Opt-Right", help:"go one word right" },
30 { shortcut: "Opt-Backspace", help:"del word before" },
37 { shortcut: "Opt-Backspace", help:"del word before" },
31 { shortcut: "Opt-Delete", help:"del word after" },
38 { shortcut: "Opt-Delete", help:"del word after" },
32 ];
39 ];
33 } else {
40 } else {
34 // PC specific
41 // PC specific
35 platform_specific = [
42 platform_specific = [
36 { shortcut: "Ctrl-Home", help:"go to cell start" },
43 { shortcut: "Ctrl-Home", help:"go to cell start" },
37 { shortcut: "Ctrl-Up", help:"go to cell start" },
44 { shortcut: "Ctrl-Up", help:"go to cell start" },
38 { shortcut: "Ctrl-End", help:"go to cell end" },
45 { shortcut: "Ctrl-End", help:"go to cell end" },
39 { shortcut: "Ctrl-Down", help:"go to cell end" },
46 { shortcut: "Ctrl-Down", help:"go to cell end" },
40 { shortcut: "Ctrl-Left", help:"go one word left" },
47 { shortcut: "Ctrl-Left", help:"go one word left" },
41 { shortcut: "Ctrl-Right", help:"go one word right" },
48 { shortcut: "Ctrl-Right", help:"go one word right" },
42 { shortcut: "Ctrl-Backspace", help:"del word before" },
49 { shortcut: "Ctrl-Backspace", help:"del word before" },
43 { shortcut: "Ctrl-Delete", help:"del word after" },
50 { shortcut: "Ctrl-Delete", help:"del word after" },
44 ];
51 ];
45 }
52 }
46
53
47 var cm_shortcuts = [
54 var cm_shortcuts = [
48 { shortcut:"Tab", help:"code completion or indent" },
55 { shortcut:"Tab", help:"code completion or indent" },
49 { shortcut:"Shift-Tab", help:"tooltip" },
56 { shortcut:"Shift-Tab", help:"tooltip" },
50 { shortcut: cmd_ctrl + "]", help:"indent" },
57 { shortcut: cmd_ctrl + "]", help:"indent" },
51 { shortcut: cmd_ctrl + "[", help:"dedent" },
58 { shortcut: cmd_ctrl + "[", help:"dedent" },
52 { shortcut: cmd_ctrl + "a", help:"select all" },
59 { shortcut: cmd_ctrl + "a", help:"select all" },
53 { shortcut: cmd_ctrl + "z", help:"undo" },
60 { shortcut: cmd_ctrl + "z", help:"undo" },
54 { shortcut: cmd_ctrl + "Shift-z", help:"redo" },
61 { shortcut: cmd_ctrl + "Shift-z", help:"redo" },
55 { shortcut: cmd_ctrl + "y", help:"redo" },
62 { shortcut: cmd_ctrl + "y", help:"redo" },
56 ].concat( platform_specific );
63 ].concat( platform_specific );
57
64
58
65
59
66
60
67
61
68
62
69
63 QuickHelp.prototype.show_keyboard_shortcuts = function () {
70 QuickHelp.prototype.show_keyboard_shortcuts = function () {
64 // toggles display of keyboard shortcut dialog
71 // toggles display of keyboard shortcut dialog
65 var that = this;
72 var that = this;
66 if ( this.force_rebuild ) {
73 if ( this.force_rebuild ) {
67 this.shortcut_dialog.remove();
74 this.shortcut_dialog.remove();
68 delete(this.shortcut_dialog);
75 delete(this.shortcut_dialog);
69 this.force_rebuild = false;
76 this.force_rebuild = false;
70 }
77 }
71 if ( this.shortcut_dialog ){
78 if ( this.shortcut_dialog ){
72 // if dialog is already shown, close it
79 // if dialog is already shown, close it
73 $(this.shortcut_dialog).modal("toggle");
80 $(this.shortcut_dialog).modal("toggle");
74 return;
81 return;
75 }
82 }
76 var command_shortcuts = this.keyboard_manager.command_shortcuts.help();
83 var command_shortcuts = this.keyboard_manager.command_shortcuts.help();
77 var edit_shortcuts = this.keyboard_manager.edit_shortcuts.help();
84 var edit_shortcuts = this.keyboard_manager.edit_shortcuts.help();
78 var help, shortcut;
85 var help, shortcut;
79 var i, half, n;
86 var i, half, n;
80 var element = $('<div/>');
87 var element = $('<div/>');
81
88
82 // The documentation
89 // The documentation
83 var doc = $('<div/>').addClass('alert alert-warning');
90 var doc = $('<div/>').addClass('alert alert-warning');
84 doc.append(
91 doc.append(
85 $('<button/>').addClass('close').attr('data-dismiss','alert').html('&times;')
92 $('<button/>').addClass('close').attr('data-dismiss','alert').html('&times;')
86 ).append(
93 ).append(
87 'The IPython Notebook has two different keyboard input modes. <b>Edit mode</b> '+
94 '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 '+
95 '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 '+
96 'border. <b>Command mode</b> binds the keyboard to notebook level actions '+
90 'and is indicated by a grey cell border.'
97 'and is indicated by a grey cell border.'
91 );
98 );
92 element.append(doc);
99 element.append(doc);
93
100
94 // Command mode
101 // Command mode
95 var cmd_div = this.build_command_help();
102 var cmd_div = this.build_command_help();
96 element.append(cmd_div);
103 element.append(cmd_div);
97
104
98 // Edit mode
105 // Edit mode
99 var edit_div = this.build_edit_help(cm_shortcuts);
106 var edit_div = this.build_edit_help(cm_shortcuts);
100 element.append(edit_div);
107 element.append(edit_div);
101
108
102 this.shortcut_dialog = dialog.modal({
109 this.shortcut_dialog = dialog.modal({
103 title : "Keyboard shortcuts",
110 title : "Keyboard shortcuts",
104 body : element,
111 body : element,
105 destroy : false,
112 destroy : false,
106 buttons : {
113 buttons : {
107 Close : {}
114 Close : {}
108 }
115 }
109 });
116 });
110 this.shortcut_dialog.addClass("modal_stretch");
117 this.shortcut_dialog.addClass("modal_stretch");
111
118
112 this.events.on('rebuild.QuickHelp', function() { that.force_rebuild = true;});
119 this.events.on('rebuild.QuickHelp', function() { that.force_rebuild = true;});
113 };
120 };
114
121
115 QuickHelp.prototype.build_command_help = function () {
122 QuickHelp.prototype.build_command_help = function () {
116 var command_shortcuts = this.keyboard_manager.command_shortcuts.help();
123 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);
124 return build_div('<h4>Command Mode (press <code>Esc</code> to enable)</h4>', command_shortcuts);
118 };
125 };
119
126
120 var special_case = { pageup: "PageUp", pagedown: "Page Down", 'minus': '-' };
127 var special_case = { pageup: "PageUp", pagedown: "Page Down", 'minus': '-' };
121 var prettify = function (s) {
128 var prettify = function (s) {
122 s = s.replace(/-$/, 'minus'); // catch shortcuts using '-' key
129 s = s.replace(/-$/, 'minus'); // catch shortcuts using '-' key
123 var keys = s.split('-');
130 var keys = s.split('-');
124 var k, i;
131 var k, i;
125 for (i=0; i < keys.length; i++) {
132 for (i=0; i < keys.length; i++) {
126 k = keys[i];
133 k = keys[i];
127 if ( k.length == 1 ) {
134 if ( k.length == 1 ) {
128 keys[i] = "<code><strong>" + k + "</strong></code>";
135 keys[i] = "<code><strong>" + k + "</strong></code>";
129 continue; // leave individual keys lower-cased
136 continue; // leave individual keys lower-cased
130 }
137 }
131 keys[i] = ( special_case[k] ? special_case[k] : k.charAt(0).toUpperCase() + k.slice(1) );
138 keys[i] = ( special_case[k] ? special_case[k] : k.charAt(0).toUpperCase() + k.slice(1) );
132 keys[i] = "<code><strong>" + keys[i] + "</strong></code>";
139 keys[i] = "<code><strong>" + keys[i] + "</strong></code>";
133 }
140 }
134 return keys.join('-');
141 return keys.join('-');
135
142
136
143
137 };
144 };
138
145
139 QuickHelp.prototype.build_edit_help = function (cm_shortcuts) {
146 QuickHelp.prototype.build_edit_help = function (cm_shortcuts) {
140 var edit_shortcuts = this.keyboard_manager.edit_shortcuts.help();
147 var edit_shortcuts = this.keyboard_manager.edit_shortcuts.help();
141 jQuery.merge(cm_shortcuts, edit_shortcuts);
148 jQuery.merge(cm_shortcuts, edit_shortcuts);
142 return build_div('<h4>Edit Mode (press <code>Enter</code> to enable)</h4>', cm_shortcuts);
149 return build_div('<h4>Edit Mode (press <code>Enter</code> to enable)</h4>', cm_shortcuts);
143 };
150 };
144
151
145 var build_one = function (s) {
152 var build_one = function (s) {
146 var help = s.help;
153 var help = s.help;
147 var shortcut = prettify(s.shortcut);
154 var shortcut = prettify(s.shortcut);
148 return $('<div>').addClass('quickhelp').
155 return $('<div>').addClass('quickhelp').
149 append($('<span/>').addClass('shortcut_key').append($(shortcut))).
156 append($('<span/>').addClass('shortcut_key').append($(shortcut))).
150 append($('<span/>').addClass('shortcut_descr').text(' : ' + help));
157 append($('<span/>').addClass('shortcut_descr').text(' : ' + help));
151
158
152 };
159 };
153
160
154 var build_div = function (title, shortcuts) {
161 var build_div = function (title, shortcuts) {
155 var i, half, n;
162 var i, half, n;
156 var div = $('<div/>').append($(title));
163 var div = $('<div/>').append($(title));
157 var sub_div = $('<div/>').addClass('hbox');
164 var sub_div = $('<div/>').addClass('hbox');
158 var col1 = $('<div/>').addClass('box-flex1');
165 var col1 = $('<div/>').addClass('box-flex1');
159 var col2 = $('<div/>').addClass('box-flex1');
166 var col2 = $('<div/>').addClass('box-flex1');
160 n = shortcuts.length;
167 n = shortcuts.length;
161 half = ~~(n/2); // Truncate :)
168 half = ~~(n/2); // Truncate :)
162 for (i=0; i<half; i++) { col1.append( build_one(shortcuts[i]) ); }
169 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]) ); }
170 for (i=half; i<n; i++) { col2.append( build_one(shortcuts[i]) ); }
164 sub_div.append(col1).append(col2);
171 sub_div.append(col1).append(col2);
165 div.append(sub_div);
172 div.append(sub_div);
166 return div;
173 return div;
167 };
174 };
168
175
169 // Backwards compatability.
176 // Backwards compatability.
170 IPython.QuickHelp = QuickHelp;
177 IPython.QuickHelp = QuickHelp;
171
178
172 return {'QuickHelp': QuickHelp};
179 return {'QuickHelp': QuickHelp};
173 });
180 });
@@ -1,172 +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 'base/js/keyboard',
9 'base/js/keyboard',
10 'dateformat/date.format',
10 'dateformat/date.format',
11 ], function(IPython, $, utils, dialog, keyboard) {
11 ], function(IPython, $, utils, dialog, keyboard) {
12 "use strict";
12 "use strict";
13
13
14 var SaveWidget = function (selector, events) {
14 var SaveWidget = function (selector, events) {
15 // TODO: Remove circulat ref.
15 this.notebook = undefined;
16 this.notebook = undefined;
16 this.selector = selector;
17 this.selector = selector;
17 this.events = events;
18 this.events = events;
18 if (this.selector !== undefined) {
19 if (this.selector !== undefined) {
19 this.element = $(selector);
20 this.element = $(selector);
20 this.style();
21 this.style();
21 this.bind_events();
22 this.bind_events();
22 }
23 }
23 };
24 };
24
25
25 SaveWidget.prototype.style = function () {
26 SaveWidget.prototype.style = function () {
26 };
27 };
27
28
28
29
29 SaveWidget.prototype.bind_events = function () {
30 SaveWidget.prototype.bind_events = function () {
30 var that = this;
31 var that = this;
31 this.element.find('span#notebook_name').click(function () {
32 this.element.find('span#notebook_name').click(function () {
32 that.rename_notebook();
33 that.rename_notebook();
33 });
34 });
34 this.element.find('span#notebook_name').hover(function () {
35 this.element.find('span#notebook_name').hover(function () {
35 $(this).addClass("ui-state-hover");
36 $(this).addClass("ui-state-hover");
36 }, function () {
37 }, function () {
37 $(this).removeClass("ui-state-hover");
38 $(this).removeClass("ui-state-hover");
38 });
39 });
39 this.events.on('notebook_loaded.Notebook', function () {
40 this.events.on('notebook_loaded.Notebook', function () {
40 that.update_notebook_name();
41 that.update_notebook_name();
41 that.update_document_title();
42 that.update_document_title();
42 });
43 });
43 this.events.on('notebook_saved.Notebook', function () {
44 this.events.on('notebook_saved.Notebook', function () {
44 that.update_notebook_name();
45 that.update_notebook_name();
45 that.update_document_title();
46 that.update_document_title();
46 });
47 });
47 this.events.on('notebook_renamed.Notebook', function () {
48 this.events.on('notebook_renamed.Notebook', function () {
48 that.update_notebook_name();
49 that.update_notebook_name();
49 that.update_document_title();
50 that.update_document_title();
50 that.update_address_bar();
51 that.update_address_bar();
51 });
52 });
52 this.events.on('notebook_save_failed.Notebook', function () {
53 this.events.on('notebook_save_failed.Notebook', function () {
53 that.set_save_status('Autosave Failed!');
54 that.set_save_status('Autosave Failed!');
54 });
55 });
55 this.events.on('checkpoints_listed.Notebook', function (event, data) {
56 this.events.on('checkpoints_listed.Notebook', function (event, data) {
56 that.set_last_checkpoint(data[0]);
57 that.set_last_checkpoint(data[0]);
57 });
58 });
58
59
59 this.events.on('checkpoint_created.Notebook', function (event, data) {
60 this.events.on('checkpoint_created.Notebook', function (event, data) {
60 that.set_last_checkpoint(data);
61 that.set_last_checkpoint(data);
61 });
62 });
62 this.events.on('set_dirty.Notebook', function (event, data) {
63 this.events.on('set_dirty.Notebook', function (event, data) {
63 that.set_autosaved(data.value);
64 that.set_autosaved(data.value);
64 });
65 });
65 };
66 };
66
67
67
68
68 SaveWidget.prototype.rename_notebook = function () {
69 SaveWidget.prototype.rename_notebook = function () {
69 var that = this;
70 var that = this;
70 var dialog_body = $('<div/>').append(
71 var dialog_body = $('<div/>').append(
71 $("<p/>").addClass("rename-message")
72 $("<p/>").addClass("rename-message")
72 .text('Enter a new notebook name:')
73 .text('Enter a new notebook name:')
73 ).append(
74 ).append(
74 $("<br/>")
75 $("<br/>")
75 ).append(
76 ).append(
76 $('<input/>').attr('type','text').attr('size','25').addClass('form-control')
77 $('<input/>').attr('type','text').attr('size','25').addClass('form-control')
77 .val(that.notebook.get_notebook_name())
78 .val(that.notebook.get_notebook_name())
78 );
79 );
79 dialog.modal({
80 dialog.modal({
80 title: "Rename Notebook",
81 title: "Rename Notebook",
81 body: dialog_body,
82 body: dialog_body,
82 buttons : {
83 buttons : {
83 "Cancel": {},
84 "Cancel": {},
84 "OK": {
85 "OK": {
85 class: "btn-primary",
86 class: "btn-primary",
86 click: function () {
87 click: function () {
87 var new_name = $(this).find('input').val();
88 var new_name = $(this).find('input').val();
88 if (!that.notebook.test_notebook_name(new_name)) {
89 if (!that.notebook.test_notebook_name(new_name)) {
89 $(this).find('.rename-message').text(
90 $(this).find('.rename-message').text(
90 "Invalid notebook name. Notebook names must "+
91 "Invalid notebook name. Notebook names must "+
91 "have 1 or more characters and can contain any characters " +
92 "have 1 or more characters and can contain any characters " +
92 "except :/\\. Please enter a new notebook name:"
93 "except :/\\. Please enter a new notebook name:"
93 );
94 );
94 return false;
95 return false;
95 } else {
96 } else {
96 that.notebook.rename(new_name);
97 that.notebook.rename(new_name);
97 }
98 }
98 }}
99 }}
99 },
100 },
100 open : function (event, ui) {
101 open : function (event, ui) {
101 var that = $(this);
102 var that = $(this);
102 // Upon ENTER, click the OK button.
103 // Upon ENTER, click the OK button.
103 that.find('input[type="text"]').keydown(function (event, ui) {
104 that.find('input[type="text"]').keydown(function (event, ui) {
104 if (event.which === that.keyboard.keycodes.enter) {
105 if (event.which === that.keyboard.keycodes.enter) {
105 that.find('.btn-primary').first().click();
106 that.find('.btn-primary').first().click();
106 return false;
107 return false;
107 }
108 }
108 });
109 });
109 that.find('input[type="text"]').focus().select();
110 that.find('input[type="text"]').focus().select();
110 }
111 }
111 });
112 });
112 };
113 };
113
114
114
115
115 SaveWidget.prototype.update_notebook_name = function () {
116 SaveWidget.prototype.update_notebook_name = function () {
116 var nbname = this.notebook.get_notebook_name();
117 var nbname = this.notebook.get_notebook_name();
117 this.element.find('span#notebook_name').text(nbname);
118 this.element.find('span#notebook_name').text(nbname);
118 };
119 };
119
120
120
121
121 SaveWidget.prototype.update_document_title = function () {
122 SaveWidget.prototype.update_document_title = function () {
122 var nbname = this.notebook.get_notebook_name();
123 var nbname = this.notebook.get_notebook_name();
123 document.title = nbname;
124 document.title = nbname;
124 };
125 };
125
126
126 SaveWidget.prototype.update_address_bar = function(){
127 SaveWidget.prototype.update_address_bar = function(){
127 var base_url = this.notebook.base_url;
128 var base_url = this.notebook.base_url;
128 var nbname = this.notebook.notebook_name;
129 var nbname = this.notebook.notebook_name;
129 var path = this.notebook.notebook_path;
130 var path = this.notebook.notebook_path;
130 var state = {path : path, name: nbname};
131 var state = {path : path, name: nbname};
131 window.history.replaceState(state, "", utils.url_join_encode(
132 window.history.replaceState(state, "", utils.url_join_encode(
132 base_url,
133 base_url,
133 "notebooks",
134 "notebooks",
134 path,
135 path,
135 nbname)
136 nbname)
136 );
137 );
137 };
138 };
138
139
139
140
140 SaveWidget.prototype.set_save_status = function (msg) {
141 SaveWidget.prototype.set_save_status = function (msg) {
141 this.element.find('span#autosave_status').text(msg);
142 this.element.find('span#autosave_status').text(msg);
142 };
143 };
143
144
144 SaveWidget.prototype.set_checkpoint_status = function (msg) {
145 SaveWidget.prototype.set_checkpoint_status = function (msg) {
145 this.element.find('span#checkpoint_status').text(msg);
146 this.element.find('span#checkpoint_status').text(msg);
146 };
147 };
147
148
148 SaveWidget.prototype.set_last_checkpoint = function (checkpoint) {
149 SaveWidget.prototype.set_last_checkpoint = function (checkpoint) {
149 if (!checkpoint) {
150 if (!checkpoint) {
150 this.set_checkpoint_status("");
151 this.set_checkpoint_status("");
151 return;
152 return;
152 }
153 }
153 var d = new Date(checkpoint.last_modified);
154 var d = new Date(checkpoint.last_modified);
154 this.set_checkpoint_status(
155 this.set_checkpoint_status(
155 "Last Checkpoint: " + d.format('mmm dd HH:MM')
156 "Last Checkpoint: " + d.format('mmm dd HH:MM')
156 );
157 );
157 };
158 };
158
159
159 SaveWidget.prototype.set_autosaved = function (dirty) {
160 SaveWidget.prototype.set_autosaved = function (dirty) {
160 if (dirty) {
161 if (dirty) {
161 this.set_save_status("(unsaved changes)");
162 this.set_save_status("(unsaved changes)");
162 } else {
163 } else {
163 this.set_save_status("(autosaved)");
164 this.set_save_status("(autosaved)");
164 }
165 }
165 };
166 };
166
167
167 // Backwards compatability.
168 // Backwards compatability.
168 IPython.SaveWidget = SaveWidget;
169 IPython.SaveWidget = SaveWidget;
169
170
170 return {'SaveWidget': SaveWidget};
171 return {'SaveWidget': SaveWidget};
171
172
172 });
173 });
@@ -1,453 +1,461 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 /**
17 * Construct a new TextCell, codemirror mode is by default 'htmlmixed', and cell type is 'text'
18 * cell start as not redered.
19 *
20 * @class TextCell
21 * @constructor TextCell
22 * @extend Cell
23 * @param {object|undefined} [options]
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)
26 */
27 var TextCell = function (options) {
16 var TextCell = function (options) {
17 // Constructor
18 //
19 // Construct a new TextCell, codemirror mode is by default 'htmlmixed',
20 // and cell type is 'text' cell start as not redered.
21 //
22 // Parameters:
23 // options: dictionary
24 // Dictionary of keyword arguments.
25 // events: $(Events) instance
26 // config: dictionary
27 // keyboard_manager: KeyboardManager instance
28 // notebook: Notebook instance
28 options = options || {};
29 options = options || {};
30
29 // in all TextCell/Cell subclasses
31 // in all TextCell/Cell subclasses
30 // do not assign most of members here, just pass it down
32 // do not assign most of members here, just pass it down
31 // in the options dict potentially overwriting what you wish.
33 // in the options dict potentially overwriting what you wish.
32 // they will be assigned in the base class.
34 // they will be assigned in the base class.
33 this.notebook = options.notebook;
35 this.notebook = options.notebook;
34 this.events = options.events;
36 this.events = options.events;
35 this.config = options.config;
37 this.config = options.config;
36
38
37 // we cannot put this as a class key as it has handle to "this".
39 // we cannot put this as a class key as it has handle to "this".
38 var cm_overwrite_options = {
40 var cm_overwrite_options = {
39 onKeyEvent: $.proxy(this.handle_keyevent,this)
41 onKeyEvent: $.proxy(this.handle_keyevent,this)
40 };
42 };
41 var config = this.mergeopt(TextCell, this.config, {cm_config:cm_overwrite_options});
43 var config = this.mergeopt(TextCell, this.config, {cm_config:cm_overwrite_options});
42 Cell.apply(this, [{
44 Cell.apply(this, [{
43 config: config,
45 config: config,
44 keyboard_manager: options.keyboard_manager,
46 keyboard_manager: options.keyboard_manager,
45 events: events}]);
47 events: events}]);
46
48
47 this.cell_type = this.cell_type || 'text';
49 this.cell_type = this.cell_type || 'text';
48 mathjaxutils = mathjaxutils;
50 mathjaxutils = mathjaxutils;
49 this.rendered = false;
51 this.rendered = false;
50 };
52 };
51
53
52 TextCell.prototype = new Cell();
54 TextCell.prototype = new Cell();
53
55
54 TextCell.options_default = {
56 TextCell.options_default = {
55 cm_config : {
57 cm_config : {
56 extraKeys: {"Tab": "indentMore","Shift-Tab" : "indentLess"},
58 extraKeys: {"Tab": "indentMore","Shift-Tab" : "indentLess"},
57 mode: 'htmlmixed',
59 mode: 'htmlmixed',
58 lineWrapping : true,
60 lineWrapping : true,
59 }
61 }
60 };
62 };
61
63
62
64
63 /**
65 /**
64 * Create the DOM element of the TextCell
66 * Create the DOM element of the TextCell
65 * @method create_element
67 * @method create_element
66 * @private
68 * @private
67 */
69 */
68 TextCell.prototype.create_element = function () {
70 TextCell.prototype.create_element = function () {
69 Cell.prototype.create_element.apply(this, arguments);
71 Cell.prototype.create_element.apply(this, arguments);
70
72
71 var cell = $("<div>").addClass('cell text_cell border-box-sizing');
73 var cell = $("<div>").addClass('cell text_cell border-box-sizing');
72 cell.attr('tabindex','2');
74 cell.attr('tabindex','2');
73
75
74 var prompt = $('<div/>').addClass('prompt input_prompt');
76 var prompt = $('<div/>').addClass('prompt input_prompt');
75 cell.append(prompt);
77 cell.append(prompt);
76 var inner_cell = $('<div/>').addClass('inner_cell');
78 var inner_cell = $('<div/>').addClass('inner_cell');
77 this.celltoolbar = new celltoolbar.CellToolbar(this, this.events, this.notebook);
79 this.celltoolbar = new celltoolbar.CellToolbar(this, this.events, this.notebook);
78 inner_cell.append(this.celltoolbar.element);
80 inner_cell.append(this.celltoolbar.element);
79 var input_area = $('<div/>').addClass('input_area');
81 var input_area = $('<div/>').addClass('input_area');
80 this.code_mirror = new CodeMirror(input_area.get(0), this.cm_config);
82 this.code_mirror = new CodeMirror(input_area.get(0), this.cm_config);
81 // The tabindex=-1 makes this div focusable.
83 // The tabindex=-1 makes this div focusable.
82 var render_area = $('<div/>').addClass('text_cell_render border-box-sizing').
84 var render_area = $('<div/>').addClass('text_cell_render border-box-sizing').
83 addClass('rendered_html').attr('tabindex','-1');
85 addClass('rendered_html').attr('tabindex','-1');
84 inner_cell.append(input_area).append(render_area);
86 inner_cell.append(input_area).append(render_area);
85 cell.append(inner_cell);
87 cell.append(inner_cell);
86 this.element = cell;
88 this.element = cell;
87 };
89 };
88
90
89
91
90 /**
92 /**
91 * Bind the DOM evet to cell actions
93 * Bind the DOM evet to cell actions
92 * Need to be called after TextCell.create_element
94 * Need to be called after TextCell.create_element
93 * @private
95 * @private
94 * @method bind_event
96 * @method bind_event
95 */
97 */
96 TextCell.prototype.bind_events = function () {
98 TextCell.prototype.bind_events = function () {
97 Cell.prototype.bind_events.apply(this);
99 Cell.prototype.bind_events.apply(this);
98 var that = this;
100 var that = this;
99
101
100 this.element.dblclick(function () {
102 this.element.dblclick(function () {
101 if (that.selected === false) {
103 if (that.selected === false) {
102 this.events.trigger('select.Cell', {'cell':that});
104 this.events.trigger('select.Cell', {'cell':that});
103 }
105 }
104 var cont = that.unrender();
106 var cont = that.unrender();
105 if (cont) {
107 if (cont) {
106 that.focus_editor();
108 that.focus_editor();
107 }
109 }
108 });
110 });
109 };
111 };
110
112
111 // Cell level actions
113 // Cell level actions
112
114
113 TextCell.prototype.select = function () {
115 TextCell.prototype.select = function () {
114 var cont = Cell.prototype.select.apply(this);
116 var cont = Cell.prototype.select.apply(this);
115 if (cont) {
117 if (cont) {
116 if (this.mode === 'edit') {
118 if (this.mode === 'edit') {
117 this.code_mirror.refresh();
119 this.code_mirror.refresh();
118 }
120 }
119 }
121 }
120 return cont;
122 return cont;
121 };
123 };
122
124
123 TextCell.prototype.unrender = function () {
125 TextCell.prototype.unrender = function () {
124 if (this.read_only) return;
126 if (this.read_only) return;
125 var cont = Cell.prototype.unrender.apply(this);
127 var cont = Cell.prototype.unrender.apply(this);
126 if (cont) {
128 if (cont) {
127 var text_cell = this.element;
129 var text_cell = this.element;
128 var output = text_cell.find("div.text_cell_render");
130 var output = text_cell.find("div.text_cell_render");
129 output.hide();
131 output.hide();
130 text_cell.find('div.input_area').show();
132 text_cell.find('div.input_area').show();
131 if (this.get_text() === this.placeholder) {
133 if (this.get_text() === this.placeholder) {
132 this.set_text('');
134 this.set_text('');
133 }
135 }
134 this.refresh();
136 this.refresh();
135 }
137 }
136 if (this.celltoolbar.ui_controls_list.length) {
138 if (this.celltoolbar.ui_controls_list.length) {
137 this.celltoolbar.show();
139 this.celltoolbar.show();
138 }
140 }
139 return cont;
141 return cont;
140 };
142 };
141
143
142 TextCell.prototype.execute = function () {
144 TextCell.prototype.execute = function () {
143 this.render();
145 this.render();
144 };
146 };
145
147
146 /**
148 /**
147 * setter: {{#crossLink "TextCell/set_text"}}{{/crossLink}}
149 * setter: {{#crossLink "TextCell/set_text"}}{{/crossLink}}
148 * @method get_text
150 * @method get_text
149 * @retrun {string} CodeMirror current text value
151 * @retrun {string} CodeMirror current text value
150 */
152 */
151 TextCell.prototype.get_text = function() {
153 TextCell.prototype.get_text = function() {
152 return this.code_mirror.getValue();
154 return this.code_mirror.getValue();
153 };
155 };
154
156
155 /**
157 /**
156 * @param {string} text - Codemiror text value
158 * @param {string} text - Codemiror text value
157 * @see TextCell#get_text
159 * @see TextCell#get_text
158 * @method set_text
160 * @method set_text
159 * */
161 * */
160 TextCell.prototype.set_text = function(text) {
162 TextCell.prototype.set_text = function(text) {
161 this.code_mirror.setValue(text);
163 this.code_mirror.setValue(text);
162 this.code_mirror.refresh();
164 this.code_mirror.refresh();
163 };
165 };
164
166
165 /**
167 /**
166 * setter :{{#crossLink "TextCell/set_rendered"}}{{/crossLink}}
168 * setter :{{#crossLink "TextCell/set_rendered"}}{{/crossLink}}
167 * @method get_rendered
169 * @method get_rendered
168 * */
170 * */
169 TextCell.prototype.get_rendered = function() {
171 TextCell.prototype.get_rendered = function() {
170 return this.element.find('div.text_cell_render').html();
172 return this.element.find('div.text_cell_render').html();
171 };
173 };
172
174
173 /**
175 /**
174 * @method set_rendered
176 * @method set_rendered
175 */
177 */
176 TextCell.prototype.set_rendered = function(text) {
178 TextCell.prototype.set_rendered = function(text) {
177 this.element.find('div.text_cell_render').html(text);
179 this.element.find('div.text_cell_render').html(text);
178 this.celltoolbar.hide();
180 this.celltoolbar.hide();
179 };
181 };
180
182
181
183
182 /**
184 /**
183 * Create Text cell from JSON
185 * Create Text cell from JSON
184 * @param {json} data - JSON serialized text-cell
186 * @param {json} data - JSON serialized text-cell
185 * @method fromJSON
187 * @method fromJSON
186 */
188 */
187 TextCell.prototype.fromJSON = function (data) {
189 TextCell.prototype.fromJSON = function (data) {
188 Cell.prototype.fromJSON.apply(this, arguments);
190 Cell.prototype.fromJSON.apply(this, arguments);
189 if (data.cell_type === this.cell_type) {
191 if (data.cell_type === this.cell_type) {
190 if (data.source !== undefined) {
192 if (data.source !== undefined) {
191 this.set_text(data.source);
193 this.set_text(data.source);
192 // make this value the starting point, so that we can only undo
194 // make this value the starting point, so that we can only undo
193 // to this state, instead of a blank cell
195 // to this state, instead of a blank cell
194 this.code_mirror.clearHistory();
196 this.code_mirror.clearHistory();
195 // TODO: This HTML needs to be treated as potentially dangerous
197 // TODO: This HTML needs to be treated as potentially dangerous
196 // user input and should be handled before set_rendered.
198 // user input and should be handled before set_rendered.
197 this.set_rendered(data.rendered || '');
199 this.set_rendered(data.rendered || '');
198 this.rendered = false;
200 this.rendered = false;
199 this.render();
201 this.render();
200 }
202 }
201 }
203 }
202 };
204 };
203
205
204 /** Generate JSON from cell
206 /** Generate JSON from cell
205 * @return {object} cell data serialised to json
207 * @return {object} cell data serialised to json
206 */
208 */
207 TextCell.prototype.toJSON = function () {
209 TextCell.prototype.toJSON = function () {
208 var data = Cell.prototype.toJSON.apply(this);
210 var data = Cell.prototype.toJSON.apply(this);
209 data.source = this.get_text();
211 data.source = this.get_text();
210 if (data.source == this.placeholder) {
212 if (data.source == this.placeholder) {
211 data.source = "";
213 data.source = "";
212 }
214 }
213 return data;
215 return data;
214 };
216 };
215
217
216
218
217 /**
218 * @class MarkdownCell
219 * @constructor MarkdownCell
220 * @extends IPython.HTMLCell
221 */
222 var MarkdownCell = function (options) {
219 var MarkdownCell = function (options) {
220 // Constructor
221 //
222 // Parameters:
223 // options: dictionary
224 // Dictionary of keyword arguments.
225 // events: $(Events) instance
226 // config: dictionary
227 // keyboard_manager: KeyboardManager instance
228 // notebook: Notebook instance
223 options = options || {};
229 options = options || {};
224 var config = this.mergeopt(MarkdownCell, options.config);
230 var config = this.mergeopt(MarkdownCell, options.config);
225 TextCell.apply(this, [$.extend({}, options, {config: config})]);
231 TextCell.apply(this, [$.extend({}, options, {config: config})]);
226
232
227 this.cell_type = 'markdown';
233 this.cell_type = 'markdown';
228 };
234 };
229
235
230 MarkdownCell.options_default = {
236 MarkdownCell.options_default = {
231 cm_config: {
237 cm_config: {
232 mode: 'ipythongfm'
238 mode: 'ipythongfm'
233 },
239 },
234 placeholder: "Type *Markdown* and LaTeX: $\\alpha^2$"
240 placeholder: "Type *Markdown* and LaTeX: $\\alpha^2$"
235 };
241 };
236
242
237 MarkdownCell.prototype = new TextCell();
243 MarkdownCell.prototype = new TextCell();
238
244
239 /**
245 /**
240 * @method render
246 * @method render
241 */
247 */
242 MarkdownCell.prototype.render = function () {
248 MarkdownCell.prototype.render = function () {
243 var cont = TextCell.prototype.render.apply(this);
249 var cont = TextCell.prototype.render.apply(this);
244 if (cont) {
250 if (cont) {
245 var text = this.get_text();
251 var text = this.get_text();
246 var math = null;
252 var math = null;
247 if (text === "") { text = this.placeholder; }
253 if (text === "") { text = this.placeholder; }
248 var text_and_math = mathjaxutils.remove_math(text);
254 var text_and_math = mathjaxutils.remove_math(text);
249 text = text_and_math[0];
255 text = text_and_math[0];
250 math = text_and_math[1];
256 math = text_and_math[1];
251 var html = marked.parser(marked.lexer(text));
257 var html = marked.parser(marked.lexer(text));
252 html = mathjaxutils.replace_math(html, math);
258 html = mathjaxutils.replace_math(html, math);
253 html = security.sanitize_html(html);
259 html = security.sanitize_html(html);
254 html = $($.parseHTML(html));
260 html = $($.parseHTML(html));
255 // links in markdown cells should open in new tabs
261 // links in markdown cells should open in new tabs
256 html.find("a[href]").not('[href^="#"]').attr("target", "_blank");
262 html.find("a[href]").not('[href^="#"]').attr("target", "_blank");
257 this.set_rendered(html);
263 this.set_rendered(html);
258 this.element.find('div.input_area').hide();
264 this.element.find('div.input_area').hide();
259 this.element.find("div.text_cell_render").show();
265 this.element.find("div.text_cell_render").show();
260 this.typeset();
266 this.typeset();
261 }
267 }
262 return cont;
268 return cont;
263 };
269 };
264
270
265
271
266 // RawCell
267
268 /**
269 * @class RawCell
270 * @constructor RawCell
271 * @extends TextCell
272 */
273 var RawCell = function (options) {
272 var RawCell = function (options) {
273 // Constructor
274 //
275 // Parameters:
276 // options: dictionary
277 // Dictionary of keyword arguments.
278 // events: $(Events) instance
279 // config: dictionary
280 // keyboard_manager: KeyboardManager instance
281 // notebook: Notebook instance
274 options = options || {};
282 options = options || {};
275 var config = this.mergeopt(RawCell, options.config);
283 var config = this.mergeopt(RawCell, options.config);
276 TextCell.apply(this, [$.extend({}, options, {config: config})]);
284 TextCell.apply(this, [$.extend({}, options, {config: config})]);
277
285
278 // RawCell should always hide its rendered div
286 // RawCell should always hide its rendered div
279 this.element.find('div.text_cell_render').hide();
287 this.element.find('div.text_cell_render').hide();
280 this.cell_type = 'raw';
288 this.cell_type = 'raw';
281 };
289 };
282
290
283 RawCell.options_default = {
291 RawCell.options_default = {
284 placeholder : "Write raw LaTeX or other formats here, for use with nbconvert. " +
292 placeholder : "Write raw LaTeX or other formats here, for use with nbconvert. " +
285 "It will not be rendered in the notebook. " +
293 "It will not be rendered in the notebook. " +
286 "When passing through nbconvert, a Raw Cell's content is added to the output unmodified."
294 "When passing through nbconvert, a Raw Cell's content is added to the output unmodified."
287 };
295 };
288
296
289 RawCell.prototype = new TextCell();
297 RawCell.prototype = new TextCell();
290
298
291 /** @method bind_events **/
299 /** @method bind_events **/
292 RawCell.prototype.bind_events = function () {
300 RawCell.prototype.bind_events = function () {
293 TextCell.prototype.bind_events.apply(this);
301 TextCell.prototype.bind_events.apply(this);
294 var that = this;
302 var that = this;
295 this.element.focusout(function() {
303 this.element.focusout(function() {
296 that.auto_highlight();
304 that.auto_highlight();
297 that.render();
305 that.render();
298 });
306 });
299
307
300 this.code_mirror.on('focus', function() { that.unrender(); });
308 this.code_mirror.on('focus', function() { that.unrender(); });
301 };
309 };
302
310
303 /**
311 /**
304 * Trigger autodetection of highlight scheme for current cell
312 * Trigger autodetection of highlight scheme for current cell
305 * @method auto_highlight
313 * @method auto_highlight
306 */
314 */
307 RawCell.prototype.auto_highlight = function () {
315 RawCell.prototype.auto_highlight = function () {
308 this._auto_highlight(this.config.raw_cell_highlight);
316 this._auto_highlight(this.config.raw_cell_highlight);
309 };
317 };
310
318
311 /** @method render **/
319 /** @method render **/
312 RawCell.prototype.render = function () {
320 RawCell.prototype.render = function () {
313 var cont = TextCell.prototype.render.apply(this);
321 var cont = TextCell.prototype.render.apply(this);
314 if (cont){
322 if (cont){
315 var text = this.get_text();
323 var text = this.get_text();
316 if (text === "") { text = this.placeholder; }
324 if (text === "") { text = this.placeholder; }
317 this.set_text(text);
325 this.set_text(text);
318 this.element.removeClass('rendered');
326 this.element.removeClass('rendered');
319 }
327 }
320 return cont;
328 return cont;
321 };
329 };
322
330
323
331
324 /**
325 * @class HeadingCell
326 * @extends TextCell
327 */
328
329 /**
330 * @constructor HeadingCell
331 * @extends TextCell
332 */
333 var HeadingCell = function (options) {
332 var HeadingCell = function (options) {
333 // Constructor
334 //
335 // Parameters:
336 // options: dictionary
337 // Dictionary of keyword arguments.
338 // events: $(Events) instance
339 // config: dictionary
340 // keyboard_manager: KeyboardManager instance
341 // notebook: Notebook instance
334 options = options || {};
342 options = options || {};
335 var config = this.mergeopt(HeadingCell, options.config);
343 var config = this.mergeopt(HeadingCell, options.config);
336 TextCell.apply(this, [$.extend({}, options, {config: config})]);
344 TextCell.apply(this, [$.extend({}, options, {config: config})]);
337
345
338 this.level = 1;
346 this.level = 1;
339 this.cell_type = 'heading';
347 this.cell_type = 'heading';
340 };
348 };
341
349
342 HeadingCell.options_default = {
350 HeadingCell.options_default = {
343 placeholder: "Type Heading Here"
351 placeholder: "Type Heading Here"
344 };
352 };
345
353
346 HeadingCell.prototype = new TextCell();
354 HeadingCell.prototype = new TextCell();
347
355
348 /** @method fromJSON */
356 /** @method fromJSON */
349 HeadingCell.prototype.fromJSON = function (data) {
357 HeadingCell.prototype.fromJSON = function (data) {
350 if (data.level !== undefined){
358 if (data.level !== undefined){
351 this.level = data.level;
359 this.level = data.level;
352 }
360 }
353 TextCell.prototype.fromJSON.apply(this, arguments);
361 TextCell.prototype.fromJSON.apply(this, arguments);
354 };
362 };
355
363
356
364
357 /** @method toJSON */
365 /** @method toJSON */
358 HeadingCell.prototype.toJSON = function () {
366 HeadingCell.prototype.toJSON = function () {
359 var data = TextCell.prototype.toJSON.apply(this);
367 var data = TextCell.prototype.toJSON.apply(this);
360 data.level = this.get_level();
368 data.level = this.get_level();
361 return data;
369 return data;
362 };
370 };
363
371
364 /**
372 /**
365 * can the cell be split into two cells
373 * can the cell be split into two cells
366 * @method is_splittable
374 * @method is_splittable
367 **/
375 **/
368 HeadingCell.prototype.is_splittable = function () {
376 HeadingCell.prototype.is_splittable = function () {
369 return false;
377 return false;
370 };
378 };
371
379
372
380
373 /**
381 /**
374 * can the cell be merged with other cells
382 * can the cell be merged with other cells
375 * @method is_mergeable
383 * @method is_mergeable
376 **/
384 **/
377 HeadingCell.prototype.is_mergeable = function () {
385 HeadingCell.prototype.is_mergeable = function () {
378 return false;
386 return false;
379 };
387 };
380
388
381 /**
389 /**
382 * Change heading level of cell, and re-render
390 * Change heading level of cell, and re-render
383 * @method set_level
391 * @method set_level
384 */
392 */
385 HeadingCell.prototype.set_level = function (level) {
393 HeadingCell.prototype.set_level = function (level) {
386 this.level = level;
394 this.level = level;
387 if (this.rendered) {
395 if (this.rendered) {
388 this.rendered = false;
396 this.rendered = false;
389 this.render();
397 this.render();
390 }
398 }
391 };
399 };
392
400
393 /** The depth of header cell, based on html (h1 to h6)
401 /** The depth of header cell, based on html (h1 to h6)
394 * @method get_level
402 * @method get_level
395 * @return {integer} level - for 1 to 6
403 * @return {integer} level - for 1 to 6
396 */
404 */
397 HeadingCell.prototype.get_level = function () {
405 HeadingCell.prototype.get_level = function () {
398 return this.level;
406 return this.level;
399 };
407 };
400
408
401
409
402 HeadingCell.prototype.get_rendered = function () {
410 HeadingCell.prototype.get_rendered = function () {
403 var r = this.element.find("div.text_cell_render");
411 var r = this.element.find("div.text_cell_render");
404 return r.children().first().html();
412 return r.children().first().html();
405 };
413 };
406
414
407 HeadingCell.prototype.render = function () {
415 HeadingCell.prototype.render = function () {
408 var cont = TextCell.prototype.render.apply(this);
416 var cont = TextCell.prototype.render.apply(this);
409 if (cont) {
417 if (cont) {
410 var text = this.get_text();
418 var text = this.get_text();
411 var math = null;
419 var math = null;
412 // Markdown headings must be a single line
420 // Markdown headings must be a single line
413 text = text.replace(/\n/g, ' ');
421 text = text.replace(/\n/g, ' ');
414 if (text === "") { text = this.placeholder; }
422 if (text === "") { text = this.placeholder; }
415 text = new Array(this.level + 1).join("#") + " " + text;
423 text = new Array(this.level + 1).join("#") + " " + text;
416 var text_and_math = mathjaxutils.remove_math(text);
424 var text_and_math = mathjaxutils.remove_math(text);
417 text = text_and_math[0];
425 text = text_and_math[0];
418 math = text_and_math[1];
426 math = text_and_math[1];
419 var html = marked.parser(marked.lexer(text));
427 var html = marked.parser(marked.lexer(text));
420 html = mathjaxutils.replace_math(html, math);
428 html = mathjaxutils.replace_math(html, math);
421 html = security.sanitize_html(html);
429 html = security.sanitize_html(html);
422 var h = $($.parseHTML(html));
430 var h = $($.parseHTML(html));
423 // add id and linkback anchor
431 // add id and linkback anchor
424 var hash = h.text().replace(/ /g, '-');
432 var hash = h.text().replace(/ /g, '-');
425 h.attr('id', hash);
433 h.attr('id', hash);
426 h.append(
434 h.append(
427 $('<a/>')
435 $('<a/>')
428 .addClass('anchor-link')
436 .addClass('anchor-link')
429 .attr('href', '#' + hash)
437 .attr('href', '#' + hash)
430 .text('ΒΆ')
438 .text('ΒΆ')
431 );
439 );
432 this.set_rendered(h);
440 this.set_rendered(h);
433 this.element.find('div.input_area').hide();
441 this.element.find('div.input_area').hide();
434 this.element.find("div.text_cell_render").show();
442 this.element.find("div.text_cell_render").show();
435 this.typeset();
443 this.typeset();
436 }
444 }
437 return cont;
445 return cont;
438 };
446 };
439
447
440 // Backwards compatability.
448 // Backwards compatability.
441 IPython.TextCell = TextCell;
449 IPython.TextCell = TextCell;
442 IPython.MarkdownCell = MarkdownCell;
450 IPython.MarkdownCell = MarkdownCell;
443 IPython.RawCell = RawCell;
451 IPython.RawCell = RawCell;
444 IPython.HeadingCell = HeadingCell;
452 IPython.HeadingCell = HeadingCell;
445
453
446 var Cells = {
454 var Cells = {
447 'TextCell': TextCell,
455 'TextCell': TextCell,
448 'MarkdownCell': MarkdownCell,
456 'MarkdownCell': MarkdownCell,
449 'RawCell': RawCell,
457 'RawCell': RawCell,
450 'HeadingCell': HeadingCell,
458 'HeadingCell': HeadingCell,
451 };
459 };
452 return Cells;
460 return Cells;
453 });
461 });
@@ -1,172 +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 ], function(IPython, $) {
7 'components/bootstrap-tour/build/js/bootstrap-tour.min',
8 ], function(IPython, $, Tour) {
8 "use strict";
9 "use strict";
9
10
10 var tour_style = "<div class='popover tour'>\n" +
11 var tour_style = "<div class='popover tour'>\n" +
11 "<div class='arrow'></div>\n" +
12 "<div class='arrow'></div>\n" +
12 "<div style='position:absolute; top:7px; right:7px'>\n" +
13 "<div style='position:absolute; top:7px; right:7px'>\n" +
13 "<button class='btn btn-default btn-sm icon-remove' data-role='end'></button>\n" +
14 "<button class='btn btn-default btn-sm icon-remove' data-role='end'></button>\n" +
14 "</div><h3 class='popover-title'></h3>\n" +
15 "</div><h3 class='popover-title'></h3>\n" +
15 "<div class='popover-content'></div>\n" +
16 "<div class='popover-content'></div>\n" +
16 "<div class='popover-navigation'>\n" +
17 "<div class='popover-navigation'>\n" +
17 "<button class='btn btn-default icon-step-backward' data-role='prev'></button>\n" +
18 "<button class='btn btn-default icon-step-backward' data-role='prev'></button>\n" +
18 "<button class='btn btn-default icon-step-forward pull-right' data-role='next'></button>\n" +
19 "<button class='btn btn-default icon-step-forward pull-right' data-role='next'></button>\n" +
19 "<button id='tour-pause' class='btn btn-sm btn-default icon-pause' data-resume-text='' data-pause-text='' data-role='pause-resume'></button>\n" +
20 "<button id='tour-pause' class='btn btn-sm btn-default icon-pause' data-resume-text='' data-pause-text='' data-role='pause-resume'></button>\n" +
20 "</div>\n" +
21 "</div>\n" +
21 "</div>";
22 "</div>";
22
23
23 var NotebookTour = function (notebook, events) {
24 var NotebookTour = function (notebook, events) {
24 var that = this;
25 var that = this;
25 this.notebook = notebook;
26 this.notebook = notebook;
26 this.step_duration = 0;
27 this.step_duration = 0;
27 this.events = events;
28 this.events = events;
28 this.tour_steps = [
29 this.tour_steps = [
29 {
30 {
30 title: "Welcome to the Notebook Tour",
31 title: "Welcome to the Notebook Tour",
31 placement: 'bottom',
32 placement: 'bottom',
32 orphan: true,
33 orphan: true,
33 content: "You can use the left and right arrow keys to go backwards and forwards.",
34 content: "You can use the left and right arrow keys to go backwards and forwards.",
34 }, {
35 }, {
35 element: "#notebook_name",
36 element: "#notebook_name",
36 title: "Filename",
37 title: "Filename",
37 placement: 'bottom',
38 placement: 'bottom',
38 content: "Click here to change the filename for this notebook."
39 content: "Click here to change the filename for this notebook."
39 }, {
40 }, {
40 element: $("#menus").parent(),
41 element: $("#menus").parent(),
41 placement: 'bottom',
42 placement: 'bottom',
42 backdrop: true,
43 backdrop: true,
43 title: "Notebook Menubar",
44 title: "Notebook Menubar",
44 content: "The menubar has menus for actions on the notebook, its cells, and the kernel it communicates with."
45 content: "The menubar has menus for actions on the notebook, its cells, and the kernel it communicates with."
45 }, {
46 }, {
46 element: "#maintoolbar",
47 element: "#maintoolbar",
47 placement: 'bottom',
48 placement: 'bottom',
48 backdrop: true,
49 backdrop: true,
49 title: "Notebook Toolbar",
50 title: "Notebook Toolbar",
50 content: "The toolbar has buttons for the most common actions. Hover your mouse over each button for more information."
51 content: "The toolbar has buttons for the most common actions. Hover your mouse over each button for more information."
51 }, {
52 }, {
52 element: "#modal_indicator",
53 element: "#modal_indicator",
53 title: "Mode Indicator",
54 title: "Mode Indicator",
54 placement: 'bottom',
55 placement: 'bottom',
55 content: "The Notebook has two modes: Edit Mode and Command Mode. In this area, an indicator can appear to tell you which mode you are in.",
56 content: "The Notebook has two modes: Edit Mode and Command Mode. In this area, an indicator can appear to tell you which mode you are in.",
56 onShow: function(tour) { that.command_icon_hack(); }
57 onShow: function(tour) { that.command_icon_hack(); }
57 }, {
58 }, {
58 element: "#modal_indicator",
59 element: "#modal_indicator",
59 title: "Command Mode",
60 title: "Command Mode",
60 placement: 'bottom',
61 placement: 'bottom',
61 onShow: function(tour) { notebook.command_mode(); that.command_icon_hack(); },
62 onShow: function(tour) { notebook.command_mode(); that.command_icon_hack(); },
62 onNext: function(tour) { that.edit_mode(); },
63 onNext: function(tour) { that.edit_mode(); },
63 content: "Right now you are in Command Mode, and many keyboard shortcuts are available. In this mode, no icon is displayed in the indicator area."
64 content: "Right now you are in Command Mode, and many keyboard shortcuts are available. In this mode, no icon is displayed in the indicator area."
64 }, {
65 }, {
65 element: "#modal_indicator",
66 element: "#modal_indicator",
66 title: "Edit Mode",
67 title: "Edit Mode",
67 placement: 'bottom',
68 placement: 'bottom',
68 onShow: function(tour) { that.edit_mode(); },
69 onShow: function(tour) { that.edit_mode(); },
69 content: "Pressing <code>Enter</code> or clicking in the input text area of the cell switches to Edit Mode."
70 content: "Pressing <code>Enter</code> or clicking in the input text area of the cell switches to Edit Mode."
70 }, {
71 }, {
71 element: '.selected',
72 element: '.selected',
72 title: "Edit Mode",
73 title: "Edit Mode",
73 placement: 'bottom',
74 placement: 'bottom',
74 onShow: function(tour) { that.edit_mode(); },
75 onShow: function(tour) { that.edit_mode(); },
75 content: "Notice that the border around the currently active cell changed color. Typing will insert text into the currently active cell."
76 content: "Notice that the border around the currently active cell changed color. Typing will insert text into the currently active cell."
76 }, {
77 }, {
77 element: '.selected',
78 element: '.selected',
78 title: "Back to Command Mode",
79 title: "Back to Command Mode",
79 placement: 'bottom',
80 placement: 'bottom',
80 onShow: function(tour) { notebook.command_mode(); },
81 onShow: function(tour) { notebook.command_mode(); },
81 onHide: function(tour) { $('#help_menu').parent().children('a').click(); },
82 onHide: function(tour) { $('#help_menu').parent().children('a').click(); },
82 content: "Pressing <code>Esc</code> or clicking outside of the input text area takes you back to Command Mode."
83 content: "Pressing <code>Esc</code> or clicking outside of the input text area takes you back to Command Mode."
83 }, {
84 }, {
84 element: '#keyboard_shortcuts',
85 element: '#keyboard_shortcuts',
85 title: "Keyboard Shortcuts",
86 title: "Keyboard Shortcuts",
86 placement: 'bottom',
87 placement: 'bottom',
87 onHide: function(tour) { $('#help_menu').parent().children('a').click(); },
88 onHide: function(tour) { $('#help_menu').parent().children('a').click(); },
88 content: "You can click here to get a list of all of the keyboard shortcuts."
89 content: "You can click here to get a list of all of the keyboard shortcuts."
89 }, {
90 }, {
90 element: "#kernel_indicator",
91 element: "#kernel_indicator",
91 title: "Kernel Indicator",
92 title: "Kernel Indicator",
92 placement: 'bottom',
93 placement: 'bottom',
93 onShow: function(tour) { events.trigger('status_idle.Kernel');},
94 onShow: function(tour) { events.trigger('status_idle.Kernel');},
94 content: "This is the Kernel indicator. It looks like this when the Kernel is idle.",
95 content: "This is the Kernel indicator. It looks like this when the Kernel is idle.",
95 }, {
96 }, {
96 element: "#kernel_indicator",
97 element: "#kernel_indicator",
97 title: "Kernel Indicator",
98 title: "Kernel Indicator",
98 placement: 'bottom',
99 placement: 'bottom',
99 onShow: function(tour) { events.trigger('status_busy.Kernel'); },
100 onShow: function(tour) { events.trigger('status_busy.Kernel'); },
100 content: "The Kernel indicator looks like this when the Kernel is busy.",
101 content: "The Kernel indicator looks like this when the Kernel is busy.",
101 }, {
102 }, {
102 element: ".icon-stop",
103 element: ".icon-stop",
103 placement: 'bottom',
104 placement: 'bottom',
104 title: "Interrupting the Kernel",
105 title: "Interrupting the Kernel",
105 onHide: function(tour) { events.trigger('status_idle.Kernel'); },
106 onHide: function(tour) { events.trigger('status_idle.Kernel'); },
106 content: "To cancel a computation in progress, you can click here."
107 content: "To cancel a computation in progress, you can click here."
107 }, {
108 }, {
108 element: "#notification_kernel",
109 element: "#notification_kernel",
109 placement: 'bottom',
110 placement: 'bottom',
110 onShow: function(tour) { $('.icon-stop').click(); },
111 onShow: function(tour) { $('.icon-stop').click(); },
111 title: "Notification Area",
112 title: "Notification Area",
112 content: "Messages in response to user actions (Save, Interrupt, etc) appear here."
113 content: "Messages in response to user actions (Save, Interrupt, etc) appear here."
113 }, {
114 }, {
114 title: "Fin.",
115 title: "Fin.",
115 placement: 'bottom',
116 placement: 'bottom',
116 orphan: true,
117 orphan: true,
117 content: "This concludes the IPython Notebook User Interface tour.Tour. Happy hacking!",
118 content: "This concludes the IPython Notebook User Interface tour.Tour. Happy hacking!",
118 }
119 }
119 ];
120 ];
120
121
121 this.tour = new Tour({
122 this.tour = new Tour({
122 //orphan: true,
123 //orphan: true,
123 storage: false, // start tour from beginning every time
124 storage: false, // start tour from beginning every time
124 //element: $("#ipython_notebook"),
125 //element: $("#ipython_notebook"),
125 debug: true,
126 debug: true,
126 reflex: true, // click on element to continue tour
127 reflex: true, // click on element to continue tour
127 //backdrop: true, // show dark behind popover
128 //backdrop: true, // show dark behind popover
128 animation: false,
129 animation: false,
129 duration: this.step_duration,
130 duration: this.step_duration,
130 onStart: function() { console.log('tour started'); },
131 onStart: function() { console.log('tour started'); },
131 // TODO: remove the onPause/onResume logic once pi's patch has been
132 // TODO: remove the onPause/onResume logic once pi's patch has been
132 // merged upstream to make this work via data-resume-class and
133 // merged upstream to make this work via data-resume-class and
133 // data-resume-text attributes.
134 // data-resume-text attributes.
134 onPause: this.toggle_pause_play,
135 onPause: this.toggle_pause_play,
135 onResume: this.toggle_pause_play,
136 onResume: this.toggle_pause_play,
136 steps: this.tour_steps,
137 steps: this.tour_steps,
137 template: tour_style,
138 template: tour_style,
138 orphan: true
139 orphan: true
139 });
140 });
140
141
141 };
142 };
142
143
143 NotebookTour.prototype.start = function () {
144 NotebookTour.prototype.start = function () {
144 console.log("let's start the tour");
145 console.log("let's start the tour");
145 this.tour.init();
146 this.tour.init();
146 this.tour.start();
147 this.tour.start();
147 if (this.tour.ended())
148 if (this.tour.ended())
148 {
149 {
149 this.tour.restart();
150 this.tour.restart();
150 }
151 }
151 };
152 };
152
153
153 NotebookTour.prototype.command_icon_hack = function() {
154 NotebookTour.prototype.command_icon_hack = function() {
154 $('#modal_indicator').css('min-height', 20);
155 $('#modal_indicator').css('min-height', 20);
155 };
156 };
156
157
157 NotebookTour.prototype.toggle_pause_play = function () {
158 NotebookTour.prototype.toggle_pause_play = function () {
158 $('#tour-pause').toggleClass('icon-pause icon-play');
159 $('#tour-pause').toggleClass('icon-pause icon-play');
159 };
160 };
160
161
161 NotebookTour.prototype.edit_mode = function() {
162 NotebookTour.prototype.edit_mode = function() {
162 this.notebook.focus_cell();
163 this.notebook.focus_cell();
163 this.notebook.edit_mode();
164 this.notebook.edit_mode();
164 };
165 };
165
166
166 // For backwards compatability.
167 // For backwards compatability.
167 IPython.NotebookTour = NotebookTour;
168 IPython.NotebookTour = NotebookTour;
168
169
169 return {'Tour': NotebookTour};
170 return {'Tour': NotebookTour};
170
171
171 });
172 });
172
173
@@ -1,36 +1,45 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) {
11 var KernelList = function (selector, options) {
12 // Constructor
13 //
14 // Parameters:
15 // selector: string
16 // options: dictionary
17 // Dictionary of keyword arguments.
18 // session_list: SessionList instance
19 // base_url: string
20 // notebook_path: string
12 notebooklist.NotebookList.call(this, selector, $.extend({
21 notebooklist.NotebookList.call(this, selector, $.extend({
13 element_name: 'running'},
22 element_name: 'running'},
14 options));
23 options));
15 };
24 };
16
25
17 KernelList.prototype = Object.create(notebooklist.NotebookList.prototype);
26 KernelList.prototype = Object.create(notebooklist.NotebookList.prototype);
18
27
19 KernelList.prototype.sessions_loaded = function (d) {
28 KernelList.prototype.sessions_loaded = function (d) {
20 this.sessions = d;
29 this.sessions = d;
21 this.clear_list();
30 this.clear_list();
22 var item;
31 var item;
23 for (var path in d) {
32 for (var path in d) {
24 item = this.new_notebook_item(-1);
33 item = this.new_notebook_item(-1);
25 this.add_link('', path, item);
34 this.add_link('', path, item);
26 this.add_shutdown_button(item, this.sessions[path]);
35 this.add_shutdown_button(item, this.sessions[path]);
27 }
36 }
28
37
29 $('#running_list_header').toggle($.isEmptyObject(d));
38 $('#running_list_header').toggle($.isEmptyObject(d));
30 };
39 };
31
40
32 // Backwards compatability.
41 // Backwards compatability.
33 IPython.KernelList = KernelList;
42 IPython.KernelList = KernelList;
34
43
35 return {'KernelList': KernelList};
44 return {'KernelList': KernelList};
36 });
45 });
@@ -1,438 +1,448 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) {
12 var NotebookList = function (selector, options) {
13 // Constructor
14 //
15 // Parameters:
16 // selector: string
17 // options: dictionary
18 // Dictionary of keyword arguments.
19 // session_list: SessionList instance
20 // element_name: string
21 // base_url: string
22 // notebook_path: string
13 var that = this;
23 var that = this;
14 this.session_list = options.session_list;
24 this.session_list = options.session_list;
15 // allow code re-use by just changing element_name in kernellist.js
25 // allow code re-use by just changing element_name in kernellist.js
16 this.element_name = options.element_name || 'notebook';
26 this.element_name = options.element_name || 'notebook';
17 this.selector = selector;
27 this.selector = selector;
18 if (this.selector !== undefined) {
28 if (this.selector !== undefined) {
19 this.element = $(selector);
29 this.element = $(selector);
20 this.style();
30 this.style();
21 this.bind_events();
31 this.bind_events();
22 }
32 }
23 this.notebooks_list = [];
33 this.notebooks_list = [];
24 this.sessions = {};
34 this.sessions = {};
25 this.base_url = options.base_url || utils.get_body_data("baseUrl");
35 this.base_url = options.base_url || utils.get_body_data("baseUrl");
26 this.notebook_path = options.notebook_path || utils.get_body_data("notebookPath");
36 this.notebook_path = options.notebook_path || utils.get_body_data("notebookPath");
27 if (this.session_list && this.session_list.events) {
37 if (this.session_list && this.session_list.events) {
28 this.session_list.events.on('sessions_loaded.Dashboard',
38 this.session_list.events.on('sessions_loaded.Dashboard',
29 function(e, d) { that.sessions_loaded(d); });
39 function(e, d) { that.sessions_loaded(d); });
30 }
40 }
31 };
41 };
32
42
33 NotebookList.prototype.style = function () {
43 NotebookList.prototype.style = function () {
34 var prefix = '#' + this.element_name;
44 var prefix = '#' + this.element_name;
35 $(prefix + '_toolbar').addClass('list_toolbar');
45 $(prefix + '_toolbar').addClass('list_toolbar');
36 $(prefix + '_list_info').addClass('toolbar_info');
46 $(prefix + '_list_info').addClass('toolbar_info');
37 $(prefix + '_buttons').addClass('toolbar_buttons');
47 $(prefix + '_buttons').addClass('toolbar_buttons');
38 $(prefix + '_list_header').addClass('list_header');
48 $(prefix + '_list_header').addClass('list_header');
39 this.element.addClass("list_container");
49 this.element.addClass("list_container");
40 };
50 };
41
51
42
52
43 NotebookList.prototype.bind_events = function () {
53 NotebookList.prototype.bind_events = function () {
44 var that = this;
54 var that = this;
45 $('#refresh_' + this.element_name + '_list').click(function () {
55 $('#refresh_' + this.element_name + '_list').click(function () {
46 that.load_sessions();
56 that.load_sessions();
47 });
57 });
48 this.element.bind('dragover', function () {
58 this.element.bind('dragover', function () {
49 return false;
59 return false;
50 });
60 });
51 this.element.bind('drop', function(event){
61 this.element.bind('drop', function(event){
52 that.handleFilesUpload(event,'drop');
62 that.handleFilesUpload(event,'drop');
53 return false;
63 return false;
54 });
64 });
55 };
65 };
56
66
57 NotebookList.prototype.handleFilesUpload = function(event, dropOrForm) {
67 NotebookList.prototype.handleFilesUpload = function(event, dropOrForm) {
58 var that = this;
68 var that = this;
59 var files;
69 var files;
60 if(dropOrForm =='drop'){
70 if(dropOrForm =='drop'){
61 files = event.originalEvent.dataTransfer.files;
71 files = event.originalEvent.dataTransfer.files;
62 } else
72 } else
63 {
73 {
64 files = event.originalEvent.target.files;
74 files = event.originalEvent.target.files;
65 }
75 }
66 for (var i = 0; i < files.length; i++) {
76 for (var i = 0; i < files.length; i++) {
67 var f = files[i];
77 var f = files[i];
68 var reader = new FileReader();
78 var reader = new FileReader();
69 reader.readAsText(f);
79 reader.readAsText(f);
70 var name_and_ext = utils.splitext(f.name);
80 var name_and_ext = utils.splitext(f.name);
71 var file_ext = name_and_ext[1];
81 var file_ext = name_and_ext[1];
72 if (file_ext === '.ipynb') {
82 if (file_ext === '.ipynb') {
73 var item = that.new_notebook_item(0);
83 var item = that.new_notebook_item(0);
74 item.addClass('new-file');
84 item.addClass('new-file');
75 that.add_name_input(f.name, item);
85 that.add_name_input(f.name, item);
76 // Store the notebook item in the reader so we can use it later
86 // Store the notebook item in the reader so we can use it later
77 // to know which item it belongs to.
87 // to know which item it belongs to.
78 $(reader).data('item', item);
88 $(reader).data('item', item);
79 reader.onload = function (event) {
89 reader.onload = function (event) {
80 var nbitem = $(event.target).data('item');
90 var nbitem = $(event.target).data('item');
81 that.add_notebook_data(event.target.result, nbitem);
91 that.add_notebook_data(event.target.result, nbitem);
82 that.add_upload_button(nbitem);
92 that.add_upload_button(nbitem);
83 };
93 };
84 } else {
94 } else {
85 var dialog_body = 'Uploaded notebooks must be .ipynb files';
95 var dialog_body = 'Uploaded notebooks must be .ipynb files';
86 dialog.modal({
96 dialog.modal({
87 title : 'Invalid file type',
97 title : 'Invalid file type',
88 body : dialog_body,
98 body : dialog_body,
89 buttons : {'OK' : {'class' : 'btn-primary'}}
99 buttons : {'OK' : {'class' : 'btn-primary'}}
90 });
100 });
91 }
101 }
92 }
102 }
93 // Replace the file input form wth a clone of itself. This is required to
103 // 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
104 // reset the form. Otherwise, if you upload a file, delete it and try to
95 // upload it again, the changed event won't fire.
105 // upload it again, the changed event won't fire.
96 var form = $('input.fileinput');
106 var form = $('input.fileinput');
97 form.replaceWith(form.clone(true));
107 form.replaceWith(form.clone(true));
98 return false;
108 return false;
99 };
109 };
100
110
101 NotebookList.prototype.clear_list = function (remove_uploads) {
111 NotebookList.prototype.clear_list = function (remove_uploads) {
102 // Clears the navigation tree.
112 // Clears the navigation tree.
103 //
113 //
104 // Parameters
114 // Parameters
105 // remove_uploads: bool=False
115 // remove_uploads: bool=False
106 // Should upload prompts also be removed from the tree.
116 // Should upload prompts also be removed from the tree.
107 if (remove_uploads) {
117 if (remove_uploads) {
108 this.element.children('.list_item').remove();
118 this.element.children('.list_item').remove();
109 } else {
119 } else {
110 this.element.children('.list_item:not(.new-file)').remove();
120 this.element.children('.list_item:not(.new-file)').remove();
111 }
121 }
112 };
122 };
113
123
114 NotebookList.prototype.load_sessions = function(){
124 NotebookList.prototype.load_sessions = function(){
115 this.session_list.load_sessions();
125 this.session_list.load_sessions();
116 };
126 };
117
127
118
128
119 NotebookList.prototype.sessions_loaded = function(data){
129 NotebookList.prototype.sessions_loaded = function(data){
120 this.sessions = data;
130 this.sessions = data;
121 this.load_list();
131 this.load_list();
122 };
132 };
123
133
124 NotebookList.prototype.load_list = function () {
134 NotebookList.prototype.load_list = function () {
125 var that = this;
135 var that = this;
126 var settings = {
136 var settings = {
127 processData : false,
137 processData : false,
128 cache : false,
138 cache : false,
129 type : "GET",
139 type : "GET",
130 dataType : "json",
140 dataType : "json",
131 success : $.proxy(this.list_loaded, this),
141 success : $.proxy(this.list_loaded, this),
132 error : $.proxy( function(xhr, status, error){
142 error : $.proxy( function(xhr, status, error){
133 utils.log_ajax_error(xhr, status, error);
143 utils.log_ajax_error(xhr, status, error);
134 that.list_loaded([], null, null, {msg:"Error connecting to server."});
144 that.list_loaded([], null, null, {msg:"Error connecting to server."});
135 },this)
145 },this)
136 };
146 };
137
147
138 var url = utils.url_join_encode(
148 var url = utils.url_join_encode(
139 this.base_url,
149 this.base_url,
140 'api',
150 'api',
141 'notebooks',
151 'notebooks',
142 this.notebook_path
152 this.notebook_path
143 );
153 );
144 $.ajax(url, settings);
154 $.ajax(url, settings);
145 };
155 };
146
156
147
157
148 NotebookList.prototype.list_loaded = function (data, status, xhr, param) {
158 NotebookList.prototype.list_loaded = function (data, status, xhr, param) {
149 var message = 'Notebook list empty.';
159 var message = 'Notebook list empty.';
150 if (param !== undefined && param.msg) {
160 if (param !== undefined && param.msg) {
151 message = param.msg;
161 message = param.msg;
152 }
162 }
153 var item = null;
163 var item = null;
154 var len = data.length;
164 var len = data.length;
155 this.clear_list();
165 this.clear_list();
156 if (len === 0) {
166 if (len === 0) {
157 item = this.new_notebook_item(0);
167 item = this.new_notebook_item(0);
158 var span12 = item.children().first();
168 var span12 = item.children().first();
159 span12.empty();
169 span12.empty();
160 span12.append($('<div style="margin:auto;text-align:center;color:grey"/>').text(message));
170 span12.append($('<div style="margin:auto;text-align:center;color:grey"/>').text(message));
161 }
171 }
162 var path = this.notebook_path;
172 var path = this.notebook_path;
163 var offset = 0;
173 var offset = 0;
164 if (path !== '') {
174 if (path !== '') {
165 item = this.new_notebook_item(0);
175 item = this.new_notebook_item(0);
166 this.add_dir(path, '..', item);
176 this.add_dir(path, '..', item);
167 offset = 1;
177 offset = 1;
168 }
178 }
169 for (var i=0; i<len; i++) {
179 for (var i=0; i<len; i++) {
170 if (data[i].type === 'directory') {
180 if (data[i].type === 'directory') {
171 var name = data[i].name;
181 var name = data[i].name;
172 item = this.new_notebook_item(i+offset);
182 item = this.new_notebook_item(i+offset);
173 this.add_dir(path, name, item);
183 this.add_dir(path, name, item);
174 } else {
184 } else {
175 var name = data[i].name;
185 var name = data[i].name;
176 item = this.new_notebook_item(i+offset);
186 item = this.new_notebook_item(i+offset);
177 this.add_link(path, name, item);
187 this.add_link(path, name, item);
178 name = utils.url_path_join(path, name);
188 name = utils.url_path_join(path, name);
179 if(this.sessions[name] === undefined){
189 if(this.sessions[name] === undefined){
180 this.add_delete_button(item);
190 this.add_delete_button(item);
181 } else {
191 } else {
182 this.add_shutdown_button(item,this.sessions[name]);
192 this.add_shutdown_button(item,this.sessions[name]);
183 }
193 }
184 }
194 }
185 }
195 }
186 };
196 };
187
197
188
198
189 NotebookList.prototype.new_notebook_item = function (index) {
199 NotebookList.prototype.new_notebook_item = function (index) {
190 var item = $('<div/>').addClass("list_item").addClass("row");
200 var item = $('<div/>').addClass("list_item").addClass("row");
191 // item.addClass('list_item ui-widget ui-widget-content ui-helper-clearfix');
201 // item.addClass('list_item ui-widget ui-widget-content ui-helper-clearfix');
192 // item.css('border-top-style','none');
202 // item.css('border-top-style','none');
193 item.append($("<div/>").addClass("col-md-12").append(
203 item.append($("<div/>").addClass("col-md-12").append(
194 $('<i/>').addClass('item_icon')
204 $('<i/>').addClass('item_icon')
195 ).append(
205 ).append(
196 $("<a/>").addClass("item_link").append(
206 $("<a/>").addClass("item_link").append(
197 $("<span/>").addClass("item_name")
207 $("<span/>").addClass("item_name")
198 )
208 )
199 ).append(
209 ).append(
200 $('<div/>').addClass("item_buttons btn-group pull-right")
210 $('<div/>').addClass("item_buttons btn-group pull-right")
201 ));
211 ));
202
212
203 if (index === -1) {
213 if (index === -1) {
204 this.element.append(item);
214 this.element.append(item);
205 } else {
215 } else {
206 this.element.children().eq(index).after(item);
216 this.element.children().eq(index).after(item);
207 }
217 }
208 return item;
218 return item;
209 };
219 };
210
220
211
221
212 NotebookList.prototype.add_dir = function (path, name, item) {
222 NotebookList.prototype.add_dir = function (path, name, item) {
213 item.data('name', name);
223 item.data('name', name);
214 item.data('path', path);
224 item.data('path', path);
215 item.find(".item_name").text(name);
225 item.find(".item_name").text(name);
216 item.find(".item_icon").addClass('folder_icon').addClass('icon-fixed-width');
226 item.find(".item_icon").addClass('folder_icon').addClass('icon-fixed-width');
217 item.find("a.item_link")
227 item.find("a.item_link")
218 .attr('href',
228 .attr('href',
219 utils.url_join_encode(
229 utils.url_join_encode(
220 this.base_url,
230 this.base_url,
221 "tree",
231 "tree",
222 path,
232 path,
223 name
233 name
224 )
234 )
225 );
235 );
226 };
236 };
227
237
228
238
229 NotebookList.prototype.add_link = function (path, nbname, item) {
239 NotebookList.prototype.add_link = function (path, nbname, item) {
230 item.data('nbname', nbname);
240 item.data('nbname', nbname);
231 item.data('path', path);
241 item.data('path', path);
232 item.find(".item_name").text(nbname);
242 item.find(".item_name").text(nbname);
233 item.find(".item_icon").addClass('notebook_icon').addClass('icon-fixed-width');
243 item.find(".item_icon").addClass('notebook_icon').addClass('icon-fixed-width');
234 item.find("a.item_link")
244 item.find("a.item_link")
235 .attr('href',
245 .attr('href',
236 utils.url_join_encode(
246 utils.url_join_encode(
237 this.base_url,
247 this.base_url,
238 "notebooks",
248 "notebooks",
239 path,
249 path,
240 nbname
250 nbname
241 )
251 )
242 ).attr('target','_blank');
252 ).attr('target','_blank');
243 };
253 };
244
254
245
255
246 NotebookList.prototype.add_name_input = function (nbname, item) {
256 NotebookList.prototype.add_name_input = function (nbname, item) {
247 item.data('nbname', nbname);
257 item.data('nbname', nbname);
248 item.find(".item_icon").addClass('notebook_icon').addClass('icon-fixed-width');
258 item.find(".item_icon").addClass('notebook_icon').addClass('icon-fixed-width');
249 item.find(".item_name").empty().append(
259 item.find(".item_name").empty().append(
250 $('<input/>')
260 $('<input/>')
251 .addClass("nbname_input")
261 .addClass("nbname_input")
252 .attr('value', utils.splitext(nbname)[0])
262 .attr('value', utils.splitext(nbname)[0])
253 .attr('size', '30')
263 .attr('size', '30')
254 .attr('type', 'text')
264 .attr('type', 'text')
255 );
265 );
256 };
266 };
257
267
258
268
259 NotebookList.prototype.add_notebook_data = function (data, item) {
269 NotebookList.prototype.add_notebook_data = function (data, item) {
260 item.data('nbdata', data);
270 item.data('nbdata', data);
261 };
271 };
262
272
263
273
264 NotebookList.prototype.add_shutdown_button = function (item, session) {
274 NotebookList.prototype.add_shutdown_button = function (item, session) {
265 var that = this;
275 var that = this;
266 var shutdown_button = $("<button/>").text("Shutdown").addClass("btn btn-xs btn-danger").
276 var shutdown_button = $("<button/>").text("Shutdown").addClass("btn btn-xs btn-danger").
267 click(function (e) {
277 click(function (e) {
268 var settings = {
278 var settings = {
269 processData : false,
279 processData : false,
270 cache : false,
280 cache : false,
271 type : "DELETE",
281 type : "DELETE",
272 dataType : "json",
282 dataType : "json",
273 success : function () {
283 success : function () {
274 that.load_sessions();
284 that.load_sessions();
275 },
285 },
276 error : utils.log_ajax_error,
286 error : utils.log_ajax_error,
277 };
287 };
278 var url = utils.url_join_encode(
288 var url = utils.url_join_encode(
279 that.base_url,
289 that.base_url,
280 'api/sessions',
290 'api/sessions',
281 session
291 session
282 );
292 );
283 $.ajax(url, settings);
293 $.ajax(url, settings);
284 return false;
294 return false;
285 });
295 });
286 // var new_buttons = item.find('a'); // shutdown_button;
296 // var new_buttons = item.find('a'); // shutdown_button;
287 item.find(".item_buttons").text("").append(shutdown_button);
297 item.find(".item_buttons").text("").append(shutdown_button);
288 };
298 };
289
299
290 NotebookList.prototype.add_delete_button = function (item) {
300 NotebookList.prototype.add_delete_button = function (item) {
291 var new_buttons = $('<span/>').addClass("btn-group pull-right");
301 var new_buttons = $('<span/>').addClass("btn-group pull-right");
292 var notebooklist = this;
302 var notebooklist = this;
293 var delete_button = $("<button/>").text("Delete").addClass("btn btn-default btn-xs").
303 var delete_button = $("<button/>").text("Delete").addClass("btn btn-default btn-xs").
294 click(function (e) {
304 click(function (e) {
295 // $(this) is the button that was clicked.
305 // $(this) is the button that was clicked.
296 var that = $(this);
306 var that = $(this);
297 // We use the nbname and notebook_id from the parent notebook_item element's
307 // 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.
308 // data because the outer scopes values change as we iterate through the loop.
299 var parent_item = that.parents('div.list_item');
309 var parent_item = that.parents('div.list_item');
300 var nbname = parent_item.data('nbname');
310 var nbname = parent_item.data('nbname');
301 var message = 'Are you sure you want to permanently delete the notebook: ' + nbname + '?';
311 var message = 'Are you sure you want to permanently delete the notebook: ' + nbname + '?';
302 dialog.modal({
312 dialog.modal({
303 title : "Delete notebook",
313 title : "Delete notebook",
304 body : message,
314 body : message,
305 buttons : {
315 buttons : {
306 Delete : {
316 Delete : {
307 class: "btn-danger",
317 class: "btn-danger",
308 click: function() {
318 click: function() {
309 var settings = {
319 var settings = {
310 processData : false,
320 processData : false,
311 cache : false,
321 cache : false,
312 type : "DELETE",
322 type : "DELETE",
313 dataType : "json",
323 dataType : "json",
314 success : function (data, status, xhr) {
324 success : function (data, status, xhr) {
315 parent_item.remove();
325 parent_item.remove();
316 },
326 },
317 error : utils.log_ajax_error,
327 error : utils.log_ajax_error,
318 };
328 };
319 var url = utils.url_join_encode(
329 var url = utils.url_join_encode(
320 notebooklist.base_url,
330 notebooklist.base_url,
321 'api/notebooks',
331 'api/notebooks',
322 notebooklist.notebook_path,
332 notebooklist.notebook_path,
323 nbname
333 nbname
324 );
334 );
325 $.ajax(url, settings);
335 $.ajax(url, settings);
326 }
336 }
327 },
337 },
328 Cancel : {}
338 Cancel : {}
329 }
339 }
330 });
340 });
331 return false;
341 return false;
332 });
342 });
333 item.find(".item_buttons").text("").append(delete_button);
343 item.find(".item_buttons").text("").append(delete_button);
334 };
344 };
335
345
336
346
337 NotebookList.prototype.add_upload_button = function (item) {
347 NotebookList.prototype.add_upload_button = function (item) {
338 var that = this;
348 var that = this;
339 var upload_button = $('<button/>').text("Upload")
349 var upload_button = $('<button/>').text("Upload")
340 .addClass('btn btn-primary btn-xs upload_button')
350 .addClass('btn btn-primary btn-xs upload_button')
341 .click(function (e) {
351 .click(function (e) {
342 var nbname = item.find('.item_name > input').val();
352 var nbname = item.find('.item_name > input').val();
343 if (nbname.slice(nbname.length-6, nbname.length) != ".ipynb") {
353 if (nbname.slice(nbname.length-6, nbname.length) != ".ipynb") {
344 nbname = nbname + ".ipynb";
354 nbname = nbname + ".ipynb";
345 }
355 }
346 var path = that.notebook_path;
356 var path = that.notebook_path;
347 var nbdata = item.data('nbdata');
357 var nbdata = item.data('nbdata');
348 var content_type = 'application/json';
358 var content_type = 'application/json';
349 var model = {
359 var model = {
350 content : JSON.parse(nbdata),
360 content : JSON.parse(nbdata),
351 };
361 };
352 var settings = {
362 var settings = {
353 processData : false,
363 processData : false,
354 cache : false,
364 cache : false,
355 type : 'PUT',
365 type : 'PUT',
356 dataType : 'json',
366 dataType : 'json',
357 data : JSON.stringify(model),
367 data : JSON.stringify(model),
358 headers : {'Content-Type': content_type},
368 headers : {'Content-Type': content_type},
359 success : function (data, status, xhr) {
369 success : function (data, status, xhr) {
360 that.add_link(path, nbname, item);
370 that.add_link(path, nbname, item);
361 that.add_delete_button(item);
371 that.add_delete_button(item);
362 },
372 },
363 error : utils.log_ajax_error,
373 error : utils.log_ajax_error,
364 };
374 };
365
375
366 var url = utils.url_join_encode(
376 var url = utils.url_join_encode(
367 that.base_url,
377 that.base_url,
368 'api/notebooks',
378 'api/notebooks',
369 that.notebook_path,
379 that.notebook_path,
370 nbname
380 nbname
371 );
381 );
372 $.ajax(url, settings);
382 $.ajax(url, settings);
373 return false;
383 return false;
374 });
384 });
375 var cancel_button = $('<button/>').text("Cancel")
385 var cancel_button = $('<button/>').text("Cancel")
376 .addClass("btn btn-default btn-xs")
386 .addClass("btn btn-default btn-xs")
377 .click(function (e) {
387 .click(function (e) {
378 console.log('cancel click');
388 console.log('cancel click');
379 item.remove();
389 item.remove();
380 return false;
390 return false;
381 });
391 });
382 item.find(".item_buttons").empty()
392 item.find(".item_buttons").empty()
383 .append(upload_button)
393 .append(upload_button)
384 .append(cancel_button);
394 .append(cancel_button);
385 };
395 };
386
396
387
397
388 NotebookList.prototype.new_notebook = function(){
398 NotebookList.prototype.new_notebook = function(){
389 var path = this.notebook_path;
399 var path = this.notebook_path;
390 var base_url = this.base_url;
400 var base_url = this.base_url;
391 var settings = {
401 var settings = {
392 processData : false,
402 processData : false,
393 cache : false,
403 cache : false,
394 type : "POST",
404 type : "POST",
395 dataType : "json",
405 dataType : "json",
396 async : false,
406 async : false,
397 success : function (data, status, xhr) {
407 success : function (data, status, xhr) {
398 var notebook_name = data.name;
408 var notebook_name = data.name;
399 window.open(
409 window.open(
400 utils.url_join_encode(
410 utils.url_join_encode(
401 base_url,
411 base_url,
402 'notebooks',
412 'notebooks',
403 path,
413 path,
404 notebook_name),
414 notebook_name),
405 '_blank'
415 '_blank'
406 );
416 );
407 },
417 },
408 error : $.proxy(this.new_notebook_failed, this),
418 error : $.proxy(this.new_notebook_failed, this),
409 };
419 };
410 var url = utils.url_join_encode(
420 var url = utils.url_join_encode(
411 base_url,
421 base_url,
412 'api/notebooks',
422 'api/notebooks',
413 path
423 path
414 );
424 );
415 $.ajax(url, settings);
425 $.ajax(url, settings);
416 };
426 };
417
427
418
428
419 NotebookList.prototype.new_notebook_failed = function (xhr, status, error) {
429 NotebookList.prototype.new_notebook_failed = function (xhr, status, error) {
420 utils.log_ajax_error(xhr, status, error);
430 utils.log_ajax_error(xhr, status, error);
421 var msg;
431 var msg;
422 if (xhr.responseJSON && xhr.responseJSON.message) {
432 if (xhr.responseJSON && xhr.responseJSON.message) {
423 msg = xhr.responseJSON.message;
433 msg = xhr.responseJSON.message;
424 } else {
434 } else {
425 msg = xhr.statusText;
435 msg = xhr.statusText;
426 }
436 }
427 dialog.modal({
437 dialog.modal({
428 title : 'Creating Notebook Failed',
438 title : 'Creating Notebook Failed',
429 body : "The error was: " + msg,
439 body : "The error was: " + msg,
430 buttons : {'OK' : {'class' : 'btn-primary'}}
440 buttons : {'OK' : {'class' : 'btn-primary'}}
431 });
441 });
432 };
442 };
433
443
434 // Backwards compatability.
444 // Backwards compatability.
435 IPython.NotebookList = NotebookList;
445 IPython.NotebookList = NotebookList;
436
446
437 return {'NotebookList': NotebookList};
447 return {'NotebookList': NotebookList};
438 });
448 });
@@ -1,49 +1,56 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) {
12 // Constructor
13 //
14 // Parameters:
15 // options: dictionary
16 // Dictionary of keyword arguments.
17 // events: $(Events) instance
18 // base_url : string
12 this.events = options.events;
19 this.events = options.events;
13 this.sessions = {};
20 this.sessions = {};
14 this.base_url = options.base_url || utils.get_body_data("baseUrl");
21 this.base_url = options.base_url || utils.get_body_data("baseUrl");
15 };
22 };
16
23
17 SesssionList.prototype.load_sessions = function(){
24 SesssionList.prototype.load_sessions = function(){
18 var that = this;
25 var that = this;
19 var settings = {
26 var settings = {
20 processData : false,
27 processData : false,
21 cache : false,
28 cache : false,
22 type : "GET",
29 type : "GET",
23 dataType : "json",
30 dataType : "json",
24 success : $.proxy(that.sessions_loaded, this),
31 success : $.proxy(that.sessions_loaded, this),
25 error : utils.log_ajax_error,
32 error : utils.log_ajax_error,
26 };
33 };
27 var url = utils.url_join_encode(this.base_url, 'api/sessions');
34 var url = utils.url_join_encode(this.base_url, 'api/sessions');
28 $.ajax(url, settings);
35 $.ajax(url, settings);
29 };
36 };
30
37
31 SesssionList.prototype.sessions_loaded = function(data){
38 SesssionList.prototype.sessions_loaded = function(data){
32 this.sessions = {};
39 this.sessions = {};
33 var len = data.length;
40 var len = data.length;
34 var nb_path;
41 var nb_path;
35 for (var i=0; i<len; i++) {
42 for (var i=0; i<len; i++) {
36 nb_path = utils.url_path_join(
43 nb_path = utils.url_path_join(
37 data[i].notebook.path,
44 data[i].notebook.path,
38 data[i].notebook.name
45 data[i].notebook.name
39 );
46 );
40 this.sessions[nb_path] = data[i].id;
47 this.sessions[nb_path] = data[i].id;
41 }
48 }
42 this.events.trigger('sessions_loaded.Dashboard', this.sessions);
49 this.events.trigger('sessions_loaded.Dashboard', this.sessions);
43 };
50 };
44
51
45 // Backwards compatability.
52 // Backwards compatability.
46 IPython.SesssionList = SesssionList;
53 IPython.SesssionList = SesssionList;
47
54
48 return {'SesssionList': SesssionList};
55 return {'SesssionList': SesssionList};
49 });
56 });
General Comments 0
You need to be logged in to leave comments. Login now