##// END OF EJS Templates
Removes unnecessary parameter from ContentManager.delete_notebook
KesterTong -
Show More
@@ -1,197 +1,197 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 var ContentManager = function(options) {
10 var ContentManager = function(options) {
11 // Constructor
11 // Constructor
12 //
12 //
13 // A contentmanager handles passing file operations
13 // A contentmanager handles passing file operations
14 // to the back-end. This includes checkpointing
14 // to the back-end. This includes checkpointing
15 // with the normal file operations.
15 // with the normal file operations.
16 //
16 //
17 // Parameters:
17 // Parameters:
18 // options: dictionary
18 // options: dictionary
19 // Dictionary of keyword arguments.
19 // Dictionary of keyword arguments.
20 // events: $(Events) instance
20 // events: $(Events) instance
21 // base_url: string
21 // base_url: string
22 this.version = 0.1;
22 this.version = 0.1;
23 this.events = options.events;
23 this.events = options.events;
24 this.base_url = options.base_url;
24 this.base_url = options.base_url;
25 };
25 };
26
26
27 /**
27 /**
28 * Creates a new notebook file at the specified path, and
28 * Creates a new notebook file at the specified path, and
29 * opens that notebook in a new window.
29 * opens that notebook in a new window.
30 *
30 *
31 * @method scroll_to_cell
31 * @method scroll_to_cell
32 * @param {String} path The path to create the new notebook at
32 * @param {String} path The path to create the new notebook at
33 */
33 */
34 ContentManager.prototype.new_notebook = function(path) {
34 ContentManager.prototype.new_notebook = function(path) {
35 var base_url = this.base_url;
35 var base_url = this.base_url;
36 var settings = {
36 var settings = {
37 processData : false,
37 processData : false,
38 cache : false,
38 cache : false,
39 type : "POST",
39 type : "POST",
40 dataType : "json",
40 dataType : "json",
41 async : false,
41 async : false,
42 success : function (data, status, xhr){
42 success : function (data, status, xhr){
43 var notebook_name = data.name;
43 var notebook_name = data.name;
44 window.open(
44 window.open(
45 utils.url_join_encode(
45 utils.url_join_encode(
46 base_url,
46 base_url,
47 'notebooks',
47 'notebooks',
48 path,
48 path,
49 notebook_name
49 notebook_name
50 ),
50 ),
51 '_blank'
51 '_blank'
52 );
52 );
53 },
53 },
54 error : function(xhr, status, error) {
54 error : function(xhr, status, error) {
55 utils.log_ajax_error(xhr, status, error);
55 utils.log_ajax_error(xhr, status, error);
56 var msg;
56 var msg;
57 if (xhr.responseJSON && xhr.responseJSON.message) {
57 if (xhr.responseJSON && xhr.responseJSON.message) {
58 msg = xhr.responseJSON.message;
58 msg = xhr.responseJSON.message;
59 } else {
59 } else {
60 msg = xhr.statusText;
60 msg = xhr.statusText;
61 }
61 }
62 dialog.modal({
62 dialog.modal({
63 title : 'Creating Notebook Failed',
63 title : 'Creating Notebook Failed',
64 body : "The error was: " + msg,
64 body : "The error was: " + msg,
65 buttons : {'OK' : {'class' : 'btn-primary'}}
65 buttons : {'OK' : {'class' : 'btn-primary'}}
66 });
66 });
67 }
67 }
68 };
68 };
69 var url = utils.url_join_encode(
69 var url = utils.url_join_encode(
70 base_url,
70 base_url,
71 'api/notebooks',
71 'api/notebooks',
72 path
72 path
73 );
73 );
74 $.ajax(url,settings);
74 $.ajax(url,settings);
75 };
75 };
76
76
77 ContentManager.prototype.delete_notebook = function(name, path, base_url) {
77 ContentManager.prototype.delete_notebook = function(name, path) {
78 var settings = {
78 var settings = {
79 processData : false,
79 processData : false,
80 cache : false,
80 cache : false,
81 type : "DELETE",
81 type : "DELETE",
82 dataType: "json",
82 dataType : "json",
83 error : utils.log_ajax_error,
83 error : utils.log_ajax_error,
84 };
84 };
85 var url = utils.url_join_encode(
85 var url = utils.url_join_encode(
86 base_url,
86 this.base_url,
87 'api/notebooks',
87 'api/notebooks',
88 path,
88 path,
89 name
89 name
90 );
90 );
91 $.ajax(url, settings);
91 $.ajax(url, settings);
92 };
92 };
93
93
94 ContentManager.prototype.rename_notebook = function(notebook, nbname) {
94 ContentManager.prototype.rename_notebook = function(notebook, nbname) {
95 var that = notebook;
95 var that = notebook;
96 if (!nbname.match(/\.ipynb$/)) {
96 if (!nbname.match(/\.ipynb$/)) {
97 nbname = nbname + ".ipynb";
97 nbname = nbname + ".ipynb";
98 }
98 }
99 var data = {name: nbname};
99 var data = {name: nbname};
100 var settings = {
100 var settings = {
101 processData : false,
101 processData : false,
102 cache : false,
102 cache : false,
103 type : "PATCH",
103 type : "PATCH",
104 data : JSON.stringify(data),
104 data : JSON.stringify(data),
105 dataType: "json",
105 dataType: "json",
106 contentType: 'application/json',
106 contentType: 'application/json',
107 success : $.proxy(that.rename_success, that),
107 success : $.proxy(that.rename_success, that),
108 error : $.proxy(that.rename_error, that)
108 error : $.proxy(that.rename_error, that)
109 };
109 };
110 this.events.trigger('rename_notebook.Notebook', data);
110 this.events.trigger('rename_notebook.Notebook', data);
111 var url = utils.url_join_encode(
111 var url = utils.url_join_encode(
112 that.base_url,
112 that.base_url,
113 'api/notebooks',
113 'api/notebooks',
114 that.notebook_path,
114 that.notebook_path,
115 that.notebook_name
115 that.notebook_name
116 );
116 );
117 $.ajax(url, settings);
117 $.ajax(url, settings);
118 };
118 };
119
119
120 ContentManager.prototype.save_notebook = function(notebook, extra_settings) {
120 ContentManager.prototype.save_notebook = function(notebook, extra_settings) {
121 var that = notebook;
121 var that = notebook;
122 // Create a JSON model to be sent to the server.
122 // Create a JSON model to be sent to the server.
123 var model = {};
123 var model = {};
124 model.name = notebook.notebook_name;
124 model.name = notebook.notebook_name;
125 model.path = notebook.notebook_path;
125 model.path = notebook.notebook_path;
126 model.content = notebook.toJSON();
126 model.content = notebook.toJSON();
127 model.content.nbformat = notebook.nbformat;
127 model.content.nbformat = notebook.nbformat;
128 model.content.nbformat_minor = notebook.nbformat_minor;
128 model.content.nbformat_minor = notebook.nbformat_minor;
129 // time the ajax call for autosave tuning purposes.
129 // time the ajax call for autosave tuning purposes.
130 var start = new Date().getTime();
130 var start = new Date().getTime();
131 // We do the call with settings so we can set cache to false.
131 // We do the call with settings so we can set cache to false.
132 var settings = {
132 var settings = {
133 processData : false,
133 processData : false,
134 cache : false,
134 cache : false,
135 type : "PUT",
135 type : "PUT",
136 data : JSON.stringify(model),
136 data : JSON.stringify(model),
137 contentType: 'application/json',
137 contentType: 'application/json',
138 success : $.proxy(notebook.save_notebook_success, that, start),
138 success : $.proxy(notebook.save_notebook_success, that, start),
139 error : $.proxy(notebook.save_notebook_error, that)
139 error : $.proxy(notebook.save_notebook_error, that)
140 };
140 };
141 if (extra_settings) {
141 if (extra_settings) {
142 for (var key in extra_settings) {
142 for (var key in extra_settings) {
143 settings[key] = extra_settings[key];
143 settings[key] = extra_settings[key];
144 }
144 }
145 }
145 }
146 notebook.events.trigger('notebook_saving.Notebook');
146 notebook.events.trigger('notebook_saving.Notebook');
147 var url = utils.url_join_encode(
147 var url = utils.url_join_encode(
148 notebook.base_url,
148 notebook.base_url,
149 'api/notebooks',
149 'api/notebooks',
150 notebook.notebook_path,
150 notebook.notebook_path,
151 notebook.notebook_name
151 notebook.notebook_name
152 );
152 );
153 $.ajax(url, settings);
153 $.ajax(url, settings);
154 };
154 };
155
155
156 ContentManager.prototype.save_checkpoint = function() {
156 ContentManager.prototype.save_checkpoint = function() {
157 // This is not necessary - integrated into save
157 // This is not necessary - integrated into save
158 };
158 };
159
159
160 ContentManager.prototype.restore_checkpoint = function(notebook, id) {
160 ContentManager.prototype.restore_checkpoint = function(notebook, id) {
161 that = notebook;
161 that = notebook;
162 this.events.trigger('notebook_restoring.Notebook', checkpoint);
162 this.events.trigger('notebook_restoring.Notebook', checkpoint);
163 var url = utils.url_join_encode(
163 var url = utils.url_join_encode(
164 this.base_url,
164 this.base_url,
165 'api/notebooks',
165 'api/notebooks',
166 this.notebook_path,
166 this.notebook_path,
167 this.notebook_name,
167 this.notebook_name,
168 'checkpoints',
168 'checkpoints',
169 checkpoint
169 checkpoint
170 );
170 );
171 $.post(url).done(
171 $.post(url).done(
172 $.proxy(that.restore_checkpoint_success, that)
172 $.proxy(that.restore_checkpoint_success, that)
173 ).fail(
173 ).fail(
174 $.proxy(that.restore_checkpoint_error, that)
174 $.proxy(that.restore_checkpoint_error, that)
175 );
175 );
176 };
176 };
177
177
178 ContentManager.prototype.list_checkpoints = function(notebook) {
178 ContentManager.prototype.list_checkpoints = function(notebook) {
179 that = notebook;
179 that = notebook;
180 var url = utils.url_join_encode(
180 var url = utils.url_join_encode(
181 that.base_url,
181 that.base_url,
182 'api/notebooks',
182 'api/notebooks',
183 that.notebook_path,
183 that.notebook_path,
184 that.notebook_name,
184 that.notebook_name,
185 'checkpoints'
185 'checkpoints'
186 );
186 );
187 $.get(url).done(
187 $.get(url).done(
188 $.proxy(that.list_checkpoints_success, that)
188 $.proxy(that.list_checkpoints_success, that)
189 ).fail(
189 ).fail(
190 $.proxy(that.list_checkpoints_error, that)
190 $.proxy(that.list_checkpoints_error, that)
191 );
191 );
192 };
192 };
193
193
194 IPython.ContentManager = ContentManager;
194 IPython.ContentManager = ContentManager;
195
195
196 return {'ContentManager': ContentManager};
196 return {'ContentManager': ContentManager};
197 });
197 });
@@ -1,2580 +1,2580 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/session',
11 'services/sessions/session',
12 'notebook/js/celltoolbar',
12 'notebook/js/celltoolbar',
13 'components/marked/lib/marked',
13 'components/marked/lib/marked',
14 'highlight',
14 'highlight',
15 'notebook/js/mathjaxutils',
15 'notebook/js/mathjaxutils',
16 'base/js/keyboard',
16 'base/js/keyboard',
17 'notebook/js/tooltip',
17 'notebook/js/tooltip',
18 'notebook/js/celltoolbarpresets/default',
18 'notebook/js/celltoolbarpresets/default',
19 'notebook/js/celltoolbarpresets/rawcell',
19 'notebook/js/celltoolbarpresets/rawcell',
20 'notebook/js/celltoolbarpresets/slideshow',
20 'notebook/js/celltoolbarpresets/slideshow',
21 'notebook/js/scrollmanager'
21 'notebook/js/scrollmanager'
22 ], function (
22 ], function (
23 IPython,
23 IPython,
24 $,
24 $,
25 utils,
25 utils,
26 dialog,
26 dialog,
27 textcell,
27 textcell,
28 codecell,
28 codecell,
29 session,
29 session,
30 celltoolbar,
30 celltoolbar,
31 marked,
31 marked,
32 hljs,
32 hljs,
33 mathjaxutils,
33 mathjaxutils,
34 keyboard,
34 keyboard,
35 tooltip,
35 tooltip,
36 default_celltoolbar,
36 default_celltoolbar,
37 rawcell_celltoolbar,
37 rawcell_celltoolbar,
38 slideshow_celltoolbar,
38 slideshow_celltoolbar,
39 scrollmanager
39 scrollmanager
40 ) {
40 ) {
41
41
42 var Notebook = function (selector, options) {
42 var Notebook = function (selector, options) {
43 // Constructor
43 // Constructor
44 //
44 //
45 // A notebook contains and manages cells.
45 // A notebook contains and manages cells.
46 //
46 //
47 // Parameters:
47 // Parameters:
48 // selector: string
48 // selector: string
49 // options: dictionary
49 // options: dictionary
50 // Dictionary of keyword arguments.
50 // Dictionary of keyword arguments.
51 // events: $(Events) instance
51 // events: $(Events) instance
52 // keyboard_manager: KeyboardManager instance
52 // keyboard_manager: KeyboardManager instance
53 // save_widget: SaveWidget instance
53 // save_widget: SaveWidget instance
54 // config: dictionary
54 // config: dictionary
55 // base_url : string
55 // base_url : string
56 // notebook_path : string
56 // notebook_path : string
57 // notebook_name : string
57 // notebook_name : string
58 this.config = utils.mergeopt(Notebook, options.config);
58 this.config = utils.mergeopt(Notebook, options.config);
59 this.base_url = options.base_url;
59 this.base_url = options.base_url;
60 this.notebook_path = options.notebook_path;
60 this.notebook_path = options.notebook_path;
61 this.notebook_name = options.notebook_name;
61 this.notebook_name = options.notebook_name;
62 this.events = options.events;
62 this.events = options.events;
63 this.keyboard_manager = options.keyboard_manager;
63 this.keyboard_manager = options.keyboard_manager;
64 this.content_manager = options.content_manager;
64 this.content_manager = options.content_manager;
65 this.save_widget = options.save_widget;
65 this.save_widget = options.save_widget;
66 this.tooltip = new tooltip.Tooltip(this.events);
66 this.tooltip = new tooltip.Tooltip(this.events);
67 this.ws_url = options.ws_url;
67 this.ws_url = options.ws_url;
68 this._session_starting = false;
68 this._session_starting = false;
69 this.default_cell_type = this.config.default_cell_type || 'code';
69 this.default_cell_type = this.config.default_cell_type || 'code';
70
70
71 // Create default scroll manager.
71 // Create default scroll manager.
72 this.scroll_manager = new scrollmanager.ScrollManager(this);
72 this.scroll_manager = new scrollmanager.ScrollManager(this);
73
73
74 // TODO: This code smells (and the other `= this` line a couple lines down)
74 // TODO: This code smells (and the other `= this` line a couple lines down)
75 // We need a better way to deal with circular instance references.
75 // We need a better way to deal with circular instance references.
76 this.keyboard_manager.notebook = this;
76 this.keyboard_manager.notebook = this;
77 this.save_widget.notebook = this;
77 this.save_widget.notebook = this;
78
78
79 mathjaxutils.init();
79 mathjaxutils.init();
80
80
81 if (marked) {
81 if (marked) {
82 marked.setOptions({
82 marked.setOptions({
83 gfm : true,
83 gfm : true,
84 tables: true,
84 tables: true,
85 langPrefix: "language-",
85 langPrefix: "language-",
86 highlight: function(code, lang) {
86 highlight: function(code, lang) {
87 if (!lang) {
87 if (!lang) {
88 // no language, no highlight
88 // no language, no highlight
89 return code;
89 return code;
90 }
90 }
91 var highlighted;
91 var highlighted;
92 try {
92 try {
93 highlighted = hljs.highlight(lang, code, false);
93 highlighted = hljs.highlight(lang, code, false);
94 } catch(err) {
94 } catch(err) {
95 highlighted = hljs.highlightAuto(code);
95 highlighted = hljs.highlightAuto(code);
96 }
96 }
97 return highlighted.value;
97 return highlighted.value;
98 }
98 }
99 });
99 });
100 }
100 }
101
101
102 this.element = $(selector);
102 this.element = $(selector);
103 this.element.scroll();
103 this.element.scroll();
104 this.element.data("notebook", this);
104 this.element.data("notebook", this);
105 this.next_prompt_number = 1;
105 this.next_prompt_number = 1;
106 this.session = null;
106 this.session = null;
107 this.kernel = null;
107 this.kernel = null;
108 this.clipboard = null;
108 this.clipboard = null;
109 this.undelete_backup = null;
109 this.undelete_backup = null;
110 this.undelete_index = null;
110 this.undelete_index = null;
111 this.undelete_below = false;
111 this.undelete_below = false;
112 this.paste_enabled = false;
112 this.paste_enabled = false;
113 // It is important to start out in command mode to match the intial mode
113 // It is important to start out in command mode to match the intial mode
114 // of the KeyboardManager.
114 // of the KeyboardManager.
115 this.mode = 'command';
115 this.mode = 'command';
116 this.set_dirty(false);
116 this.set_dirty(false);
117 this.metadata = {};
117 this.metadata = {};
118 this._checkpoint_after_save = false;
118 this._checkpoint_after_save = false;
119 this.last_checkpoint = null;
119 this.last_checkpoint = null;
120 this.checkpoints = [];
120 this.checkpoints = [];
121 this.autosave_interval = 0;
121 this.autosave_interval = 0;
122 this.autosave_timer = null;
122 this.autosave_timer = null;
123 // autosave *at most* every two minutes
123 // autosave *at most* every two minutes
124 this.minimum_autosave_interval = 120000;
124 this.minimum_autosave_interval = 120000;
125 this.notebook_name_blacklist_re = /[\/\\:]/;
125 this.notebook_name_blacklist_re = /[\/\\:]/;
126 this.nbformat = 4; // Increment this when changing the nbformat
126 this.nbformat = 4; // Increment this when changing the nbformat
127 this.nbformat_minor = 0; // Increment this when changing the nbformat
127 this.nbformat_minor = 0; // Increment this when changing the nbformat
128 this.codemirror_mode = 'ipython';
128 this.codemirror_mode = 'ipython';
129 this.create_elements();
129 this.create_elements();
130 this.bind_events();
130 this.bind_events();
131 this.save_notebook = function() { // don't allow save until notebook_loaded
131 this.save_notebook = function() { // don't allow save until notebook_loaded
132 this.save_notebook_error(null, null, "Load failed, save is disabled");
132 this.save_notebook_error(null, null, "Load failed, save is disabled");
133 };
133 };
134
134
135 // Trigger cell toolbar registration.
135 // Trigger cell toolbar registration.
136 default_celltoolbar.register(this);
136 default_celltoolbar.register(this);
137 rawcell_celltoolbar.register(this);
137 rawcell_celltoolbar.register(this);
138 slideshow_celltoolbar.register(this);
138 slideshow_celltoolbar.register(this);
139 };
139 };
140
140
141 Notebook.options_default = {
141 Notebook.options_default = {
142 // can be any cell type, or the special values of
142 // can be any cell type, or the special values of
143 // 'above', 'below', or 'selected' to get the value from another cell.
143 // 'above', 'below', or 'selected' to get the value from another cell.
144 Notebook: {
144 Notebook: {
145 default_cell_type: 'code',
145 default_cell_type: 'code',
146 }
146 }
147 };
147 };
148
148
149
149
150 /**
150 /**
151 * Create an HTML and CSS representation of the notebook.
151 * Create an HTML and CSS representation of the notebook.
152 *
152 *
153 * @method create_elements
153 * @method create_elements
154 */
154 */
155 Notebook.prototype.create_elements = function () {
155 Notebook.prototype.create_elements = function () {
156 var that = this;
156 var that = this;
157 this.element.attr('tabindex','-1');
157 this.element.attr('tabindex','-1');
158 this.container = $("<div/>").addClass("container").attr("id", "notebook-container");
158 this.container = $("<div/>").addClass("container").attr("id", "notebook-container");
159 // We add this end_space div to the end of the notebook div to:
159 // We add this end_space div to the end of the notebook div to:
160 // i) provide a margin between the last cell and the end of the notebook
160 // i) provide a margin between the last cell and the end of the notebook
161 // ii) to prevent the div from scrolling up when the last cell is being
161 // ii) to prevent the div from scrolling up when the last cell is being
162 // edited, but is too low on the page, which browsers will do automatically.
162 // edited, but is too low on the page, which browsers will do automatically.
163 var end_space = $('<div/>').addClass('end_space');
163 var end_space = $('<div/>').addClass('end_space');
164 end_space.dblclick(function (e) {
164 end_space.dblclick(function (e) {
165 var ncells = that.ncells();
165 var ncells = that.ncells();
166 that.insert_cell_below('code',ncells-1);
166 that.insert_cell_below('code',ncells-1);
167 });
167 });
168 this.element.append(this.container);
168 this.element.append(this.container);
169 this.container.append(end_space);
169 this.container.append(end_space);
170 };
170 };
171
171
172 /**
172 /**
173 * Bind JavaScript events: key presses and custom IPython events.
173 * Bind JavaScript events: key presses and custom IPython events.
174 *
174 *
175 * @method bind_events
175 * @method bind_events
176 */
176 */
177 Notebook.prototype.bind_events = function () {
177 Notebook.prototype.bind_events = function () {
178 var that = this;
178 var that = this;
179
179
180 this.events.on('set_next_input.Notebook', function (event, data) {
180 this.events.on('set_next_input.Notebook', function (event, data) {
181 var index = that.find_cell_index(data.cell);
181 var index = that.find_cell_index(data.cell);
182 var new_cell = that.insert_cell_below('code',index);
182 var new_cell = that.insert_cell_below('code',index);
183 new_cell.set_text(data.text);
183 new_cell.set_text(data.text);
184 that.dirty = true;
184 that.dirty = true;
185 });
185 });
186
186
187 this.events.on('set_dirty.Notebook', function (event, data) {
187 this.events.on('set_dirty.Notebook', function (event, data) {
188 that.dirty = data.value;
188 that.dirty = data.value;
189 });
189 });
190
190
191 this.events.on('trust_changed.Notebook', function (event, trusted) {
191 this.events.on('trust_changed.Notebook', function (event, trusted) {
192 that.trusted = trusted;
192 that.trusted = trusted;
193 });
193 });
194
194
195 this.events.on('select.Cell', function (event, data) {
195 this.events.on('select.Cell', function (event, data) {
196 var index = that.find_cell_index(data.cell);
196 var index = that.find_cell_index(data.cell);
197 that.select(index);
197 that.select(index);
198 });
198 });
199
199
200 this.events.on('edit_mode.Cell', function (event, data) {
200 this.events.on('edit_mode.Cell', function (event, data) {
201 that.handle_edit_mode(data.cell);
201 that.handle_edit_mode(data.cell);
202 });
202 });
203
203
204 this.events.on('command_mode.Cell', function (event, data) {
204 this.events.on('command_mode.Cell', function (event, data) {
205 that.handle_command_mode(data.cell);
205 that.handle_command_mode(data.cell);
206 });
206 });
207
207
208 this.events.on('spec_changed.Kernel', function(event, data) {
208 this.events.on('spec_changed.Kernel', function(event, data) {
209 that.metadata.kernelspec =
209 that.metadata.kernelspec =
210 {name: data.name, display_name: data.display_name};
210 {name: data.name, display_name: data.display_name};
211 });
211 });
212
212
213 this.events.on('kernel_ready.Kernel', function(event, data) {
213 this.events.on('kernel_ready.Kernel', function(event, data) {
214 var kinfo = data.kernel.info_reply
214 var kinfo = data.kernel.info_reply
215 var langinfo = kinfo.language_info || {};
215 var langinfo = kinfo.language_info || {};
216 if (!langinfo.name) langinfo.name = kinfo.language;
216 if (!langinfo.name) langinfo.name = kinfo.language;
217
217
218 that.metadata.language_info = langinfo;
218 that.metadata.language_info = langinfo;
219 // Mode 'null' should be plain, unhighlighted text.
219 // Mode 'null' should be plain, unhighlighted text.
220 var cm_mode = langinfo.codemirror_mode || langinfo.language || 'null'
220 var cm_mode = langinfo.codemirror_mode || langinfo.language || 'null'
221 that.set_codemirror_mode(cm_mode);
221 that.set_codemirror_mode(cm_mode);
222 });
222 });
223
223
224 var collapse_time = function (time) {
224 var collapse_time = function (time) {
225 var app_height = $('#ipython-main-app').height(); // content height
225 var app_height = $('#ipython-main-app').height(); // content height
226 var splitter_height = $('div#pager_splitter').outerHeight(true);
226 var splitter_height = $('div#pager_splitter').outerHeight(true);
227 var new_height = app_height - splitter_height;
227 var new_height = app_height - splitter_height;
228 that.element.animate({height : new_height + 'px'}, time);
228 that.element.animate({height : new_height + 'px'}, time);
229 };
229 };
230
230
231 this.element.bind('collapse_pager', function (event, extrap) {
231 this.element.bind('collapse_pager', function (event, extrap) {
232 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
232 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
233 collapse_time(time);
233 collapse_time(time);
234 });
234 });
235
235
236 var expand_time = function (time) {
236 var expand_time = function (time) {
237 var app_height = $('#ipython-main-app').height(); // content height
237 var app_height = $('#ipython-main-app').height(); // content height
238 var splitter_height = $('div#pager_splitter').outerHeight(true);
238 var splitter_height = $('div#pager_splitter').outerHeight(true);
239 var pager_height = $('div#pager').outerHeight(true);
239 var pager_height = $('div#pager').outerHeight(true);
240 var new_height = app_height - pager_height - splitter_height;
240 var new_height = app_height - pager_height - splitter_height;
241 that.element.animate({height : new_height + 'px'}, time);
241 that.element.animate({height : new_height + 'px'}, time);
242 };
242 };
243
243
244 this.element.bind('expand_pager', function (event, extrap) {
244 this.element.bind('expand_pager', function (event, extrap) {
245 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
245 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
246 expand_time(time);
246 expand_time(time);
247 });
247 });
248
248
249 // Firefox 22 broke $(window).on("beforeunload")
249 // Firefox 22 broke $(window).on("beforeunload")
250 // I'm not sure why or how.
250 // I'm not sure why or how.
251 window.onbeforeunload = function (e) {
251 window.onbeforeunload = function (e) {
252 // TODO: Make killing the kernel configurable.
252 // TODO: Make killing the kernel configurable.
253 var kill_kernel = false;
253 var kill_kernel = false;
254 if (kill_kernel) {
254 if (kill_kernel) {
255 that.session.delete();
255 that.session.delete();
256 }
256 }
257 // if we are autosaving, trigger an autosave on nav-away.
257 // if we are autosaving, trigger an autosave on nav-away.
258 // still warn, because if we don't the autosave may fail.
258 // still warn, because if we don't the autosave may fail.
259 if (that.dirty) {
259 if (that.dirty) {
260 if ( that.autosave_interval ) {
260 if ( that.autosave_interval ) {
261 // schedule autosave in a timeout
261 // schedule autosave in a timeout
262 // this gives you a chance to forcefully discard changes
262 // this gives you a chance to forcefully discard changes
263 // by reloading the page if you *really* want to.
263 // by reloading the page if you *really* want to.
264 // the timer doesn't start until you *dismiss* the dialog.
264 // the timer doesn't start until you *dismiss* the dialog.
265 setTimeout(function () {
265 setTimeout(function () {
266 if (that.dirty) {
266 if (that.dirty) {
267 that.save_notebook();
267 that.save_notebook();
268 }
268 }
269 }, 1000);
269 }, 1000);
270 return "Autosave in progress, latest changes may be lost.";
270 return "Autosave in progress, latest changes may be lost.";
271 } else {
271 } else {
272 return "Unsaved changes will be lost.";
272 return "Unsaved changes will be lost.";
273 }
273 }
274 }
274 }
275 // Null is the *only* return value that will make the browser not
275 // Null is the *only* return value that will make the browser not
276 // pop up the "don't leave" dialog.
276 // pop up the "don't leave" dialog.
277 return null;
277 return null;
278 };
278 };
279 };
279 };
280
280
281 /**
281 /**
282 * Set the dirty flag, and trigger the set_dirty.Notebook event
282 * Set the dirty flag, and trigger the set_dirty.Notebook event
283 *
283 *
284 * @method set_dirty
284 * @method set_dirty
285 */
285 */
286 Notebook.prototype.set_dirty = function (value) {
286 Notebook.prototype.set_dirty = function (value) {
287 if (value === undefined) {
287 if (value === undefined) {
288 value = true;
288 value = true;
289 }
289 }
290 if (this.dirty == value) {
290 if (this.dirty == value) {
291 return;
291 return;
292 }
292 }
293 this.events.trigger('set_dirty.Notebook', {value: value});
293 this.events.trigger('set_dirty.Notebook', {value: value});
294 };
294 };
295
295
296 /**
296 /**
297 * Scroll the top of the page to a given cell.
297 * Scroll the top of the page to a given cell.
298 *
298 *
299 * @method scroll_to_cell
299 * @method scroll_to_cell
300 * @param {Number} cell_number An index of the cell to view
300 * @param {Number} cell_number An index of the cell to view
301 * @param {Number} time Animation time in milliseconds
301 * @param {Number} time Animation time in milliseconds
302 * @return {Number} Pixel offset from the top of the container
302 * @return {Number} Pixel offset from the top of the container
303 */
303 */
304 Notebook.prototype.scroll_to_cell = function (cell_number, time) {
304 Notebook.prototype.scroll_to_cell = function (cell_number, time) {
305 var cells = this.get_cells();
305 var cells = this.get_cells();
306 time = time || 0;
306 time = time || 0;
307 cell_number = Math.min(cells.length-1,cell_number);
307 cell_number = Math.min(cells.length-1,cell_number);
308 cell_number = Math.max(0 ,cell_number);
308 cell_number = Math.max(0 ,cell_number);
309 var scroll_value = cells[cell_number].element.position().top-cells[0].element.position().top ;
309 var scroll_value = cells[cell_number].element.position().top-cells[0].element.position().top ;
310 this.element.animate({scrollTop:scroll_value}, time);
310 this.element.animate({scrollTop:scroll_value}, time);
311 return scroll_value;
311 return scroll_value;
312 };
312 };
313
313
314 /**
314 /**
315 * Scroll to the bottom of the page.
315 * Scroll to the bottom of the page.
316 *
316 *
317 * @method scroll_to_bottom
317 * @method scroll_to_bottom
318 */
318 */
319 Notebook.prototype.scroll_to_bottom = function () {
319 Notebook.prototype.scroll_to_bottom = function () {
320 this.element.animate({scrollTop:this.element.get(0).scrollHeight}, 0);
320 this.element.animate({scrollTop:this.element.get(0).scrollHeight}, 0);
321 };
321 };
322
322
323 /**
323 /**
324 * Scroll to the top of the page.
324 * Scroll to the top of the page.
325 *
325 *
326 * @method scroll_to_top
326 * @method scroll_to_top
327 */
327 */
328 Notebook.prototype.scroll_to_top = function () {
328 Notebook.prototype.scroll_to_top = function () {
329 this.element.animate({scrollTop:0}, 0);
329 this.element.animate({scrollTop:0}, 0);
330 };
330 };
331
331
332 // Edit Notebook metadata
332 // Edit Notebook metadata
333
333
334 Notebook.prototype.edit_metadata = function () {
334 Notebook.prototype.edit_metadata = function () {
335 var that = this;
335 var that = this;
336 dialog.edit_metadata({
336 dialog.edit_metadata({
337 md: this.metadata,
337 md: this.metadata,
338 callback: function (md) {
338 callback: function (md) {
339 that.metadata = md;
339 that.metadata = md;
340 },
340 },
341 name: 'Notebook',
341 name: 'Notebook',
342 notebook: this,
342 notebook: this,
343 keyboard_manager: this.keyboard_manager});
343 keyboard_manager: this.keyboard_manager});
344 };
344 };
345
345
346 // Cell indexing, retrieval, etc.
346 // Cell indexing, retrieval, etc.
347
347
348 /**
348 /**
349 * Get all cell elements in the notebook.
349 * Get all cell elements in the notebook.
350 *
350 *
351 * @method get_cell_elements
351 * @method get_cell_elements
352 * @return {jQuery} A selector of all cell elements
352 * @return {jQuery} A selector of all cell elements
353 */
353 */
354 Notebook.prototype.get_cell_elements = function () {
354 Notebook.prototype.get_cell_elements = function () {
355 return this.container.children("div.cell");
355 return this.container.children("div.cell");
356 };
356 };
357
357
358 /**
358 /**
359 * Get a particular cell element.
359 * Get a particular cell element.
360 *
360 *
361 * @method get_cell_element
361 * @method get_cell_element
362 * @param {Number} index An index of a cell to select
362 * @param {Number} index An index of a cell to select
363 * @return {jQuery} A selector of the given cell.
363 * @return {jQuery} A selector of the given cell.
364 */
364 */
365 Notebook.prototype.get_cell_element = function (index) {
365 Notebook.prototype.get_cell_element = function (index) {
366 var result = null;
366 var result = null;
367 var e = this.get_cell_elements().eq(index);
367 var e = this.get_cell_elements().eq(index);
368 if (e.length !== 0) {
368 if (e.length !== 0) {
369 result = e;
369 result = e;
370 }
370 }
371 return result;
371 return result;
372 };
372 };
373
373
374 /**
374 /**
375 * Try to get a particular cell by msg_id.
375 * Try to get a particular cell by msg_id.
376 *
376 *
377 * @method get_msg_cell
377 * @method get_msg_cell
378 * @param {String} msg_id A message UUID
378 * @param {String} msg_id A message UUID
379 * @return {Cell} Cell or null if no cell was found.
379 * @return {Cell} Cell or null if no cell was found.
380 */
380 */
381 Notebook.prototype.get_msg_cell = function (msg_id) {
381 Notebook.prototype.get_msg_cell = function (msg_id) {
382 return codecell.CodeCell.msg_cells[msg_id] || null;
382 return codecell.CodeCell.msg_cells[msg_id] || null;
383 };
383 };
384
384
385 /**
385 /**
386 * Count the cells in this notebook.
386 * Count the cells in this notebook.
387 *
387 *
388 * @method ncells
388 * @method ncells
389 * @return {Number} The number of cells in this notebook
389 * @return {Number} The number of cells in this notebook
390 */
390 */
391 Notebook.prototype.ncells = function () {
391 Notebook.prototype.ncells = function () {
392 return this.get_cell_elements().length;
392 return this.get_cell_elements().length;
393 };
393 };
394
394
395 /**
395 /**
396 * Get all Cell objects in this notebook.
396 * Get all Cell objects in this notebook.
397 *
397 *
398 * @method get_cells
398 * @method get_cells
399 * @return {Array} This notebook's Cell objects
399 * @return {Array} This notebook's Cell objects
400 */
400 */
401 // TODO: we are often calling cells as cells()[i], which we should optimize
401 // TODO: we are often calling cells as cells()[i], which we should optimize
402 // to cells(i) or a new method.
402 // to cells(i) or a new method.
403 Notebook.prototype.get_cells = function () {
403 Notebook.prototype.get_cells = function () {
404 return this.get_cell_elements().toArray().map(function (e) {
404 return this.get_cell_elements().toArray().map(function (e) {
405 return $(e).data("cell");
405 return $(e).data("cell");
406 });
406 });
407 };
407 };
408
408
409 /**
409 /**
410 * Get a Cell object from this notebook.
410 * Get a Cell object from this notebook.
411 *
411 *
412 * @method get_cell
412 * @method get_cell
413 * @param {Number} index An index of a cell to retrieve
413 * @param {Number} index An index of a cell to retrieve
414 * @return {Cell} Cell or null if no cell was found.
414 * @return {Cell} Cell or null if no cell was found.
415 */
415 */
416 Notebook.prototype.get_cell = function (index) {
416 Notebook.prototype.get_cell = function (index) {
417 var result = null;
417 var result = null;
418 var ce = this.get_cell_element(index);
418 var ce = this.get_cell_element(index);
419 if (ce !== null) {
419 if (ce !== null) {
420 result = ce.data('cell');
420 result = ce.data('cell');
421 }
421 }
422 return result;
422 return result;
423 };
423 };
424
424
425 /**
425 /**
426 * Get the cell below a given cell.
426 * Get the cell below a given cell.
427 *
427 *
428 * @method get_next_cell
428 * @method get_next_cell
429 * @param {Cell} cell The provided cell
429 * @param {Cell} cell The provided cell
430 * @return {Cell} the next cell or null if no cell was found.
430 * @return {Cell} the next cell or null if no cell was found.
431 */
431 */
432 Notebook.prototype.get_next_cell = function (cell) {
432 Notebook.prototype.get_next_cell = function (cell) {
433 var result = null;
433 var result = null;
434 var index = this.find_cell_index(cell);
434 var index = this.find_cell_index(cell);
435 if (this.is_valid_cell_index(index+1)) {
435 if (this.is_valid_cell_index(index+1)) {
436 result = this.get_cell(index+1);
436 result = this.get_cell(index+1);
437 }
437 }
438 return result;
438 return result;
439 };
439 };
440
440
441 /**
441 /**
442 * Get the cell above a given cell.
442 * Get the cell above a given cell.
443 *
443 *
444 * @method get_prev_cell
444 * @method get_prev_cell
445 * @param {Cell} cell The provided cell
445 * @param {Cell} cell The provided cell
446 * @return {Cell} The previous cell or null if no cell was found.
446 * @return {Cell} The previous cell or null if no cell was found.
447 */
447 */
448 Notebook.prototype.get_prev_cell = function (cell) {
448 Notebook.prototype.get_prev_cell = function (cell) {
449 var result = null;
449 var result = null;
450 var index = this.find_cell_index(cell);
450 var index = this.find_cell_index(cell);
451 if (index !== null && index > 0) {
451 if (index !== null && index > 0) {
452 result = this.get_cell(index-1);
452 result = this.get_cell(index-1);
453 }
453 }
454 return result;
454 return result;
455 };
455 };
456
456
457 /**
457 /**
458 * Get the numeric index of a given cell.
458 * Get the numeric index of a given cell.
459 *
459 *
460 * @method find_cell_index
460 * @method find_cell_index
461 * @param {Cell} cell The provided cell
461 * @param {Cell} cell The provided cell
462 * @return {Number} The cell's numeric index or null if no cell was found.
462 * @return {Number} The cell's numeric index or null if no cell was found.
463 */
463 */
464 Notebook.prototype.find_cell_index = function (cell) {
464 Notebook.prototype.find_cell_index = function (cell) {
465 var result = null;
465 var result = null;
466 this.get_cell_elements().filter(function (index) {
466 this.get_cell_elements().filter(function (index) {
467 if ($(this).data("cell") === cell) {
467 if ($(this).data("cell") === cell) {
468 result = index;
468 result = index;
469 }
469 }
470 });
470 });
471 return result;
471 return result;
472 };
472 };
473
473
474 /**
474 /**
475 * Get a given index , or the selected index if none is provided.
475 * Get a given index , or the selected index if none is provided.
476 *
476 *
477 * @method index_or_selected
477 * @method index_or_selected
478 * @param {Number} index A cell's index
478 * @param {Number} index A cell's index
479 * @return {Number} The given index, or selected index if none is provided.
479 * @return {Number} The given index, or selected index if none is provided.
480 */
480 */
481 Notebook.prototype.index_or_selected = function (index) {
481 Notebook.prototype.index_or_selected = function (index) {
482 var i;
482 var i;
483 if (index === undefined || index === null) {
483 if (index === undefined || index === null) {
484 i = this.get_selected_index();
484 i = this.get_selected_index();
485 if (i === null) {
485 if (i === null) {
486 i = 0;
486 i = 0;
487 }
487 }
488 } else {
488 } else {
489 i = index;
489 i = index;
490 }
490 }
491 return i;
491 return i;
492 };
492 };
493
493
494 /**
494 /**
495 * Get the currently selected cell.
495 * Get the currently selected cell.
496 * @method get_selected_cell
496 * @method get_selected_cell
497 * @return {Cell} The selected cell
497 * @return {Cell} The selected cell
498 */
498 */
499 Notebook.prototype.get_selected_cell = function () {
499 Notebook.prototype.get_selected_cell = function () {
500 var index = this.get_selected_index();
500 var index = this.get_selected_index();
501 return this.get_cell(index);
501 return this.get_cell(index);
502 };
502 };
503
503
504 /**
504 /**
505 * Check whether a cell index is valid.
505 * Check whether a cell index is valid.
506 *
506 *
507 * @method is_valid_cell_index
507 * @method is_valid_cell_index
508 * @param {Number} index A cell index
508 * @param {Number} index A cell index
509 * @return True if the index is valid, false otherwise
509 * @return True if the index is valid, false otherwise
510 */
510 */
511 Notebook.prototype.is_valid_cell_index = function (index) {
511 Notebook.prototype.is_valid_cell_index = function (index) {
512 if (index !== null && index >= 0 && index < this.ncells()) {
512 if (index !== null && index >= 0 && index < this.ncells()) {
513 return true;
513 return true;
514 } else {
514 } else {
515 return false;
515 return false;
516 }
516 }
517 };
517 };
518
518
519 /**
519 /**
520 * Get the index of the currently selected cell.
520 * Get the index of the currently selected cell.
521
521
522 * @method get_selected_index
522 * @method get_selected_index
523 * @return {Number} The selected cell's numeric index
523 * @return {Number} The selected cell's numeric index
524 */
524 */
525 Notebook.prototype.get_selected_index = function () {
525 Notebook.prototype.get_selected_index = function () {
526 var result = null;
526 var result = null;
527 this.get_cell_elements().filter(function (index) {
527 this.get_cell_elements().filter(function (index) {
528 if ($(this).data("cell").selected === true) {
528 if ($(this).data("cell").selected === true) {
529 result = index;
529 result = index;
530 }
530 }
531 });
531 });
532 return result;
532 return result;
533 };
533 };
534
534
535
535
536 // Cell selection.
536 // Cell selection.
537
537
538 /**
538 /**
539 * Programmatically select a cell.
539 * Programmatically select a cell.
540 *
540 *
541 * @method select
541 * @method select
542 * @param {Number} index A cell's index
542 * @param {Number} index A cell's index
543 * @return {Notebook} This notebook
543 * @return {Notebook} This notebook
544 */
544 */
545 Notebook.prototype.select = function (index) {
545 Notebook.prototype.select = function (index) {
546 if (this.is_valid_cell_index(index)) {
546 if (this.is_valid_cell_index(index)) {
547 var sindex = this.get_selected_index();
547 var sindex = this.get_selected_index();
548 if (sindex !== null && index !== sindex) {
548 if (sindex !== null && index !== sindex) {
549 // If we are about to select a different cell, make sure we are
549 // If we are about to select a different cell, make sure we are
550 // first in command mode.
550 // first in command mode.
551 if (this.mode !== 'command') {
551 if (this.mode !== 'command') {
552 this.command_mode();
552 this.command_mode();
553 }
553 }
554 this.get_cell(sindex).unselect();
554 this.get_cell(sindex).unselect();
555 }
555 }
556 var cell = this.get_cell(index);
556 var cell = this.get_cell(index);
557 cell.select();
557 cell.select();
558 if (cell.cell_type === 'heading') {
558 if (cell.cell_type === 'heading') {
559 this.events.trigger('selected_cell_type_changed.Notebook',
559 this.events.trigger('selected_cell_type_changed.Notebook',
560 {'cell_type':cell.cell_type,level:cell.level}
560 {'cell_type':cell.cell_type,level:cell.level}
561 );
561 );
562 } else {
562 } else {
563 this.events.trigger('selected_cell_type_changed.Notebook',
563 this.events.trigger('selected_cell_type_changed.Notebook',
564 {'cell_type':cell.cell_type}
564 {'cell_type':cell.cell_type}
565 );
565 );
566 }
566 }
567 }
567 }
568 return this;
568 return this;
569 };
569 };
570
570
571 /**
571 /**
572 * Programmatically select the next cell.
572 * Programmatically select the next cell.
573 *
573 *
574 * @method select_next
574 * @method select_next
575 * @return {Notebook} This notebook
575 * @return {Notebook} This notebook
576 */
576 */
577 Notebook.prototype.select_next = function () {
577 Notebook.prototype.select_next = function () {
578 var index = this.get_selected_index();
578 var index = this.get_selected_index();
579 this.select(index+1);
579 this.select(index+1);
580 return this;
580 return this;
581 };
581 };
582
582
583 /**
583 /**
584 * Programmatically select the previous cell.
584 * Programmatically select the previous cell.
585 *
585 *
586 * @method select_prev
586 * @method select_prev
587 * @return {Notebook} This notebook
587 * @return {Notebook} This notebook
588 */
588 */
589 Notebook.prototype.select_prev = function () {
589 Notebook.prototype.select_prev = function () {
590 var index = this.get_selected_index();
590 var index = this.get_selected_index();
591 this.select(index-1);
591 this.select(index-1);
592 return this;
592 return this;
593 };
593 };
594
594
595
595
596 // Edit/Command mode
596 // Edit/Command mode
597
597
598 /**
598 /**
599 * Gets the index of the cell that is in edit mode.
599 * Gets the index of the cell that is in edit mode.
600 *
600 *
601 * @method get_edit_index
601 * @method get_edit_index
602 *
602 *
603 * @return index {int}
603 * @return index {int}
604 **/
604 **/
605 Notebook.prototype.get_edit_index = function () {
605 Notebook.prototype.get_edit_index = function () {
606 var result = null;
606 var result = null;
607 this.get_cell_elements().filter(function (index) {
607 this.get_cell_elements().filter(function (index) {
608 if ($(this).data("cell").mode === 'edit') {
608 if ($(this).data("cell").mode === 'edit') {
609 result = index;
609 result = index;
610 }
610 }
611 });
611 });
612 return result;
612 return result;
613 };
613 };
614
614
615 /**
615 /**
616 * Handle when a a cell blurs and the notebook should enter command mode.
616 * Handle when a a cell blurs and the notebook should enter command mode.
617 *
617 *
618 * @method handle_command_mode
618 * @method handle_command_mode
619 * @param [cell] {Cell} Cell to enter command mode on.
619 * @param [cell] {Cell} Cell to enter command mode on.
620 **/
620 **/
621 Notebook.prototype.handle_command_mode = function (cell) {
621 Notebook.prototype.handle_command_mode = function (cell) {
622 if (this.mode !== 'command') {
622 if (this.mode !== 'command') {
623 cell.command_mode();
623 cell.command_mode();
624 this.mode = 'command';
624 this.mode = 'command';
625 this.events.trigger('command_mode.Notebook');
625 this.events.trigger('command_mode.Notebook');
626 this.keyboard_manager.command_mode();
626 this.keyboard_manager.command_mode();
627 }
627 }
628 };
628 };
629
629
630 /**
630 /**
631 * Make the notebook enter command mode.
631 * Make the notebook enter command mode.
632 *
632 *
633 * @method command_mode
633 * @method command_mode
634 **/
634 **/
635 Notebook.prototype.command_mode = function () {
635 Notebook.prototype.command_mode = function () {
636 var cell = this.get_cell(this.get_edit_index());
636 var cell = this.get_cell(this.get_edit_index());
637 if (cell && this.mode !== 'command') {
637 if (cell && this.mode !== 'command') {
638 // We don't call cell.command_mode, but rather call cell.focus_cell()
638 // We don't call cell.command_mode, but rather call cell.focus_cell()
639 // which will blur and CM editor and trigger the call to
639 // which will blur and CM editor and trigger the call to
640 // handle_command_mode.
640 // handle_command_mode.
641 cell.focus_cell();
641 cell.focus_cell();
642 }
642 }
643 };
643 };
644
644
645 /**
645 /**
646 * Handle when a cell fires it's edit_mode event.
646 * Handle when a cell fires it's edit_mode event.
647 *
647 *
648 * @method handle_edit_mode
648 * @method handle_edit_mode
649 * @param [cell] {Cell} Cell to enter edit mode on.
649 * @param [cell] {Cell} Cell to enter edit mode on.
650 **/
650 **/
651 Notebook.prototype.handle_edit_mode = function (cell) {
651 Notebook.prototype.handle_edit_mode = function (cell) {
652 if (cell && this.mode !== 'edit') {
652 if (cell && this.mode !== 'edit') {
653 cell.edit_mode();
653 cell.edit_mode();
654 this.mode = 'edit';
654 this.mode = 'edit';
655 this.events.trigger('edit_mode.Notebook');
655 this.events.trigger('edit_mode.Notebook');
656 this.keyboard_manager.edit_mode();
656 this.keyboard_manager.edit_mode();
657 }
657 }
658 };
658 };
659
659
660 /**
660 /**
661 * Make a cell enter edit mode.
661 * Make a cell enter edit mode.
662 *
662 *
663 * @method edit_mode
663 * @method edit_mode
664 **/
664 **/
665 Notebook.prototype.edit_mode = function () {
665 Notebook.prototype.edit_mode = function () {
666 var cell = this.get_selected_cell();
666 var cell = this.get_selected_cell();
667 if (cell && this.mode !== 'edit') {
667 if (cell && this.mode !== 'edit') {
668 cell.unrender();
668 cell.unrender();
669 cell.focus_editor();
669 cell.focus_editor();
670 }
670 }
671 };
671 };
672
672
673 /**
673 /**
674 * Focus the currently selected cell.
674 * Focus the currently selected cell.
675 *
675 *
676 * @method focus_cell
676 * @method focus_cell
677 **/
677 **/
678 Notebook.prototype.focus_cell = function () {
678 Notebook.prototype.focus_cell = function () {
679 var cell = this.get_selected_cell();
679 var cell = this.get_selected_cell();
680 if (cell === null) {return;} // No cell is selected
680 if (cell === null) {return;} // No cell is selected
681 cell.focus_cell();
681 cell.focus_cell();
682 };
682 };
683
683
684 // Cell movement
684 // Cell movement
685
685
686 /**
686 /**
687 * Move given (or selected) cell up and select it.
687 * Move given (or selected) cell up and select it.
688 *
688 *
689 * @method move_cell_up
689 * @method move_cell_up
690 * @param [index] {integer} cell index
690 * @param [index] {integer} cell index
691 * @return {Notebook} This notebook
691 * @return {Notebook} This notebook
692 **/
692 **/
693 Notebook.prototype.move_cell_up = function (index) {
693 Notebook.prototype.move_cell_up = function (index) {
694 var i = this.index_or_selected(index);
694 var i = this.index_or_selected(index);
695 if (this.is_valid_cell_index(i) && i > 0) {
695 if (this.is_valid_cell_index(i) && i > 0) {
696 var pivot = this.get_cell_element(i-1);
696 var pivot = this.get_cell_element(i-1);
697 var tomove = this.get_cell_element(i);
697 var tomove = this.get_cell_element(i);
698 if (pivot !== null && tomove !== null) {
698 if (pivot !== null && tomove !== null) {
699 tomove.detach();
699 tomove.detach();
700 pivot.before(tomove);
700 pivot.before(tomove);
701 this.select(i-1);
701 this.select(i-1);
702 var cell = this.get_selected_cell();
702 var cell = this.get_selected_cell();
703 cell.focus_cell();
703 cell.focus_cell();
704 }
704 }
705 this.set_dirty(true);
705 this.set_dirty(true);
706 }
706 }
707 return this;
707 return this;
708 };
708 };
709
709
710
710
711 /**
711 /**
712 * Move given (or selected) cell down and select it
712 * Move given (or selected) cell down and select it
713 *
713 *
714 * @method move_cell_down
714 * @method move_cell_down
715 * @param [index] {integer} cell index
715 * @param [index] {integer} cell index
716 * @return {Notebook} This notebook
716 * @return {Notebook} This notebook
717 **/
717 **/
718 Notebook.prototype.move_cell_down = function (index) {
718 Notebook.prototype.move_cell_down = function (index) {
719 var i = this.index_or_selected(index);
719 var i = this.index_or_selected(index);
720 if (this.is_valid_cell_index(i) && this.is_valid_cell_index(i+1)) {
720 if (this.is_valid_cell_index(i) && this.is_valid_cell_index(i+1)) {
721 var pivot = this.get_cell_element(i+1);
721 var pivot = this.get_cell_element(i+1);
722 var tomove = this.get_cell_element(i);
722 var tomove = this.get_cell_element(i);
723 if (pivot !== null && tomove !== null) {
723 if (pivot !== null && tomove !== null) {
724 tomove.detach();
724 tomove.detach();
725 pivot.after(tomove);
725 pivot.after(tomove);
726 this.select(i+1);
726 this.select(i+1);
727 var cell = this.get_selected_cell();
727 var cell = this.get_selected_cell();
728 cell.focus_cell();
728 cell.focus_cell();
729 }
729 }
730 }
730 }
731 this.set_dirty();
731 this.set_dirty();
732 return this;
732 return this;
733 };
733 };
734
734
735
735
736 // Insertion, deletion.
736 // Insertion, deletion.
737
737
738 /**
738 /**
739 * Delete a cell from the notebook.
739 * Delete a cell from the notebook.
740 *
740 *
741 * @method delete_cell
741 * @method delete_cell
742 * @param [index] A cell's numeric index
742 * @param [index] A cell's numeric index
743 * @return {Notebook} This notebook
743 * @return {Notebook} This notebook
744 */
744 */
745 Notebook.prototype.delete_cell = function (index) {
745 Notebook.prototype.delete_cell = function (index) {
746 var i = this.index_or_selected(index);
746 var i = this.index_or_selected(index);
747 var cell = this.get_cell(i);
747 var cell = this.get_cell(i);
748 if (!cell.is_deletable()) {
748 if (!cell.is_deletable()) {
749 return this;
749 return this;
750 }
750 }
751
751
752 this.undelete_backup = cell.toJSON();
752 this.undelete_backup = cell.toJSON();
753 $('#undelete_cell').removeClass('disabled');
753 $('#undelete_cell').removeClass('disabled');
754 if (this.is_valid_cell_index(i)) {
754 if (this.is_valid_cell_index(i)) {
755 var old_ncells = this.ncells();
755 var old_ncells = this.ncells();
756 var ce = this.get_cell_element(i);
756 var ce = this.get_cell_element(i);
757 ce.remove();
757 ce.remove();
758 if (i === 0) {
758 if (i === 0) {
759 // Always make sure we have at least one cell.
759 // Always make sure we have at least one cell.
760 if (old_ncells === 1) {
760 if (old_ncells === 1) {
761 this.insert_cell_below('code');
761 this.insert_cell_below('code');
762 }
762 }
763 this.select(0);
763 this.select(0);
764 this.undelete_index = 0;
764 this.undelete_index = 0;
765 this.undelete_below = false;
765 this.undelete_below = false;
766 } else if (i === old_ncells-1 && i !== 0) {
766 } else if (i === old_ncells-1 && i !== 0) {
767 this.select(i-1);
767 this.select(i-1);
768 this.undelete_index = i - 1;
768 this.undelete_index = i - 1;
769 this.undelete_below = true;
769 this.undelete_below = true;
770 } else {
770 } else {
771 this.select(i);
771 this.select(i);
772 this.undelete_index = i;
772 this.undelete_index = i;
773 this.undelete_below = false;
773 this.undelete_below = false;
774 }
774 }
775 this.events.trigger('delete.Cell', {'cell': cell, 'index': i});
775 this.events.trigger('delete.Cell', {'cell': cell, 'index': i});
776 this.set_dirty(true);
776 this.set_dirty(true);
777 }
777 }
778 return this;
778 return this;
779 };
779 };
780
780
781 /**
781 /**
782 * Restore the most recently deleted cell.
782 * Restore the most recently deleted cell.
783 *
783 *
784 * @method undelete
784 * @method undelete
785 */
785 */
786 Notebook.prototype.undelete_cell = function() {
786 Notebook.prototype.undelete_cell = function() {
787 if (this.undelete_backup !== null && this.undelete_index !== null) {
787 if (this.undelete_backup !== null && this.undelete_index !== null) {
788 var current_index = this.get_selected_index();
788 var current_index = this.get_selected_index();
789 if (this.undelete_index < current_index) {
789 if (this.undelete_index < current_index) {
790 current_index = current_index + 1;
790 current_index = current_index + 1;
791 }
791 }
792 if (this.undelete_index >= this.ncells()) {
792 if (this.undelete_index >= this.ncells()) {
793 this.select(this.ncells() - 1);
793 this.select(this.ncells() - 1);
794 }
794 }
795 else {
795 else {
796 this.select(this.undelete_index);
796 this.select(this.undelete_index);
797 }
797 }
798 var cell_data = this.undelete_backup;
798 var cell_data = this.undelete_backup;
799 var new_cell = null;
799 var new_cell = null;
800 if (this.undelete_below) {
800 if (this.undelete_below) {
801 new_cell = this.insert_cell_below(cell_data.cell_type);
801 new_cell = this.insert_cell_below(cell_data.cell_type);
802 } else {
802 } else {
803 new_cell = this.insert_cell_above(cell_data.cell_type);
803 new_cell = this.insert_cell_above(cell_data.cell_type);
804 }
804 }
805 new_cell.fromJSON(cell_data);
805 new_cell.fromJSON(cell_data);
806 if (this.undelete_below) {
806 if (this.undelete_below) {
807 this.select(current_index+1);
807 this.select(current_index+1);
808 } else {
808 } else {
809 this.select(current_index);
809 this.select(current_index);
810 }
810 }
811 this.undelete_backup = null;
811 this.undelete_backup = null;
812 this.undelete_index = null;
812 this.undelete_index = null;
813 }
813 }
814 $('#undelete_cell').addClass('disabled');
814 $('#undelete_cell').addClass('disabled');
815 };
815 };
816
816
817 /**
817 /**
818 * Insert a cell so that after insertion the cell is at given index.
818 * Insert a cell so that after insertion the cell is at given index.
819 *
819 *
820 * If cell type is not provided, it will default to the type of the
820 * If cell type is not provided, it will default to the type of the
821 * currently active cell.
821 * currently active cell.
822 *
822 *
823 * Similar to insert_above, but index parameter is mandatory
823 * Similar to insert_above, but index parameter is mandatory
824 *
824 *
825 * Index will be brought back into the accessible range [0,n]
825 * Index will be brought back into the accessible range [0,n]
826 *
826 *
827 * @method insert_cell_at_index
827 * @method insert_cell_at_index
828 * @param [type] {string} in ['code','markdown', 'raw'], defaults to 'code'
828 * @param [type] {string} in ['code','markdown', 'raw'], defaults to 'code'
829 * @param [index] {int} a valid index where to insert cell
829 * @param [index] {int} a valid index where to insert cell
830 *
830 *
831 * @return cell {cell|null} created cell or null
831 * @return cell {cell|null} created cell or null
832 **/
832 **/
833 Notebook.prototype.insert_cell_at_index = function(type, index){
833 Notebook.prototype.insert_cell_at_index = function(type, index){
834
834
835 var ncells = this.ncells();
835 var ncells = this.ncells();
836 index = Math.min(index, ncells);
836 index = Math.min(index, ncells);
837 index = Math.max(index, 0);
837 index = Math.max(index, 0);
838 var cell = null;
838 var cell = null;
839 type = type || this.default_cell_type;
839 type = type || this.default_cell_type;
840 if (type === 'above') {
840 if (type === 'above') {
841 if (index > 0) {
841 if (index > 0) {
842 type = this.get_cell(index-1).cell_type;
842 type = this.get_cell(index-1).cell_type;
843 } else {
843 } else {
844 type = 'code';
844 type = 'code';
845 }
845 }
846 } else if (type === 'below') {
846 } else if (type === 'below') {
847 if (index < ncells) {
847 if (index < ncells) {
848 type = this.get_cell(index).cell_type;
848 type = this.get_cell(index).cell_type;
849 } else {
849 } else {
850 type = 'code';
850 type = 'code';
851 }
851 }
852 } else if (type === 'selected') {
852 } else if (type === 'selected') {
853 type = this.get_selected_cell().cell_type;
853 type = this.get_selected_cell().cell_type;
854 }
854 }
855
855
856 if (ncells === 0 || this.is_valid_cell_index(index) || index === ncells) {
856 if (ncells === 0 || this.is_valid_cell_index(index) || index === ncells) {
857 var cell_options = {
857 var cell_options = {
858 events: this.events,
858 events: this.events,
859 config: this.config,
859 config: this.config,
860 keyboard_manager: this.keyboard_manager,
860 keyboard_manager: this.keyboard_manager,
861 notebook: this,
861 notebook: this,
862 tooltip: this.tooltip,
862 tooltip: this.tooltip,
863 };
863 };
864 switch(type) {
864 switch(type) {
865 case 'code':
865 case 'code':
866 cell = new codecell.CodeCell(this.kernel, cell_options);
866 cell = new codecell.CodeCell(this.kernel, cell_options);
867 cell.set_input_prompt();
867 cell.set_input_prompt();
868 break;
868 break;
869 case 'markdown':
869 case 'markdown':
870 cell = new textcell.MarkdownCell(cell_options);
870 cell = new textcell.MarkdownCell(cell_options);
871 break;
871 break;
872 case 'raw':
872 case 'raw':
873 cell = new textcell.RawCell(cell_options);
873 cell = new textcell.RawCell(cell_options);
874 break;
874 break;
875 default:
875 default:
876 console.log("invalid cell type: ", type);
876 console.log("invalid cell type: ", type);
877 }
877 }
878
878
879 if(this._insert_element_at_index(cell.element,index)) {
879 if(this._insert_element_at_index(cell.element,index)) {
880 cell.render();
880 cell.render();
881 this.events.trigger('create.Cell', {'cell': cell, 'index': index});
881 this.events.trigger('create.Cell', {'cell': cell, 'index': index});
882 cell.refresh();
882 cell.refresh();
883 // We used to select the cell after we refresh it, but there
883 // We used to select the cell after we refresh it, but there
884 // are now cases were this method is called where select is
884 // are now cases were this method is called where select is
885 // not appropriate. The selection logic should be handled by the
885 // not appropriate. The selection logic should be handled by the
886 // caller of the the top level insert_cell methods.
886 // caller of the the top level insert_cell methods.
887 this.set_dirty(true);
887 this.set_dirty(true);
888 }
888 }
889 }
889 }
890 return cell;
890 return cell;
891
891
892 };
892 };
893
893
894 /**
894 /**
895 * Insert an element at given cell index.
895 * Insert an element at given cell index.
896 *
896 *
897 * @method _insert_element_at_index
897 * @method _insert_element_at_index
898 * @param element {dom_element} a cell element
898 * @param element {dom_element} a cell element
899 * @param [index] {int} a valid index where to inser cell
899 * @param [index] {int} a valid index where to inser cell
900 * @private
900 * @private
901 *
901 *
902 * return true if everything whent fine.
902 * return true if everything whent fine.
903 **/
903 **/
904 Notebook.prototype._insert_element_at_index = function(element, index){
904 Notebook.prototype._insert_element_at_index = function(element, index){
905 if (element === undefined){
905 if (element === undefined){
906 return false;
906 return false;
907 }
907 }
908
908
909 var ncells = this.ncells();
909 var ncells = this.ncells();
910
910
911 if (ncells === 0) {
911 if (ncells === 0) {
912 // special case append if empty
912 // special case append if empty
913 this.element.find('div.end_space').before(element);
913 this.element.find('div.end_space').before(element);
914 } else if ( ncells === index ) {
914 } else if ( ncells === index ) {
915 // special case append it the end, but not empty
915 // special case append it the end, but not empty
916 this.get_cell_element(index-1).after(element);
916 this.get_cell_element(index-1).after(element);
917 } else if (this.is_valid_cell_index(index)) {
917 } else if (this.is_valid_cell_index(index)) {
918 // otherwise always somewhere to append to
918 // otherwise always somewhere to append to
919 this.get_cell_element(index).before(element);
919 this.get_cell_element(index).before(element);
920 } else {
920 } else {
921 return false;
921 return false;
922 }
922 }
923
923
924 if (this.undelete_index !== null && index <= this.undelete_index) {
924 if (this.undelete_index !== null && index <= this.undelete_index) {
925 this.undelete_index = this.undelete_index + 1;
925 this.undelete_index = this.undelete_index + 1;
926 this.set_dirty(true);
926 this.set_dirty(true);
927 }
927 }
928 return true;
928 return true;
929 };
929 };
930
930
931 /**
931 /**
932 * Insert a cell of given type above given index, or at top
932 * Insert a cell of given type above given index, or at top
933 * of notebook if index smaller than 0.
933 * of notebook if index smaller than 0.
934 *
934 *
935 * default index value is the one of currently selected cell
935 * default index value is the one of currently selected cell
936 *
936 *
937 * @method insert_cell_above
937 * @method insert_cell_above
938 * @param [type] {string} cell type
938 * @param [type] {string} cell type
939 * @param [index] {integer}
939 * @param [index] {integer}
940 *
940 *
941 * @return handle to created cell or null
941 * @return handle to created cell or null
942 **/
942 **/
943 Notebook.prototype.insert_cell_above = function (type, index) {
943 Notebook.prototype.insert_cell_above = function (type, index) {
944 index = this.index_or_selected(index);
944 index = this.index_or_selected(index);
945 return this.insert_cell_at_index(type, index);
945 return this.insert_cell_at_index(type, index);
946 };
946 };
947
947
948 /**
948 /**
949 * Insert a cell of given type below given index, or at bottom
949 * Insert a cell of given type below given index, or at bottom
950 * of notebook if index greater than number of cells
950 * of notebook if index greater than number of cells
951 *
951 *
952 * default index value is the one of currently selected cell
952 * default index value is the one of currently selected cell
953 *
953 *
954 * @method insert_cell_below
954 * @method insert_cell_below
955 * @param [type] {string} cell type
955 * @param [type] {string} cell type
956 * @param [index] {integer}
956 * @param [index] {integer}
957 *
957 *
958 * @return handle to created cell or null
958 * @return handle to created cell or null
959 *
959 *
960 **/
960 **/
961 Notebook.prototype.insert_cell_below = function (type, index) {
961 Notebook.prototype.insert_cell_below = function (type, index) {
962 index = this.index_or_selected(index);
962 index = this.index_or_selected(index);
963 return this.insert_cell_at_index(type, index+1);
963 return this.insert_cell_at_index(type, index+1);
964 };
964 };
965
965
966
966
967 /**
967 /**
968 * Insert cell at end of notebook
968 * Insert cell at end of notebook
969 *
969 *
970 * @method insert_cell_at_bottom
970 * @method insert_cell_at_bottom
971 * @param {String} type cell type
971 * @param {String} type cell type
972 *
972 *
973 * @return the added cell; or null
973 * @return the added cell; or null
974 **/
974 **/
975 Notebook.prototype.insert_cell_at_bottom = function (type){
975 Notebook.prototype.insert_cell_at_bottom = function (type){
976 var len = this.ncells();
976 var len = this.ncells();
977 return this.insert_cell_below(type,len-1);
977 return this.insert_cell_below(type,len-1);
978 };
978 };
979
979
980 /**
980 /**
981 * Turn a cell into a code cell.
981 * Turn a cell into a code cell.
982 *
982 *
983 * @method to_code
983 * @method to_code
984 * @param {Number} [index] A cell's index
984 * @param {Number} [index] A cell's index
985 */
985 */
986 Notebook.prototype.to_code = function (index) {
986 Notebook.prototype.to_code = function (index) {
987 var i = this.index_or_selected(index);
987 var i = this.index_or_selected(index);
988 if (this.is_valid_cell_index(i)) {
988 if (this.is_valid_cell_index(i)) {
989 var source_cell = this.get_cell(i);
989 var source_cell = this.get_cell(i);
990 if (!(source_cell instanceof codecell.CodeCell)) {
990 if (!(source_cell instanceof codecell.CodeCell)) {
991 var target_cell = this.insert_cell_below('code',i);
991 var target_cell = this.insert_cell_below('code',i);
992 var text = source_cell.get_text();
992 var text = source_cell.get_text();
993 if (text === source_cell.placeholder) {
993 if (text === source_cell.placeholder) {
994 text = '';
994 text = '';
995 }
995 }
996 //metadata
996 //metadata
997 target_cell.metadata = source_cell.metadata;
997 target_cell.metadata = source_cell.metadata;
998
998
999 target_cell.set_text(text);
999 target_cell.set_text(text);
1000 // make this value the starting point, so that we can only undo
1000 // make this value the starting point, so that we can only undo
1001 // to this state, instead of a blank cell
1001 // to this state, instead of a blank cell
1002 target_cell.code_mirror.clearHistory();
1002 target_cell.code_mirror.clearHistory();
1003 source_cell.element.remove();
1003 source_cell.element.remove();
1004 this.select(i);
1004 this.select(i);
1005 var cursor = source_cell.code_mirror.getCursor();
1005 var cursor = source_cell.code_mirror.getCursor();
1006 target_cell.code_mirror.setCursor(cursor);
1006 target_cell.code_mirror.setCursor(cursor);
1007 this.set_dirty(true);
1007 this.set_dirty(true);
1008 }
1008 }
1009 }
1009 }
1010 };
1010 };
1011
1011
1012 /**
1012 /**
1013 * Turn a cell into a Markdown cell.
1013 * Turn a cell into a Markdown cell.
1014 *
1014 *
1015 * @method to_markdown
1015 * @method to_markdown
1016 * @param {Number} [index] A cell's index
1016 * @param {Number} [index] A cell's index
1017 */
1017 */
1018 Notebook.prototype.to_markdown = function (index) {
1018 Notebook.prototype.to_markdown = function (index) {
1019 var i = this.index_or_selected(index);
1019 var i = this.index_or_selected(index);
1020 if (this.is_valid_cell_index(i)) {
1020 if (this.is_valid_cell_index(i)) {
1021 var source_cell = this.get_cell(i);
1021 var source_cell = this.get_cell(i);
1022
1022
1023 if (!(source_cell instanceof textcell.MarkdownCell)) {
1023 if (!(source_cell instanceof textcell.MarkdownCell)) {
1024 var target_cell = this.insert_cell_below('markdown',i);
1024 var target_cell = this.insert_cell_below('markdown',i);
1025 var text = source_cell.get_text();
1025 var text = source_cell.get_text();
1026
1026
1027 if (text === source_cell.placeholder) {
1027 if (text === source_cell.placeholder) {
1028 text = '';
1028 text = '';
1029 }
1029 }
1030 // metadata
1030 // metadata
1031 target_cell.metadata = source_cell.metadata
1031 target_cell.metadata = source_cell.metadata
1032 // We must show the editor before setting its contents
1032 // We must show the editor before setting its contents
1033 target_cell.unrender();
1033 target_cell.unrender();
1034 target_cell.set_text(text);
1034 target_cell.set_text(text);
1035 // make this value the starting point, so that we can only undo
1035 // make this value the starting point, so that we can only undo
1036 // to this state, instead of a blank cell
1036 // to this state, instead of a blank cell
1037 target_cell.code_mirror.clearHistory();
1037 target_cell.code_mirror.clearHistory();
1038 source_cell.element.remove();
1038 source_cell.element.remove();
1039 this.select(i);
1039 this.select(i);
1040 if ((source_cell instanceof textcell.TextCell) && source_cell.rendered) {
1040 if ((source_cell instanceof textcell.TextCell) && source_cell.rendered) {
1041 target_cell.render();
1041 target_cell.render();
1042 }
1042 }
1043 var cursor = source_cell.code_mirror.getCursor();
1043 var cursor = source_cell.code_mirror.getCursor();
1044 target_cell.code_mirror.setCursor(cursor);
1044 target_cell.code_mirror.setCursor(cursor);
1045 this.set_dirty(true);
1045 this.set_dirty(true);
1046 }
1046 }
1047 }
1047 }
1048 };
1048 };
1049
1049
1050 /**
1050 /**
1051 * Turn a cell into a raw text cell.
1051 * Turn a cell into a raw text cell.
1052 *
1052 *
1053 * @method to_raw
1053 * @method to_raw
1054 * @param {Number} [index] A cell's index
1054 * @param {Number} [index] A cell's index
1055 */
1055 */
1056 Notebook.prototype.to_raw = function (index) {
1056 Notebook.prototype.to_raw = function (index) {
1057 var i = this.index_or_selected(index);
1057 var i = this.index_or_selected(index);
1058 if (this.is_valid_cell_index(i)) {
1058 if (this.is_valid_cell_index(i)) {
1059 var target_cell = null;
1059 var target_cell = null;
1060 var source_cell = this.get_cell(i);
1060 var source_cell = this.get_cell(i);
1061
1061
1062 if (!(source_cell instanceof textcell.RawCell)) {
1062 if (!(source_cell instanceof textcell.RawCell)) {
1063 target_cell = this.insert_cell_below('raw',i);
1063 target_cell = this.insert_cell_below('raw',i);
1064 var text = source_cell.get_text();
1064 var text = source_cell.get_text();
1065 if (text === source_cell.placeholder) {
1065 if (text === source_cell.placeholder) {
1066 text = '';
1066 text = '';
1067 }
1067 }
1068 //metadata
1068 //metadata
1069 target_cell.metadata = source_cell.metadata;
1069 target_cell.metadata = source_cell.metadata;
1070 // We must show the editor before setting its contents
1070 // We must show the editor before setting its contents
1071 target_cell.unrender();
1071 target_cell.unrender();
1072 target_cell.set_text(text);
1072 target_cell.set_text(text);
1073 // make this value the starting point, so that we can only undo
1073 // make this value the starting point, so that we can only undo
1074 // to this state, instead of a blank cell
1074 // to this state, instead of a blank cell
1075 target_cell.code_mirror.clearHistory();
1075 target_cell.code_mirror.clearHistory();
1076 source_cell.element.remove();
1076 source_cell.element.remove();
1077 this.select(i);
1077 this.select(i);
1078 var cursor = source_cell.code_mirror.getCursor();
1078 var cursor = source_cell.code_mirror.getCursor();
1079 target_cell.code_mirror.setCursor(cursor);
1079 target_cell.code_mirror.setCursor(cursor);
1080 this.set_dirty(true);
1080 this.set_dirty(true);
1081 }
1081 }
1082 }
1082 }
1083 };
1083 };
1084
1084
1085 /**
1085 /**
1086 * Turn a cell into a heading cell.
1086 * Turn a cell into a heading cell.
1087 *
1087 *
1088 * @method to_heading
1088 * @method to_heading
1089 * @param {Number} [index] A cell's index
1089 * @param {Number} [index] A cell's index
1090 * @param {Number} [level] A heading level (e.g., 1 becomes &lt;h1&gt;)
1090 * @param {Number} [level] A heading level (e.g., 1 becomes &lt;h1&gt;)
1091 */
1091 */
1092 Notebook.prototype.to_heading = function (index, level) {
1092 Notebook.prototype.to_heading = function (index, level) {
1093 level = level || 1;
1093 level = level || 1;
1094 var i = this.index_or_selected(index);
1094 var i = this.index_or_selected(index);
1095 if (this.is_valid_cell_index(i)) {
1095 if (this.is_valid_cell_index(i)) {
1096 var source_cell = this.get_cell(i);
1096 var source_cell = this.get_cell(i);
1097 var target_cell = null;
1097 var target_cell = null;
1098 if (source_cell instanceof textcell.MarkdownCell) {
1098 if (source_cell instanceof textcell.MarkdownCell) {
1099 source_cell.set_heading_level(level);
1099 source_cell.set_heading_level(level);
1100 } else {
1100 } else {
1101 target_cell = this.insert_cell_below('markdown',i);
1101 target_cell = this.insert_cell_below('markdown',i);
1102 var text = source_cell.get_text();
1102 var text = source_cell.get_text();
1103 if (text === source_cell.placeholder) {
1103 if (text === source_cell.placeholder) {
1104 text = '';
1104 text = '';
1105 }
1105 }
1106 //metadata
1106 //metadata
1107 target_cell.metadata = source_cell.metadata;
1107 target_cell.metadata = source_cell.metadata;
1108 // We must show the editor before setting its contents
1108 // We must show the editor before setting its contents
1109 target_cell.unrender();
1109 target_cell.unrender();
1110 target_cell.set_text(text);
1110 target_cell.set_text(text);
1111 target_cell.set_heading_level(level);
1111 target_cell.set_heading_level(level);
1112 // make this value the starting point, so that we can only undo
1112 // make this value the starting point, so that we can only undo
1113 // to this state, instead of a blank cell
1113 // to this state, instead of a blank cell
1114 target_cell.code_mirror.clearHistory();
1114 target_cell.code_mirror.clearHistory();
1115 source_cell.element.remove();
1115 source_cell.element.remove();
1116 this.select(i);
1116 this.select(i);
1117 var cursor = source_cell.code_mirror.getCursor();
1117 var cursor = source_cell.code_mirror.getCursor();
1118 target_cell.code_mirror.setCursor(cursor);
1118 target_cell.code_mirror.setCursor(cursor);
1119 if ((source_cell instanceof textcell.TextCell) && source_cell.rendered) {
1119 if ((source_cell instanceof textcell.TextCell) && source_cell.rendered) {
1120 target_cell.render();
1120 target_cell.render();
1121 }
1121 }
1122 }
1122 }
1123 this.set_dirty(true);
1123 this.set_dirty(true);
1124 this.events.trigger('selected_cell_type_changed.Notebook',
1124 this.events.trigger('selected_cell_type_changed.Notebook',
1125 {'cell_type':'markdown',level:level}
1125 {'cell_type':'markdown',level:level}
1126 );
1126 );
1127 }
1127 }
1128 };
1128 };
1129
1129
1130
1130
1131 // Cut/Copy/Paste
1131 // Cut/Copy/Paste
1132
1132
1133 /**
1133 /**
1134 * Enable UI elements for pasting cells.
1134 * Enable UI elements for pasting cells.
1135 *
1135 *
1136 * @method enable_paste
1136 * @method enable_paste
1137 */
1137 */
1138 Notebook.prototype.enable_paste = function () {
1138 Notebook.prototype.enable_paste = function () {
1139 var that = this;
1139 var that = this;
1140 if (!this.paste_enabled) {
1140 if (!this.paste_enabled) {
1141 $('#paste_cell_replace').removeClass('disabled')
1141 $('#paste_cell_replace').removeClass('disabled')
1142 .on('click', function () {that.paste_cell_replace();});
1142 .on('click', function () {that.paste_cell_replace();});
1143 $('#paste_cell_above').removeClass('disabled')
1143 $('#paste_cell_above').removeClass('disabled')
1144 .on('click', function () {that.paste_cell_above();});
1144 .on('click', function () {that.paste_cell_above();});
1145 $('#paste_cell_below').removeClass('disabled')
1145 $('#paste_cell_below').removeClass('disabled')
1146 .on('click', function () {that.paste_cell_below();});
1146 .on('click', function () {that.paste_cell_below();});
1147 this.paste_enabled = true;
1147 this.paste_enabled = true;
1148 }
1148 }
1149 };
1149 };
1150
1150
1151 /**
1151 /**
1152 * Disable UI elements for pasting cells.
1152 * Disable UI elements for pasting cells.
1153 *
1153 *
1154 * @method disable_paste
1154 * @method disable_paste
1155 */
1155 */
1156 Notebook.prototype.disable_paste = function () {
1156 Notebook.prototype.disable_paste = function () {
1157 if (this.paste_enabled) {
1157 if (this.paste_enabled) {
1158 $('#paste_cell_replace').addClass('disabled').off('click');
1158 $('#paste_cell_replace').addClass('disabled').off('click');
1159 $('#paste_cell_above').addClass('disabled').off('click');
1159 $('#paste_cell_above').addClass('disabled').off('click');
1160 $('#paste_cell_below').addClass('disabled').off('click');
1160 $('#paste_cell_below').addClass('disabled').off('click');
1161 this.paste_enabled = false;
1161 this.paste_enabled = false;
1162 }
1162 }
1163 };
1163 };
1164
1164
1165 /**
1165 /**
1166 * Cut a cell.
1166 * Cut a cell.
1167 *
1167 *
1168 * @method cut_cell
1168 * @method cut_cell
1169 */
1169 */
1170 Notebook.prototype.cut_cell = function () {
1170 Notebook.prototype.cut_cell = function () {
1171 this.copy_cell();
1171 this.copy_cell();
1172 this.delete_cell();
1172 this.delete_cell();
1173 };
1173 };
1174
1174
1175 /**
1175 /**
1176 * Copy a cell.
1176 * Copy a cell.
1177 *
1177 *
1178 * @method copy_cell
1178 * @method copy_cell
1179 */
1179 */
1180 Notebook.prototype.copy_cell = function () {
1180 Notebook.prototype.copy_cell = function () {
1181 var cell = this.get_selected_cell();
1181 var cell = this.get_selected_cell();
1182 this.clipboard = cell.toJSON();
1182 this.clipboard = cell.toJSON();
1183 // remove undeletable status from the copied cell
1183 // remove undeletable status from the copied cell
1184 if (this.clipboard.metadata.deletable !== undefined) {
1184 if (this.clipboard.metadata.deletable !== undefined) {
1185 delete this.clipboard.metadata.deletable;
1185 delete this.clipboard.metadata.deletable;
1186 }
1186 }
1187 this.enable_paste();
1187 this.enable_paste();
1188 };
1188 };
1189
1189
1190 /**
1190 /**
1191 * Replace the selected cell with a cell in the clipboard.
1191 * Replace the selected cell with a cell in the clipboard.
1192 *
1192 *
1193 * @method paste_cell_replace
1193 * @method paste_cell_replace
1194 */
1194 */
1195 Notebook.prototype.paste_cell_replace = function () {
1195 Notebook.prototype.paste_cell_replace = function () {
1196 if (this.clipboard !== null && this.paste_enabled) {
1196 if (this.clipboard !== null && this.paste_enabled) {
1197 var cell_data = this.clipboard;
1197 var cell_data = this.clipboard;
1198 var new_cell = this.insert_cell_above(cell_data.cell_type);
1198 var new_cell = this.insert_cell_above(cell_data.cell_type);
1199 new_cell.fromJSON(cell_data);
1199 new_cell.fromJSON(cell_data);
1200 var old_cell = this.get_next_cell(new_cell);
1200 var old_cell = this.get_next_cell(new_cell);
1201 this.delete_cell(this.find_cell_index(old_cell));
1201 this.delete_cell(this.find_cell_index(old_cell));
1202 this.select(this.find_cell_index(new_cell));
1202 this.select(this.find_cell_index(new_cell));
1203 }
1203 }
1204 };
1204 };
1205
1205
1206 /**
1206 /**
1207 * Paste a cell from the clipboard above the selected cell.
1207 * Paste a cell from the clipboard above the selected cell.
1208 *
1208 *
1209 * @method paste_cell_above
1209 * @method paste_cell_above
1210 */
1210 */
1211 Notebook.prototype.paste_cell_above = function () {
1211 Notebook.prototype.paste_cell_above = function () {
1212 if (this.clipboard !== null && this.paste_enabled) {
1212 if (this.clipboard !== null && this.paste_enabled) {
1213 var cell_data = this.clipboard;
1213 var cell_data = this.clipboard;
1214 var new_cell = this.insert_cell_above(cell_data.cell_type);
1214 var new_cell = this.insert_cell_above(cell_data.cell_type);
1215 new_cell.fromJSON(cell_data);
1215 new_cell.fromJSON(cell_data);
1216 new_cell.focus_cell();
1216 new_cell.focus_cell();
1217 }
1217 }
1218 };
1218 };
1219
1219
1220 /**
1220 /**
1221 * Paste a cell from the clipboard below the selected cell.
1221 * Paste a cell from the clipboard below the selected cell.
1222 *
1222 *
1223 * @method paste_cell_below
1223 * @method paste_cell_below
1224 */
1224 */
1225 Notebook.prototype.paste_cell_below = function () {
1225 Notebook.prototype.paste_cell_below = function () {
1226 if (this.clipboard !== null && this.paste_enabled) {
1226 if (this.clipboard !== null && this.paste_enabled) {
1227 var cell_data = this.clipboard;
1227 var cell_data = this.clipboard;
1228 var new_cell = this.insert_cell_below(cell_data.cell_type);
1228 var new_cell = this.insert_cell_below(cell_data.cell_type);
1229 new_cell.fromJSON(cell_data);
1229 new_cell.fromJSON(cell_data);
1230 new_cell.focus_cell();
1230 new_cell.focus_cell();
1231 }
1231 }
1232 };
1232 };
1233
1233
1234 // Split/merge
1234 // Split/merge
1235
1235
1236 /**
1236 /**
1237 * Split the selected cell into two, at the cursor.
1237 * Split the selected cell into two, at the cursor.
1238 *
1238 *
1239 * @method split_cell
1239 * @method split_cell
1240 */
1240 */
1241 Notebook.prototype.split_cell = function () {
1241 Notebook.prototype.split_cell = function () {
1242 var mdc = textcell.MarkdownCell;
1242 var mdc = textcell.MarkdownCell;
1243 var rc = textcell.RawCell;
1243 var rc = textcell.RawCell;
1244 var cell = this.get_selected_cell();
1244 var cell = this.get_selected_cell();
1245 if (cell.is_splittable()) {
1245 if (cell.is_splittable()) {
1246 var texta = cell.get_pre_cursor();
1246 var texta = cell.get_pre_cursor();
1247 var textb = cell.get_post_cursor();
1247 var textb = cell.get_post_cursor();
1248 cell.set_text(textb);
1248 cell.set_text(textb);
1249 var new_cell = this.insert_cell_above(cell.cell_type);
1249 var new_cell = this.insert_cell_above(cell.cell_type);
1250 // Unrender the new cell so we can call set_text.
1250 // Unrender the new cell so we can call set_text.
1251 new_cell.unrender();
1251 new_cell.unrender();
1252 new_cell.set_text(texta);
1252 new_cell.set_text(texta);
1253 }
1253 }
1254 };
1254 };
1255
1255
1256 /**
1256 /**
1257 * Combine the selected cell into the cell above it.
1257 * Combine the selected cell into the cell above it.
1258 *
1258 *
1259 * @method merge_cell_above
1259 * @method merge_cell_above
1260 */
1260 */
1261 Notebook.prototype.merge_cell_above = function () {
1261 Notebook.prototype.merge_cell_above = function () {
1262 var mdc = textcell.MarkdownCell;
1262 var mdc = textcell.MarkdownCell;
1263 var rc = textcell.RawCell;
1263 var rc = textcell.RawCell;
1264 var index = this.get_selected_index();
1264 var index = this.get_selected_index();
1265 var cell = this.get_cell(index);
1265 var cell = this.get_cell(index);
1266 var render = cell.rendered;
1266 var render = cell.rendered;
1267 if (!cell.is_mergeable()) {
1267 if (!cell.is_mergeable()) {
1268 return;
1268 return;
1269 }
1269 }
1270 if (index > 0) {
1270 if (index > 0) {
1271 var upper_cell = this.get_cell(index-1);
1271 var upper_cell = this.get_cell(index-1);
1272 if (!upper_cell.is_mergeable()) {
1272 if (!upper_cell.is_mergeable()) {
1273 return;
1273 return;
1274 }
1274 }
1275 var upper_text = upper_cell.get_text();
1275 var upper_text = upper_cell.get_text();
1276 var text = cell.get_text();
1276 var text = cell.get_text();
1277 if (cell instanceof codecell.CodeCell) {
1277 if (cell instanceof codecell.CodeCell) {
1278 cell.set_text(upper_text+'\n'+text);
1278 cell.set_text(upper_text+'\n'+text);
1279 } else {
1279 } else {
1280 cell.unrender(); // Must unrender before we set_text.
1280 cell.unrender(); // Must unrender before we set_text.
1281 cell.set_text(upper_text+'\n\n'+text);
1281 cell.set_text(upper_text+'\n\n'+text);
1282 if (render) {
1282 if (render) {
1283 // The rendered state of the final cell should match
1283 // The rendered state of the final cell should match
1284 // that of the original selected cell;
1284 // that of the original selected cell;
1285 cell.render();
1285 cell.render();
1286 }
1286 }
1287 }
1287 }
1288 this.delete_cell(index-1);
1288 this.delete_cell(index-1);
1289 this.select(this.find_cell_index(cell));
1289 this.select(this.find_cell_index(cell));
1290 }
1290 }
1291 };
1291 };
1292
1292
1293 /**
1293 /**
1294 * Combine the selected cell into the cell below it.
1294 * Combine the selected cell into the cell below it.
1295 *
1295 *
1296 * @method merge_cell_below
1296 * @method merge_cell_below
1297 */
1297 */
1298 Notebook.prototype.merge_cell_below = function () {
1298 Notebook.prototype.merge_cell_below = function () {
1299 var mdc = textcell.MarkdownCell;
1299 var mdc = textcell.MarkdownCell;
1300 var rc = textcell.RawCell;
1300 var rc = textcell.RawCell;
1301 var index = this.get_selected_index();
1301 var index = this.get_selected_index();
1302 var cell = this.get_cell(index);
1302 var cell = this.get_cell(index);
1303 var render = cell.rendered;
1303 var render = cell.rendered;
1304 if (!cell.is_mergeable()) {
1304 if (!cell.is_mergeable()) {
1305 return;
1305 return;
1306 }
1306 }
1307 if (index < this.ncells()-1) {
1307 if (index < this.ncells()-1) {
1308 var lower_cell = this.get_cell(index+1);
1308 var lower_cell = this.get_cell(index+1);
1309 if (!lower_cell.is_mergeable()) {
1309 if (!lower_cell.is_mergeable()) {
1310 return;
1310 return;
1311 }
1311 }
1312 var lower_text = lower_cell.get_text();
1312 var lower_text = lower_cell.get_text();
1313 var text = cell.get_text();
1313 var text = cell.get_text();
1314 if (cell instanceof codecell.CodeCell) {
1314 if (cell instanceof codecell.CodeCell) {
1315 cell.set_text(text+'\n'+lower_text);
1315 cell.set_text(text+'\n'+lower_text);
1316 } else {
1316 } else {
1317 cell.unrender(); // Must unrender before we set_text.
1317 cell.unrender(); // Must unrender before we set_text.
1318 cell.set_text(text+'\n\n'+lower_text);
1318 cell.set_text(text+'\n\n'+lower_text);
1319 if (render) {
1319 if (render) {
1320 // The rendered state of the final cell should match
1320 // The rendered state of the final cell should match
1321 // that of the original selected cell;
1321 // that of the original selected cell;
1322 cell.render();
1322 cell.render();
1323 }
1323 }
1324 }
1324 }
1325 this.delete_cell(index+1);
1325 this.delete_cell(index+1);
1326 this.select(this.find_cell_index(cell));
1326 this.select(this.find_cell_index(cell));
1327 }
1327 }
1328 };
1328 };
1329
1329
1330
1330
1331 // Cell collapsing and output clearing
1331 // Cell collapsing and output clearing
1332
1332
1333 /**
1333 /**
1334 * Hide a cell's output.
1334 * Hide a cell's output.
1335 *
1335 *
1336 * @method collapse_output
1336 * @method collapse_output
1337 * @param {Number} index A cell's numeric index
1337 * @param {Number} index A cell's numeric index
1338 */
1338 */
1339 Notebook.prototype.collapse_output = function (index) {
1339 Notebook.prototype.collapse_output = function (index) {
1340 var i = this.index_or_selected(index);
1340 var i = this.index_or_selected(index);
1341 var cell = this.get_cell(i);
1341 var cell = this.get_cell(i);
1342 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1342 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1343 cell.collapse_output();
1343 cell.collapse_output();
1344 this.set_dirty(true);
1344 this.set_dirty(true);
1345 }
1345 }
1346 };
1346 };
1347
1347
1348 /**
1348 /**
1349 * Hide each code cell's output area.
1349 * Hide each code cell's output area.
1350 *
1350 *
1351 * @method collapse_all_output
1351 * @method collapse_all_output
1352 */
1352 */
1353 Notebook.prototype.collapse_all_output = function () {
1353 Notebook.prototype.collapse_all_output = function () {
1354 $.map(this.get_cells(), function (cell, i) {
1354 $.map(this.get_cells(), function (cell, i) {
1355 if (cell instanceof codecell.CodeCell) {
1355 if (cell instanceof codecell.CodeCell) {
1356 cell.collapse_output();
1356 cell.collapse_output();
1357 }
1357 }
1358 });
1358 });
1359 // this should not be set if the `collapse` key is removed from nbformat
1359 // this should not be set if the `collapse` key is removed from nbformat
1360 this.set_dirty(true);
1360 this.set_dirty(true);
1361 };
1361 };
1362
1362
1363 /**
1363 /**
1364 * Show a cell's output.
1364 * Show a cell's output.
1365 *
1365 *
1366 * @method expand_output
1366 * @method expand_output
1367 * @param {Number} index A cell's numeric index
1367 * @param {Number} index A cell's numeric index
1368 */
1368 */
1369 Notebook.prototype.expand_output = function (index) {
1369 Notebook.prototype.expand_output = function (index) {
1370 var i = this.index_or_selected(index);
1370 var i = this.index_or_selected(index);
1371 var cell = this.get_cell(i);
1371 var cell = this.get_cell(i);
1372 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1372 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1373 cell.expand_output();
1373 cell.expand_output();
1374 this.set_dirty(true);
1374 this.set_dirty(true);
1375 }
1375 }
1376 };
1376 };
1377
1377
1378 /**
1378 /**
1379 * Expand each code cell's output area, and remove scrollbars.
1379 * Expand each code cell's output area, and remove scrollbars.
1380 *
1380 *
1381 * @method expand_all_output
1381 * @method expand_all_output
1382 */
1382 */
1383 Notebook.prototype.expand_all_output = function () {
1383 Notebook.prototype.expand_all_output = function () {
1384 $.map(this.get_cells(), function (cell, i) {
1384 $.map(this.get_cells(), function (cell, i) {
1385 if (cell instanceof codecell.CodeCell) {
1385 if (cell instanceof codecell.CodeCell) {
1386 cell.expand_output();
1386 cell.expand_output();
1387 }
1387 }
1388 });
1388 });
1389 // this should not be set if the `collapse` key is removed from nbformat
1389 // this should not be set if the `collapse` key is removed from nbformat
1390 this.set_dirty(true);
1390 this.set_dirty(true);
1391 };
1391 };
1392
1392
1393 /**
1393 /**
1394 * Clear the selected CodeCell's output area.
1394 * Clear the selected CodeCell's output area.
1395 *
1395 *
1396 * @method clear_output
1396 * @method clear_output
1397 * @param {Number} index A cell's numeric index
1397 * @param {Number} index A cell's numeric index
1398 */
1398 */
1399 Notebook.prototype.clear_output = function (index) {
1399 Notebook.prototype.clear_output = function (index) {
1400 var i = this.index_or_selected(index);
1400 var i = this.index_or_selected(index);
1401 var cell = this.get_cell(i);
1401 var cell = this.get_cell(i);
1402 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1402 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1403 cell.clear_output();
1403 cell.clear_output();
1404 this.set_dirty(true);
1404 this.set_dirty(true);
1405 }
1405 }
1406 };
1406 };
1407
1407
1408 /**
1408 /**
1409 * Clear each code cell's output area.
1409 * Clear each code cell's output area.
1410 *
1410 *
1411 * @method clear_all_output
1411 * @method clear_all_output
1412 */
1412 */
1413 Notebook.prototype.clear_all_output = function () {
1413 Notebook.prototype.clear_all_output = function () {
1414 $.map(this.get_cells(), function (cell, i) {
1414 $.map(this.get_cells(), function (cell, i) {
1415 if (cell instanceof codecell.CodeCell) {
1415 if (cell instanceof codecell.CodeCell) {
1416 cell.clear_output();
1416 cell.clear_output();
1417 }
1417 }
1418 });
1418 });
1419 this.set_dirty(true);
1419 this.set_dirty(true);
1420 };
1420 };
1421
1421
1422 /**
1422 /**
1423 * Scroll the selected CodeCell's output area.
1423 * Scroll the selected CodeCell's output area.
1424 *
1424 *
1425 * @method scroll_output
1425 * @method scroll_output
1426 * @param {Number} index A cell's numeric index
1426 * @param {Number} index A cell's numeric index
1427 */
1427 */
1428 Notebook.prototype.scroll_output = function (index) {
1428 Notebook.prototype.scroll_output = function (index) {
1429 var i = this.index_or_selected(index);
1429 var i = this.index_or_selected(index);
1430 var cell = this.get_cell(i);
1430 var cell = this.get_cell(i);
1431 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1431 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1432 cell.scroll_output();
1432 cell.scroll_output();
1433 this.set_dirty(true);
1433 this.set_dirty(true);
1434 }
1434 }
1435 };
1435 };
1436
1436
1437 /**
1437 /**
1438 * Expand each code cell's output area, and add a scrollbar for long output.
1438 * Expand each code cell's output area, and add a scrollbar for long output.
1439 *
1439 *
1440 * @method scroll_all_output
1440 * @method scroll_all_output
1441 */
1441 */
1442 Notebook.prototype.scroll_all_output = function () {
1442 Notebook.prototype.scroll_all_output = function () {
1443 $.map(this.get_cells(), function (cell, i) {
1443 $.map(this.get_cells(), function (cell, i) {
1444 if (cell instanceof codecell.CodeCell) {
1444 if (cell instanceof codecell.CodeCell) {
1445 cell.scroll_output();
1445 cell.scroll_output();
1446 }
1446 }
1447 });
1447 });
1448 // this should not be set if the `collapse` key is removed from nbformat
1448 // this should not be set if the `collapse` key is removed from nbformat
1449 this.set_dirty(true);
1449 this.set_dirty(true);
1450 };
1450 };
1451
1451
1452 /** Toggle whether a cell's output is collapsed or expanded.
1452 /** Toggle whether a cell's output is collapsed or expanded.
1453 *
1453 *
1454 * @method toggle_output
1454 * @method toggle_output
1455 * @param {Number} index A cell's numeric index
1455 * @param {Number} index A cell's numeric index
1456 */
1456 */
1457 Notebook.prototype.toggle_output = function (index) {
1457 Notebook.prototype.toggle_output = function (index) {
1458 var i = this.index_or_selected(index);
1458 var i = this.index_or_selected(index);
1459 var cell = this.get_cell(i);
1459 var cell = this.get_cell(i);
1460 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1460 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1461 cell.toggle_output();
1461 cell.toggle_output();
1462 this.set_dirty(true);
1462 this.set_dirty(true);
1463 }
1463 }
1464 };
1464 };
1465
1465
1466 /**
1466 /**
1467 * Hide/show the output of all cells.
1467 * Hide/show the output of all cells.
1468 *
1468 *
1469 * @method toggle_all_output
1469 * @method toggle_all_output
1470 */
1470 */
1471 Notebook.prototype.toggle_all_output = function () {
1471 Notebook.prototype.toggle_all_output = function () {
1472 $.map(this.get_cells(), function (cell, i) {
1472 $.map(this.get_cells(), function (cell, i) {
1473 if (cell instanceof codecell.CodeCell) {
1473 if (cell instanceof codecell.CodeCell) {
1474 cell.toggle_output();
1474 cell.toggle_output();
1475 }
1475 }
1476 });
1476 });
1477 // this should not be set if the `collapse` key is removed from nbformat
1477 // this should not be set if the `collapse` key is removed from nbformat
1478 this.set_dirty(true);
1478 this.set_dirty(true);
1479 };
1479 };
1480
1480
1481 /**
1481 /**
1482 * Toggle a scrollbar for long cell outputs.
1482 * Toggle a scrollbar for long cell outputs.
1483 *
1483 *
1484 * @method toggle_output_scroll
1484 * @method toggle_output_scroll
1485 * @param {Number} index A cell's numeric index
1485 * @param {Number} index A cell's numeric index
1486 */
1486 */
1487 Notebook.prototype.toggle_output_scroll = function (index) {
1487 Notebook.prototype.toggle_output_scroll = function (index) {
1488 var i = this.index_or_selected(index);
1488 var i = this.index_or_selected(index);
1489 var cell = this.get_cell(i);
1489 var cell = this.get_cell(i);
1490 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1490 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1491 cell.toggle_output_scroll();
1491 cell.toggle_output_scroll();
1492 this.set_dirty(true);
1492 this.set_dirty(true);
1493 }
1493 }
1494 };
1494 };
1495
1495
1496 /**
1496 /**
1497 * Toggle the scrolling of long output on all cells.
1497 * Toggle the scrolling of long output on all cells.
1498 *
1498 *
1499 * @method toggle_all_output_scrolling
1499 * @method toggle_all_output_scrolling
1500 */
1500 */
1501 Notebook.prototype.toggle_all_output_scroll = function () {
1501 Notebook.prototype.toggle_all_output_scroll = function () {
1502 $.map(this.get_cells(), function (cell, i) {
1502 $.map(this.get_cells(), function (cell, i) {
1503 if (cell instanceof codecell.CodeCell) {
1503 if (cell instanceof codecell.CodeCell) {
1504 cell.toggle_output_scroll();
1504 cell.toggle_output_scroll();
1505 }
1505 }
1506 });
1506 });
1507 // this should not be set if the `collapse` key is removed from nbformat
1507 // this should not be set if the `collapse` key is removed from nbformat
1508 this.set_dirty(true);
1508 this.set_dirty(true);
1509 };
1509 };
1510
1510
1511 // Other cell functions: line numbers, ...
1511 // Other cell functions: line numbers, ...
1512
1512
1513 /**
1513 /**
1514 * Toggle line numbers in the selected cell's input area.
1514 * Toggle line numbers in the selected cell's input area.
1515 *
1515 *
1516 * @method cell_toggle_line_numbers
1516 * @method cell_toggle_line_numbers
1517 */
1517 */
1518 Notebook.prototype.cell_toggle_line_numbers = function() {
1518 Notebook.prototype.cell_toggle_line_numbers = function() {
1519 this.get_selected_cell().toggle_line_numbers();
1519 this.get_selected_cell().toggle_line_numbers();
1520 };
1520 };
1521
1521
1522 /**
1522 /**
1523 * Set the codemirror mode for all code cells, including the default for
1523 * Set the codemirror mode for all code cells, including the default for
1524 * new code cells.
1524 * new code cells.
1525 *
1525 *
1526 * @method set_codemirror_mode
1526 * @method set_codemirror_mode
1527 */
1527 */
1528 Notebook.prototype.set_codemirror_mode = function(newmode){
1528 Notebook.prototype.set_codemirror_mode = function(newmode){
1529 if (newmode === this.codemirror_mode) {
1529 if (newmode === this.codemirror_mode) {
1530 return;
1530 return;
1531 }
1531 }
1532 this.codemirror_mode = newmode;
1532 this.codemirror_mode = newmode;
1533 codecell.CodeCell.options_default.cm_config.mode = newmode;
1533 codecell.CodeCell.options_default.cm_config.mode = newmode;
1534 modename = newmode.mode || newmode.name || newmode;
1534 modename = newmode.mode || newmode.name || newmode;
1535
1535
1536 that = this;
1536 that = this;
1537 utils.requireCodeMirrorMode(modename, function () {
1537 utils.requireCodeMirrorMode(modename, function () {
1538 $.map(that.get_cells(), function(cell, i) {
1538 $.map(that.get_cells(), function(cell, i) {
1539 if (cell.cell_type === 'code'){
1539 if (cell.cell_type === 'code'){
1540 cell.code_mirror.setOption('mode', newmode);
1540 cell.code_mirror.setOption('mode', newmode);
1541 // This is currently redundant, because cm_config ends up as
1541 // This is currently redundant, because cm_config ends up as
1542 // codemirror's own .options object, but I don't want to
1542 // codemirror's own .options object, but I don't want to
1543 // rely on that.
1543 // rely on that.
1544 cell.cm_config.mode = newmode;
1544 cell.cm_config.mode = newmode;
1545 }
1545 }
1546 });
1546 });
1547 });
1547 });
1548 };
1548 };
1549
1549
1550 // Session related things
1550 // Session related things
1551
1551
1552 /**
1552 /**
1553 * Start a new session and set it on each code cell.
1553 * Start a new session and set it on each code cell.
1554 *
1554 *
1555 * @method start_session
1555 * @method start_session
1556 */
1556 */
1557 Notebook.prototype.start_session = function (kernel_name) {
1557 Notebook.prototype.start_session = function (kernel_name) {
1558 var that = this;
1558 var that = this;
1559 if (this._session_starting) {
1559 if (this._session_starting) {
1560 throw new session.SessionAlreadyStarting();
1560 throw new session.SessionAlreadyStarting();
1561 }
1561 }
1562 this._session_starting = true;
1562 this._session_starting = true;
1563
1563
1564 var options = {
1564 var options = {
1565 base_url: this.base_url,
1565 base_url: this.base_url,
1566 ws_url: this.ws_url,
1566 ws_url: this.ws_url,
1567 notebook_path: this.notebook_path,
1567 notebook_path: this.notebook_path,
1568 notebook_name: this.notebook_name,
1568 notebook_name: this.notebook_name,
1569 kernel_name: kernel_name,
1569 kernel_name: kernel_name,
1570 notebook: this
1570 notebook: this
1571 };
1571 };
1572
1572
1573 var success = $.proxy(this._session_started, this);
1573 var success = $.proxy(this._session_started, this);
1574 var failure = $.proxy(this._session_start_failed, this);
1574 var failure = $.proxy(this._session_start_failed, this);
1575
1575
1576 if (this.session !== null) {
1576 if (this.session !== null) {
1577 this.session.restart(options, success, failure);
1577 this.session.restart(options, success, failure);
1578 } else {
1578 } else {
1579 this.session = new session.Session(options);
1579 this.session = new session.Session(options);
1580 this.session.start(success, failure);
1580 this.session.start(success, failure);
1581 }
1581 }
1582 };
1582 };
1583
1583
1584
1584
1585 /**
1585 /**
1586 * Once a session is started, link the code cells to the kernel and pass the
1586 * Once a session is started, link the code cells to the kernel and pass the
1587 * comm manager to the widget manager
1587 * comm manager to the widget manager
1588 *
1588 *
1589 */
1589 */
1590 Notebook.prototype._session_started = function (){
1590 Notebook.prototype._session_started = function (){
1591 this._session_starting = false;
1591 this._session_starting = false;
1592 this.kernel = this.session.kernel;
1592 this.kernel = this.session.kernel;
1593 var ncells = this.ncells();
1593 var ncells = this.ncells();
1594 for (var i=0; i<ncells; i++) {
1594 for (var i=0; i<ncells; i++) {
1595 var cell = this.get_cell(i);
1595 var cell = this.get_cell(i);
1596 if (cell instanceof codecell.CodeCell) {
1596 if (cell instanceof codecell.CodeCell) {
1597 cell.set_kernel(this.session.kernel);
1597 cell.set_kernel(this.session.kernel);
1598 }
1598 }
1599 }
1599 }
1600 };
1600 };
1601 Notebook.prototype._session_start_failed = function (jqxhr, status, error){
1601 Notebook.prototype._session_start_failed = function (jqxhr, status, error){
1602 this._session_starting = false;
1602 this._session_starting = false;
1603 utils.log_ajax_error(jqxhr, status, error);
1603 utils.log_ajax_error(jqxhr, status, error);
1604 };
1604 };
1605
1605
1606 /**
1606 /**
1607 * Prompt the user to restart the IPython kernel.
1607 * Prompt the user to restart the IPython kernel.
1608 *
1608 *
1609 * @method restart_kernel
1609 * @method restart_kernel
1610 */
1610 */
1611 Notebook.prototype.restart_kernel = function () {
1611 Notebook.prototype.restart_kernel = function () {
1612 var that = this;
1612 var that = this;
1613 dialog.modal({
1613 dialog.modal({
1614 notebook: this,
1614 notebook: this,
1615 keyboard_manager: this.keyboard_manager,
1615 keyboard_manager: this.keyboard_manager,
1616 title : "Restart kernel or continue running?",
1616 title : "Restart kernel or continue running?",
1617 body : $("<p/>").text(
1617 body : $("<p/>").text(
1618 'Do you want to restart the current kernel? You will lose all variables defined in it.'
1618 'Do you want to restart the current kernel? You will lose all variables defined in it.'
1619 ),
1619 ),
1620 buttons : {
1620 buttons : {
1621 "Continue running" : {},
1621 "Continue running" : {},
1622 "Restart" : {
1622 "Restart" : {
1623 "class" : "btn-danger",
1623 "class" : "btn-danger",
1624 "click" : function() {
1624 "click" : function() {
1625 that.kernel.restart();
1625 that.kernel.restart();
1626 }
1626 }
1627 }
1627 }
1628 }
1628 }
1629 });
1629 });
1630 };
1630 };
1631
1631
1632 /**
1632 /**
1633 * Execute or render cell outputs and go into command mode.
1633 * Execute or render cell outputs and go into command mode.
1634 *
1634 *
1635 * @method execute_cell
1635 * @method execute_cell
1636 */
1636 */
1637 Notebook.prototype.execute_cell = function () {
1637 Notebook.prototype.execute_cell = function () {
1638 // mode = shift, ctrl, alt
1638 // mode = shift, ctrl, alt
1639 var cell = this.get_selected_cell();
1639 var cell = this.get_selected_cell();
1640 var cell_index = this.find_cell_index(cell);
1640 var cell_index = this.find_cell_index(cell);
1641
1641
1642 cell.execute();
1642 cell.execute();
1643 this.command_mode();
1643 this.command_mode();
1644 this.set_dirty(true);
1644 this.set_dirty(true);
1645 };
1645 };
1646
1646
1647 /**
1647 /**
1648 * Execute or render cell outputs and insert a new cell below.
1648 * Execute or render cell outputs and insert a new cell below.
1649 *
1649 *
1650 * @method execute_cell_and_insert_below
1650 * @method execute_cell_and_insert_below
1651 */
1651 */
1652 Notebook.prototype.execute_cell_and_insert_below = function () {
1652 Notebook.prototype.execute_cell_and_insert_below = function () {
1653 var cell = this.get_selected_cell();
1653 var cell = this.get_selected_cell();
1654 var cell_index = this.find_cell_index(cell);
1654 var cell_index = this.find_cell_index(cell);
1655
1655
1656 cell.execute();
1656 cell.execute();
1657
1657
1658 // If we are at the end always insert a new cell and return
1658 // If we are at the end always insert a new cell and return
1659 if (cell_index === (this.ncells()-1)) {
1659 if (cell_index === (this.ncells()-1)) {
1660 this.command_mode();
1660 this.command_mode();
1661 this.insert_cell_below();
1661 this.insert_cell_below();
1662 this.select(cell_index+1);
1662 this.select(cell_index+1);
1663 this.edit_mode();
1663 this.edit_mode();
1664 this.scroll_to_bottom();
1664 this.scroll_to_bottom();
1665 this.set_dirty(true);
1665 this.set_dirty(true);
1666 return;
1666 return;
1667 }
1667 }
1668
1668
1669 this.command_mode();
1669 this.command_mode();
1670 this.insert_cell_below();
1670 this.insert_cell_below();
1671 this.select(cell_index+1);
1671 this.select(cell_index+1);
1672 this.edit_mode();
1672 this.edit_mode();
1673 this.set_dirty(true);
1673 this.set_dirty(true);
1674 };
1674 };
1675
1675
1676 /**
1676 /**
1677 * Execute or render cell outputs and select the next cell.
1677 * Execute or render cell outputs and select the next cell.
1678 *
1678 *
1679 * @method execute_cell_and_select_below
1679 * @method execute_cell_and_select_below
1680 */
1680 */
1681 Notebook.prototype.execute_cell_and_select_below = function () {
1681 Notebook.prototype.execute_cell_and_select_below = function () {
1682
1682
1683 var cell = this.get_selected_cell();
1683 var cell = this.get_selected_cell();
1684 var cell_index = this.find_cell_index(cell);
1684 var cell_index = this.find_cell_index(cell);
1685
1685
1686 cell.execute();
1686 cell.execute();
1687
1687
1688 // If we are at the end always insert a new cell and return
1688 // If we are at the end always insert a new cell and return
1689 if (cell_index === (this.ncells()-1)) {
1689 if (cell_index === (this.ncells()-1)) {
1690 this.command_mode();
1690 this.command_mode();
1691 this.insert_cell_below();
1691 this.insert_cell_below();
1692 this.select(cell_index+1);
1692 this.select(cell_index+1);
1693 this.edit_mode();
1693 this.edit_mode();
1694 this.scroll_to_bottom();
1694 this.scroll_to_bottom();
1695 this.set_dirty(true);
1695 this.set_dirty(true);
1696 return;
1696 return;
1697 }
1697 }
1698
1698
1699 this.command_mode();
1699 this.command_mode();
1700 this.select(cell_index+1);
1700 this.select(cell_index+1);
1701 this.focus_cell();
1701 this.focus_cell();
1702 this.set_dirty(true);
1702 this.set_dirty(true);
1703 };
1703 };
1704
1704
1705 /**
1705 /**
1706 * Execute all cells below the selected cell.
1706 * Execute all cells below the selected cell.
1707 *
1707 *
1708 * @method execute_cells_below
1708 * @method execute_cells_below
1709 */
1709 */
1710 Notebook.prototype.execute_cells_below = function () {
1710 Notebook.prototype.execute_cells_below = function () {
1711 this.execute_cell_range(this.get_selected_index(), this.ncells());
1711 this.execute_cell_range(this.get_selected_index(), this.ncells());
1712 this.scroll_to_bottom();
1712 this.scroll_to_bottom();
1713 };
1713 };
1714
1714
1715 /**
1715 /**
1716 * Execute all cells above the selected cell.
1716 * Execute all cells above the selected cell.
1717 *
1717 *
1718 * @method execute_cells_above
1718 * @method execute_cells_above
1719 */
1719 */
1720 Notebook.prototype.execute_cells_above = function () {
1720 Notebook.prototype.execute_cells_above = function () {
1721 this.execute_cell_range(0, this.get_selected_index());
1721 this.execute_cell_range(0, this.get_selected_index());
1722 };
1722 };
1723
1723
1724 /**
1724 /**
1725 * Execute all cells.
1725 * Execute all cells.
1726 *
1726 *
1727 * @method execute_all_cells
1727 * @method execute_all_cells
1728 */
1728 */
1729 Notebook.prototype.execute_all_cells = function () {
1729 Notebook.prototype.execute_all_cells = function () {
1730 this.execute_cell_range(0, this.ncells());
1730 this.execute_cell_range(0, this.ncells());
1731 this.scroll_to_bottom();
1731 this.scroll_to_bottom();
1732 };
1732 };
1733
1733
1734 /**
1734 /**
1735 * Execute a contiguous range of cells.
1735 * Execute a contiguous range of cells.
1736 *
1736 *
1737 * @method execute_cell_range
1737 * @method execute_cell_range
1738 * @param {Number} start Index of the first cell to execute (inclusive)
1738 * @param {Number} start Index of the first cell to execute (inclusive)
1739 * @param {Number} end Index of the last cell to execute (exclusive)
1739 * @param {Number} end Index of the last cell to execute (exclusive)
1740 */
1740 */
1741 Notebook.prototype.execute_cell_range = function (start, end) {
1741 Notebook.prototype.execute_cell_range = function (start, end) {
1742 this.command_mode();
1742 this.command_mode();
1743 for (var i=start; i<end; i++) {
1743 for (var i=start; i<end; i++) {
1744 this.select(i);
1744 this.select(i);
1745 this.execute_cell();
1745 this.execute_cell();
1746 }
1746 }
1747 };
1747 };
1748
1748
1749 // Persistance and loading
1749 // Persistance and loading
1750
1750
1751 /**
1751 /**
1752 * Getter method for this notebook's name.
1752 * Getter method for this notebook's name.
1753 *
1753 *
1754 * @method get_notebook_name
1754 * @method get_notebook_name
1755 * @return {String} This notebook's name (excluding file extension)
1755 * @return {String} This notebook's name (excluding file extension)
1756 */
1756 */
1757 Notebook.prototype.get_notebook_name = function () {
1757 Notebook.prototype.get_notebook_name = function () {
1758 var nbname = this.notebook_name.substring(0,this.notebook_name.length-6);
1758 var nbname = this.notebook_name.substring(0,this.notebook_name.length-6);
1759 return nbname;
1759 return nbname;
1760 };
1760 };
1761
1761
1762 /**
1762 /**
1763 * Setter method for this notebook's name.
1763 * Setter method for this notebook's name.
1764 *
1764 *
1765 * @method set_notebook_name
1765 * @method set_notebook_name
1766 * @param {String} name A new name for this notebook
1766 * @param {String} name A new name for this notebook
1767 */
1767 */
1768 Notebook.prototype.set_notebook_name = function (name) {
1768 Notebook.prototype.set_notebook_name = function (name) {
1769 this.notebook_name = name;
1769 this.notebook_name = name;
1770 };
1770 };
1771
1771
1772 /**
1772 /**
1773 * Check that a notebook's name is valid.
1773 * Check that a notebook's name is valid.
1774 *
1774 *
1775 * @method test_notebook_name
1775 * @method test_notebook_name
1776 * @param {String} nbname A name for this notebook
1776 * @param {String} nbname A name for this notebook
1777 * @return {Boolean} True if the name is valid, false if invalid
1777 * @return {Boolean} True if the name is valid, false if invalid
1778 */
1778 */
1779 Notebook.prototype.test_notebook_name = function (nbname) {
1779 Notebook.prototype.test_notebook_name = function (nbname) {
1780 nbname = nbname || '';
1780 nbname = nbname || '';
1781 if (nbname.length>0 && !this.notebook_name_blacklist_re.test(nbname)) {
1781 if (nbname.length>0 && !this.notebook_name_blacklist_re.test(nbname)) {
1782 return true;
1782 return true;
1783 } else {
1783 } else {
1784 return false;
1784 return false;
1785 }
1785 }
1786 };
1786 };
1787
1787
1788 /**
1788 /**
1789 * Load a notebook from JSON (.ipynb).
1789 * Load a notebook from JSON (.ipynb).
1790 *
1790 *
1791 * @method fromJSON
1791 * @method fromJSON
1792 * @param {Object} data JSON representation of a notebook
1792 * @param {Object} data JSON representation of a notebook
1793 */
1793 */
1794 Notebook.prototype.fromJSON = function (data) {
1794 Notebook.prototype.fromJSON = function (data) {
1795
1795
1796 var content = data.content;
1796 var content = data.content;
1797 var ncells = this.ncells();
1797 var ncells = this.ncells();
1798 var i;
1798 var i;
1799 for (i=0; i<ncells; i++) {
1799 for (i=0; i<ncells; i++) {
1800 // Always delete cell 0 as they get renumbered as they are deleted.
1800 // Always delete cell 0 as they get renumbered as they are deleted.
1801 this.delete_cell(0);
1801 this.delete_cell(0);
1802 }
1802 }
1803 // Save the metadata and name.
1803 // Save the metadata and name.
1804 this.metadata = content.metadata;
1804 this.metadata = content.metadata;
1805 this.notebook_name = data.name;
1805 this.notebook_name = data.name;
1806 var trusted = true;
1806 var trusted = true;
1807
1807
1808 // Trigger an event changing the kernel spec - this will set the default
1808 // Trigger an event changing the kernel spec - this will set the default
1809 // codemirror mode
1809 // codemirror mode
1810 if (this.metadata.kernelspec !== undefined) {
1810 if (this.metadata.kernelspec !== undefined) {
1811 this.events.trigger('spec_changed.Kernel', this.metadata.kernelspec);
1811 this.events.trigger('spec_changed.Kernel', this.metadata.kernelspec);
1812 }
1812 }
1813
1813
1814 // Set the codemirror mode from language_info metadata
1814 // Set the codemirror mode from language_info metadata
1815 if (this.metadata.language_info !== undefined) {
1815 if (this.metadata.language_info !== undefined) {
1816 var langinfo = this.metadata.language_info;
1816 var langinfo = this.metadata.language_info;
1817 // Mode 'null' should be plain, unhighlighted text.
1817 // Mode 'null' should be plain, unhighlighted text.
1818 var cm_mode = langinfo.codemirror_mode || langinfo.language || 'null'
1818 var cm_mode = langinfo.codemirror_mode || langinfo.language || 'null'
1819 this.set_codemirror_mode(cm_mode);
1819 this.set_codemirror_mode(cm_mode);
1820 }
1820 }
1821
1821
1822 var new_cells = content.cells;
1822 var new_cells = content.cells;
1823 ncells = new_cells.length;
1823 ncells = new_cells.length;
1824 var cell_data = null;
1824 var cell_data = null;
1825 var new_cell = null;
1825 var new_cell = null;
1826 for (i=0; i<ncells; i++) {
1826 for (i=0; i<ncells; i++) {
1827 cell_data = new_cells[i];
1827 cell_data = new_cells[i];
1828 new_cell = this.insert_cell_at_index(cell_data.cell_type, i);
1828 new_cell = this.insert_cell_at_index(cell_data.cell_type, i);
1829 new_cell.fromJSON(cell_data);
1829 new_cell.fromJSON(cell_data);
1830 if (new_cell.cell_type == 'code' && !new_cell.output_area.trusted) {
1830 if (new_cell.cell_type == 'code' && !new_cell.output_area.trusted) {
1831 trusted = false;
1831 trusted = false;
1832 }
1832 }
1833 }
1833 }
1834 if (trusted !== this.trusted) {
1834 if (trusted !== this.trusted) {
1835 this.trusted = trusted;
1835 this.trusted = trusted;
1836 this.events.trigger("trust_changed.Notebook", trusted);
1836 this.events.trigger("trust_changed.Notebook", trusted);
1837 }
1837 }
1838 };
1838 };
1839
1839
1840 /**
1840 /**
1841 * Dump this notebook into a JSON-friendly object.
1841 * Dump this notebook into a JSON-friendly object.
1842 *
1842 *
1843 * @method toJSON
1843 * @method toJSON
1844 * @return {Object} A JSON-friendly representation of this notebook.
1844 * @return {Object} A JSON-friendly representation of this notebook.
1845 */
1845 */
1846 Notebook.prototype.toJSON = function () {
1846 Notebook.prototype.toJSON = function () {
1847 // remove the conversion indicator, which only belongs in-memory
1847 // remove the conversion indicator, which only belongs in-memory
1848 delete this.metadata.orig_nbformat;
1848 delete this.metadata.orig_nbformat;
1849 delete this.metadata.orig_nbformat_minor;
1849 delete this.metadata.orig_nbformat_minor;
1850
1850
1851 var cells = this.get_cells();
1851 var cells = this.get_cells();
1852 var ncells = cells.length;
1852 var ncells = cells.length;
1853 var cell_array = new Array(ncells);
1853 var cell_array = new Array(ncells);
1854 var trusted = true;
1854 var trusted = true;
1855 for (var i=0; i<ncells; i++) {
1855 for (var i=0; i<ncells; i++) {
1856 var cell = cells[i];
1856 var cell = cells[i];
1857 if (cell.cell_type == 'code' && !cell.output_area.trusted) {
1857 if (cell.cell_type == 'code' && !cell.output_area.trusted) {
1858 trusted = false;
1858 trusted = false;
1859 }
1859 }
1860 cell_array[i] = cell.toJSON();
1860 cell_array[i] = cell.toJSON();
1861 }
1861 }
1862 var data = {
1862 var data = {
1863 cells: cell_array,
1863 cells: cell_array,
1864 metadata : this.metadata
1864 metadata : this.metadata
1865 };
1865 };
1866 if (trusted != this.trusted) {
1866 if (trusted != this.trusted) {
1867 this.trusted = trusted;
1867 this.trusted = trusted;
1868 this.events.trigger("trust_changed.Notebook", trusted);
1868 this.events.trigger("trust_changed.Notebook", trusted);
1869 }
1869 }
1870 return data;
1870 return data;
1871 };
1871 };
1872
1872
1873 /**
1873 /**
1874 * Start an autosave timer, for periodically saving the notebook.
1874 * Start an autosave timer, for periodically saving the notebook.
1875 *
1875 *
1876 * @method set_autosave_interval
1876 * @method set_autosave_interval
1877 * @param {Integer} interval the autosave interval in milliseconds
1877 * @param {Integer} interval the autosave interval in milliseconds
1878 */
1878 */
1879 Notebook.prototype.set_autosave_interval = function (interval) {
1879 Notebook.prototype.set_autosave_interval = function (interval) {
1880 var that = this;
1880 var that = this;
1881 // clear previous interval, so we don't get simultaneous timers
1881 // clear previous interval, so we don't get simultaneous timers
1882 if (this.autosave_timer) {
1882 if (this.autosave_timer) {
1883 clearInterval(this.autosave_timer);
1883 clearInterval(this.autosave_timer);
1884 }
1884 }
1885
1885
1886 this.autosave_interval = this.minimum_autosave_interval = interval;
1886 this.autosave_interval = this.minimum_autosave_interval = interval;
1887 if (interval) {
1887 if (interval) {
1888 this.autosave_timer = setInterval(function() {
1888 this.autosave_timer = setInterval(function() {
1889 if (that.dirty) {
1889 if (that.dirty) {
1890 that.save_notebook();
1890 that.save_notebook();
1891 }
1891 }
1892 }, interval);
1892 }, interval);
1893 this.events.trigger("autosave_enabled.Notebook", interval);
1893 this.events.trigger("autosave_enabled.Notebook", interval);
1894 } else {
1894 } else {
1895 this.autosave_timer = null;
1895 this.autosave_timer = null;
1896 this.events.trigger("autosave_disabled.Notebook");
1896 this.events.trigger("autosave_disabled.Notebook");
1897 }
1897 }
1898 };
1898 };
1899
1899
1900 /**
1900 /**
1901 * Save this notebook on the server. This becomes a notebook instance's
1901 * Save this notebook on the server. This becomes a notebook instance's
1902 * .save_notebook method *after* the entire notebook has been loaded.
1902 * .save_notebook method *after* the entire notebook has been loaded.
1903 *
1903 *
1904 * @method save_notebook
1904 * @method save_notebook
1905 */
1905 */
1906 Notebook.prototype.save_notebook = function (extra_settings) {
1906 Notebook.prototype.save_notebook = function (extra_settings) {
1907 this.content_manager.save_notebook(this, extra_settings);
1907 this.content_manager.save_notebook(this, extra_settings);
1908 };
1908 };
1909
1909
1910 /**
1910 /**
1911 * Success callback for saving a notebook.
1911 * Success callback for saving a notebook.
1912 *
1912 *
1913 * @method save_notebook_success
1913 * @method save_notebook_success
1914 * @param {Integer} start the time when the save request started
1914 * @param {Integer} start the time when the save request started
1915 * @param {Object} data JSON representation of a notebook
1915 * @param {Object} data JSON representation of a notebook
1916 * @param {String} status Description of response status
1916 * @param {String} status Description of response status
1917 * @param {jqXHR} xhr jQuery Ajax object
1917 * @param {jqXHR} xhr jQuery Ajax object
1918 */
1918 */
1919 Notebook.prototype.save_notebook_success = function (start, data, status, xhr) {
1919 Notebook.prototype.save_notebook_success = function (start, data, status, xhr) {
1920 this.set_dirty(false);
1920 this.set_dirty(false);
1921 if (data.message) {
1921 if (data.message) {
1922 // save succeeded, but validation failed.
1922 // save succeeded, but validation failed.
1923 var body = $("<div>");
1923 var body = $("<div>");
1924 var title = "Notebook validation failed";
1924 var title = "Notebook validation failed";
1925
1925
1926 body.append($("<p>").text(
1926 body.append($("<p>").text(
1927 "The save operation succeeded," +
1927 "The save operation succeeded," +
1928 " but the notebook does not appear to be valid." +
1928 " but the notebook does not appear to be valid." +
1929 " The validation error was:"
1929 " The validation error was:"
1930 )).append($("<div>").addClass("validation-error").append(
1930 )).append($("<div>").addClass("validation-error").append(
1931 $("<pre>").text(data.message)
1931 $("<pre>").text(data.message)
1932 ));
1932 ));
1933 dialog.modal({
1933 dialog.modal({
1934 notebook: this,
1934 notebook: this,
1935 keyboard_manager: this.keyboard_manager,
1935 keyboard_manager: this.keyboard_manager,
1936 title: title,
1936 title: title,
1937 body: body,
1937 body: body,
1938 buttons : {
1938 buttons : {
1939 OK : {
1939 OK : {
1940 "class" : "btn-primary"
1940 "class" : "btn-primary"
1941 }
1941 }
1942 }
1942 }
1943 });
1943 });
1944 }
1944 }
1945 this.events.trigger('notebook_saved.Notebook');
1945 this.events.trigger('notebook_saved.Notebook');
1946 this._update_autosave_interval(start);
1946 this._update_autosave_interval(start);
1947 if (this._checkpoint_after_save) {
1947 if (this._checkpoint_after_save) {
1948 this.create_checkpoint();
1948 this.create_checkpoint();
1949 this._checkpoint_after_save = false;
1949 this._checkpoint_after_save = false;
1950 }
1950 }
1951 };
1951 };
1952
1952
1953 /**
1953 /**
1954 * update the autosave interval based on how long the last save took
1954 * update the autosave interval based on how long the last save took
1955 *
1955 *
1956 * @method _update_autosave_interval
1956 * @method _update_autosave_interval
1957 * @param {Integer} timestamp when the save request started
1957 * @param {Integer} timestamp when the save request started
1958 */
1958 */
1959 Notebook.prototype._update_autosave_interval = function (start) {
1959 Notebook.prototype._update_autosave_interval = function (start) {
1960 var duration = (new Date().getTime() - start);
1960 var duration = (new Date().getTime() - start);
1961 if (this.autosave_interval) {
1961 if (this.autosave_interval) {
1962 // new save interval: higher of 10x save duration or parameter (default 30 seconds)
1962 // new save interval: higher of 10x save duration or parameter (default 30 seconds)
1963 var interval = Math.max(10 * duration, this.minimum_autosave_interval);
1963 var interval = Math.max(10 * duration, this.minimum_autosave_interval);
1964 // round to 10 seconds, otherwise we will be setting a new interval too often
1964 // round to 10 seconds, otherwise we will be setting a new interval too often
1965 interval = 10000 * Math.round(interval / 10000);
1965 interval = 10000 * Math.round(interval / 10000);
1966 // set new interval, if it's changed
1966 // set new interval, if it's changed
1967 if (interval != this.autosave_interval) {
1967 if (interval != this.autosave_interval) {
1968 this.set_autosave_interval(interval);
1968 this.set_autosave_interval(interval);
1969 }
1969 }
1970 }
1970 }
1971 };
1971 };
1972
1972
1973 /**
1973 /**
1974 * Failure callback for saving a notebook.
1974 * Failure callback for saving a notebook.
1975 *
1975 *
1976 * @method save_notebook_error
1976 * @method save_notebook_error
1977 * @param {jqXHR} xhr jQuery Ajax object
1977 * @param {jqXHR} xhr jQuery Ajax object
1978 * @param {String} status Description of response status
1978 * @param {String} status Description of response status
1979 * @param {String} error HTTP error message
1979 * @param {String} error HTTP error message
1980 */
1980 */
1981 Notebook.prototype.save_notebook_error = function (xhr, status, error) {
1981 Notebook.prototype.save_notebook_error = function (xhr, status, error) {
1982 this.events.trigger('notebook_save_failed.Notebook', [xhr, status, error]);
1982 this.events.trigger('notebook_save_failed.Notebook', [xhr, status, error]);
1983 };
1983 };
1984
1984
1985 /**
1985 /**
1986 * Explicitly trust the output of this notebook.
1986 * Explicitly trust the output of this notebook.
1987 *
1987 *
1988 * @method trust_notebook
1988 * @method trust_notebook
1989 */
1989 */
1990 Notebook.prototype.trust_notebook = function (extra_settings) {
1990 Notebook.prototype.trust_notebook = function (extra_settings) {
1991 var body = $("<div>").append($("<p>")
1991 var body = $("<div>").append($("<p>")
1992 .text("A trusted IPython notebook may execute hidden malicious code ")
1992 .text("A trusted IPython notebook may execute hidden malicious code ")
1993 .append($("<strong>")
1993 .append($("<strong>")
1994 .append(
1994 .append(
1995 $("<em>").text("when you open it")
1995 $("<em>").text("when you open it")
1996 )
1996 )
1997 ).append(".").append(
1997 ).append(".").append(
1998 " Selecting trust will immediately reload this notebook in a trusted state."
1998 " Selecting trust will immediately reload this notebook in a trusted state."
1999 ).append(
1999 ).append(
2000 " For more information, see the "
2000 " For more information, see the "
2001 ).append($("<a>").attr("href", "http://ipython.org/ipython-doc/2/notebook/security.html")
2001 ).append($("<a>").attr("href", "http://ipython.org/ipython-doc/2/notebook/security.html")
2002 .text("IPython security documentation")
2002 .text("IPython security documentation")
2003 ).append(".")
2003 ).append(".")
2004 );
2004 );
2005
2005
2006 var nb = this;
2006 var nb = this;
2007 dialog.modal({
2007 dialog.modal({
2008 notebook: this,
2008 notebook: this,
2009 keyboard_manager: this.keyboard_manager,
2009 keyboard_manager: this.keyboard_manager,
2010 title: "Trust this notebook?",
2010 title: "Trust this notebook?",
2011 body: body,
2011 body: body,
2012
2012
2013 buttons: {
2013 buttons: {
2014 Cancel : {},
2014 Cancel : {},
2015 Trust : {
2015 Trust : {
2016 class : "btn-danger",
2016 class : "btn-danger",
2017 click : function () {
2017 click : function () {
2018 var cells = nb.get_cells();
2018 var cells = nb.get_cells();
2019 for (var i = 0; i < cells.length; i++) {
2019 for (var i = 0; i < cells.length; i++) {
2020 var cell = cells[i];
2020 var cell = cells[i];
2021 if (cell.cell_type == 'code') {
2021 if (cell.cell_type == 'code') {
2022 cell.output_area.trusted = true;
2022 cell.output_area.trusted = true;
2023 }
2023 }
2024 }
2024 }
2025 nb.events.on('notebook_saved.Notebook', function () {
2025 nb.events.on('notebook_saved.Notebook', function () {
2026 window.location.reload();
2026 window.location.reload();
2027 });
2027 });
2028 nb.save_notebook();
2028 nb.save_notebook();
2029 }
2029 }
2030 }
2030 }
2031 }
2031 }
2032 });
2032 });
2033 };
2033 };
2034
2034
2035 Notebook.prototype.copy_notebook = function(){
2035 Notebook.prototype.copy_notebook = function(){
2036 var path = this.notebook_path;
2036 var path = this.notebook_path;
2037 var base_url = this.base_url;
2037 var base_url = this.base_url;
2038 var settings = {
2038 var settings = {
2039 processData : false,
2039 processData : false,
2040 cache : false,
2040 cache : false,
2041 type : "POST",
2041 type : "POST",
2042 dataType : "json",
2042 dataType : "json",
2043 data : JSON.stringify({copy_from : this.notebook_name}),
2043 data : JSON.stringify({copy_from : this.notebook_name}),
2044 async : false,
2044 async : false,
2045 success : function (data, status, xhr) {
2045 success : function (data, status, xhr) {
2046 window.open(utils.url_join_encode(
2046 window.open(utils.url_join_encode(
2047 base_url,
2047 base_url,
2048 'notebooks',
2048 'notebooks',
2049 data.path,
2049 data.path,
2050 data.name
2050 data.name
2051 ), '_blank');
2051 ), '_blank');
2052 },
2052 },
2053 error : utils.log_ajax_error,
2053 error : utils.log_ajax_error,
2054 };
2054 };
2055 var url = utils.url_join_encode(
2055 var url = utils.url_join_encode(
2056 base_url,
2056 base_url,
2057 'api/contents',
2057 'api/contents',
2058 path
2058 path
2059 );
2059 );
2060 $.ajax(url,settings);
2060 $.ajax(url,settings);
2061 };
2061 };
2062
2062
2063 Notebook.prototype.rename = function (nbname) {
2063 Notebook.prototype.rename = function (nbname) {
2064 this.content_manager.rename_notebook(this, nbname);
2064 this.content_manager.rename_notebook(this, nbname);
2065 };
2065 };
2066
2066
2067 Notebook.prototype.delete = function () {
2067 Notebook.prototype.delete = function () {
2068 this.content_manager.delete_notebook(this.notebook_name, this.notebook_path, this.base_url);
2068 this.content_manager.delete_notebook(this.notebook_name, this.notebook_path);
2069 };
2069 };
2070
2070
2071 Notebook.prototype.rename_success = function (json, status, xhr) {
2071 Notebook.prototype.rename_success = function (json, status, xhr) {
2072 var name = this.notebook_name = json.name;
2072 var name = this.notebook_name = json.name;
2073 var path = json.path;
2073 var path = json.path;
2074 this.session.rename_notebook(name, path);
2074 this.session.rename_notebook(name, path);
2075 this.events.trigger('notebook_renamed.Notebook', json);
2075 this.events.trigger('notebook_renamed.Notebook', json);
2076 };
2076 };
2077
2077
2078 Notebook.prototype.rename_error = function (xhr, status, error) {
2078 Notebook.prototype.rename_error = function (xhr, status, error) {
2079 var that = this;
2079 var that = this;
2080 var dialog_body = $('<div/>').append(
2080 var dialog_body = $('<div/>').append(
2081 $("<p/>").text('This notebook name already exists.')
2081 $("<p/>").text('This notebook name already exists.')
2082 );
2082 );
2083 this.events.trigger('notebook_rename_failed.Notebook', [xhr, status, error]);
2083 this.events.trigger('notebook_rename_failed.Notebook', [xhr, status, error]);
2084 dialog.modal({
2084 dialog.modal({
2085 notebook: this,
2085 notebook: this,
2086 keyboard_manager: this.keyboard_manager,
2086 keyboard_manager: this.keyboard_manager,
2087 title: "Notebook Rename Error!",
2087 title: "Notebook Rename Error!",
2088 body: dialog_body,
2088 body: dialog_body,
2089 buttons : {
2089 buttons : {
2090 "Cancel": {},
2090 "Cancel": {},
2091 "OK": {
2091 "OK": {
2092 class: "btn-primary",
2092 class: "btn-primary",
2093 click: function () {
2093 click: function () {
2094 this.save_widget.rename_notebook({notebook:that});
2094 this.save_widget.rename_notebook({notebook:that});
2095 }}
2095 }}
2096 },
2096 },
2097 open : function (event, ui) {
2097 open : function (event, ui) {
2098 var that = $(this);
2098 var that = $(this);
2099 // Upon ENTER, click the OK button.
2099 // Upon ENTER, click the OK button.
2100 that.find('input[type="text"]').keydown(function (event, ui) {
2100 that.find('input[type="text"]').keydown(function (event, ui) {
2101 if (event.which === this.keyboard.keycodes.enter) {
2101 if (event.which === this.keyboard.keycodes.enter) {
2102 that.find('.btn-primary').first().click();
2102 that.find('.btn-primary').first().click();
2103 }
2103 }
2104 });
2104 });
2105 that.find('input[type="text"]').focus();
2105 that.find('input[type="text"]').focus();
2106 }
2106 }
2107 });
2107 });
2108 };
2108 };
2109
2109
2110 /**
2110 /**
2111 * Request a notebook's data from the server.
2111 * Request a notebook's data from the server.
2112 *
2112 *
2113 * @method load_notebook
2113 * @method load_notebook
2114 * @param {String} notebook_name and path A notebook to load
2114 * @param {String} notebook_name and path A notebook to load
2115 */
2115 */
2116 Notebook.prototype.load_notebook = function (notebook_name, notebook_path) {
2116 Notebook.prototype.load_notebook = function (notebook_name, notebook_path) {
2117 var that = this;
2117 var that = this;
2118 this.notebook_name = notebook_name;
2118 this.notebook_name = notebook_name;
2119 this.notebook_path = notebook_path;
2119 this.notebook_path = notebook_path;
2120 // We do the call with settings so we can set cache to false.
2120 // We do the call with settings so we can set cache to false.
2121 var settings = {
2121 var settings = {
2122 processData : false,
2122 processData : false,
2123 cache : false,
2123 cache : false,
2124 type : "GET",
2124 type : "GET",
2125 dataType : "json",
2125 dataType : "json",
2126 success : $.proxy(this.load_notebook_success,this),
2126 success : $.proxy(this.load_notebook_success,this),
2127 error : $.proxy(this.load_notebook_error,this),
2127 error : $.proxy(this.load_notebook_error,this),
2128 };
2128 };
2129 this.events.trigger('notebook_loading.Notebook');
2129 this.events.trigger('notebook_loading.Notebook');
2130 var url = utils.url_join_encode(
2130 var url = utils.url_join_encode(
2131 this.base_url,
2131 this.base_url,
2132 'api/contents',
2132 'api/contents',
2133 this.notebook_path,
2133 this.notebook_path,
2134 this.notebook_name
2134 this.notebook_name
2135 );
2135 );
2136 $.ajax(url, settings);
2136 $.ajax(url, settings);
2137 };
2137 };
2138
2138
2139 /**
2139 /**
2140 * Success callback for loading a notebook from the server.
2140 * Success callback for loading a notebook from the server.
2141 *
2141 *
2142 * Load notebook data from the JSON response.
2142 * Load notebook data from the JSON response.
2143 *
2143 *
2144 * @method load_notebook_success
2144 * @method load_notebook_success
2145 * @param {Object} data JSON representation of a notebook
2145 * @param {Object} data JSON representation of a notebook
2146 * @param {String} status Description of response status
2146 * @param {String} status Description of response status
2147 * @param {jqXHR} xhr jQuery Ajax object
2147 * @param {jqXHR} xhr jQuery Ajax object
2148 */
2148 */
2149 Notebook.prototype.load_notebook_success = function (data, status, xhr) {
2149 Notebook.prototype.load_notebook_success = function (data, status, xhr) {
2150 var failed;
2150 var failed;
2151 try {
2151 try {
2152 this.fromJSON(data);
2152 this.fromJSON(data);
2153 } catch (e) {
2153 } catch (e) {
2154 failed = e;
2154 failed = e;
2155 console.log("Notebook failed to load from JSON:", e);
2155 console.log("Notebook failed to load from JSON:", e);
2156 }
2156 }
2157 if (failed || data.message) {
2157 if (failed || data.message) {
2158 // *either* fromJSON failed or validation failed
2158 // *either* fromJSON failed or validation failed
2159 var body = $("<div>");
2159 var body = $("<div>");
2160 var title;
2160 var title;
2161 if (failed) {
2161 if (failed) {
2162 title = "Notebook failed to load";
2162 title = "Notebook failed to load";
2163 body.append($("<p>").text(
2163 body.append($("<p>").text(
2164 "The error was: "
2164 "The error was: "
2165 )).append($("<div>").addClass("js-error").text(
2165 )).append($("<div>").addClass("js-error").text(
2166 failed.toString()
2166 failed.toString()
2167 )).append($("<p>").text(
2167 )).append($("<p>").text(
2168 "See the error console for details."
2168 "See the error console for details."
2169 ));
2169 ));
2170 } else {
2170 } else {
2171 title = "Notebook validation failed";
2171 title = "Notebook validation failed";
2172 }
2172 }
2173
2173
2174 if (data.message) {
2174 if (data.message) {
2175 var msg;
2175 var msg;
2176 if (failed) {
2176 if (failed) {
2177 msg = "The notebook also failed validation:"
2177 msg = "The notebook also failed validation:"
2178 } else {
2178 } else {
2179 msg = "An invalid notebook may not function properly." +
2179 msg = "An invalid notebook may not function properly." +
2180 " The validation error was:"
2180 " The validation error was:"
2181 }
2181 }
2182 body.append($("<p>").text(
2182 body.append($("<p>").text(
2183 msg
2183 msg
2184 )).append($("<div>").addClass("validation-error").append(
2184 )).append($("<div>").addClass("validation-error").append(
2185 $("<pre>").text(data.message)
2185 $("<pre>").text(data.message)
2186 ));
2186 ));
2187 }
2187 }
2188
2188
2189 dialog.modal({
2189 dialog.modal({
2190 notebook: this,
2190 notebook: this,
2191 keyboard_manager: this.keyboard_manager,
2191 keyboard_manager: this.keyboard_manager,
2192 title: title,
2192 title: title,
2193 body: body,
2193 body: body,
2194 buttons : {
2194 buttons : {
2195 OK : {
2195 OK : {
2196 "class" : "btn-primary"
2196 "class" : "btn-primary"
2197 }
2197 }
2198 }
2198 }
2199 });
2199 });
2200 }
2200 }
2201 if (this.ncells() === 0) {
2201 if (this.ncells() === 0) {
2202 this.insert_cell_below('code');
2202 this.insert_cell_below('code');
2203 this.edit_mode(0);
2203 this.edit_mode(0);
2204 } else {
2204 } else {
2205 this.select(0);
2205 this.select(0);
2206 this.handle_command_mode(this.get_cell(0));
2206 this.handle_command_mode(this.get_cell(0));
2207 }
2207 }
2208 this.set_dirty(false);
2208 this.set_dirty(false);
2209 this.scroll_to_top();
2209 this.scroll_to_top();
2210 var nbmodel = data.content;
2210 var nbmodel = data.content;
2211 var orig_nbformat = nbmodel.metadata.orig_nbformat;
2211 var orig_nbformat = nbmodel.metadata.orig_nbformat;
2212 var orig_nbformat_minor = nbmodel.metadata.orig_nbformat_minor;
2212 var orig_nbformat_minor = nbmodel.metadata.orig_nbformat_minor;
2213 if (orig_nbformat !== undefined && nbmodel.nbformat !== orig_nbformat) {
2213 if (orig_nbformat !== undefined && nbmodel.nbformat !== orig_nbformat) {
2214 var msg = "This notebook has been converted from an older " +
2214 var msg = "This notebook has been converted from an older " +
2215 "notebook format (v"+orig_nbformat+") to the current notebook " +
2215 "notebook format (v"+orig_nbformat+") to the current notebook " +
2216 "format (v"+nbmodel.nbformat+"). The next time you save this notebook, the " +
2216 "format (v"+nbmodel.nbformat+"). The next time you save this notebook, the " +
2217 "newer notebook format will be used and older versions of IPython " +
2217 "newer notebook format will be used and older versions of IPython " +
2218 "may not be able to read it. To keep the older version, close the " +
2218 "may not be able to read it. To keep the older version, close the " +
2219 "notebook without saving it.";
2219 "notebook without saving it.";
2220 dialog.modal({
2220 dialog.modal({
2221 notebook: this,
2221 notebook: this,
2222 keyboard_manager: this.keyboard_manager,
2222 keyboard_manager: this.keyboard_manager,
2223 title : "Notebook converted",
2223 title : "Notebook converted",
2224 body : msg,
2224 body : msg,
2225 buttons : {
2225 buttons : {
2226 OK : {
2226 OK : {
2227 class : "btn-primary"
2227 class : "btn-primary"
2228 }
2228 }
2229 }
2229 }
2230 });
2230 });
2231 } else if (orig_nbformat_minor !== undefined && nbmodel.nbformat_minor !== orig_nbformat_minor) {
2231 } else if (orig_nbformat_minor !== undefined && nbmodel.nbformat_minor !== orig_nbformat_minor) {
2232 var that = this;
2232 var that = this;
2233 var orig_vs = 'v' + nbmodel.nbformat + '.' + orig_nbformat_minor;
2233 var orig_vs = 'v' + nbmodel.nbformat + '.' + orig_nbformat_minor;
2234 var this_vs = 'v' + nbmodel.nbformat + '.' + this.nbformat_minor;
2234 var this_vs = 'v' + nbmodel.nbformat + '.' + this.nbformat_minor;
2235 var msg = "This notebook is version " + orig_vs + ", but we only fully support up to " +
2235 var msg = "This notebook is version " + orig_vs + ", but we only fully support up to " +
2236 this_vs + ". You can still work with this notebook, but some features " +
2236 this_vs + ". You can still work with this notebook, but some features " +
2237 "introduced in later notebook versions may not be available.";
2237 "introduced in later notebook versions may not be available.";
2238
2238
2239 dialog.modal({
2239 dialog.modal({
2240 notebook: this,
2240 notebook: this,
2241 keyboard_manager: this.keyboard_manager,
2241 keyboard_manager: this.keyboard_manager,
2242 title : "Newer Notebook",
2242 title : "Newer Notebook",
2243 body : msg,
2243 body : msg,
2244 buttons : {
2244 buttons : {
2245 OK : {
2245 OK : {
2246 class : "btn-danger"
2246 class : "btn-danger"
2247 }
2247 }
2248 }
2248 }
2249 });
2249 });
2250
2250
2251 }
2251 }
2252
2252
2253 // Create the session after the notebook is completely loaded to prevent
2253 // Create the session after the notebook is completely loaded to prevent
2254 // code execution upon loading, which is a security risk.
2254 // code execution upon loading, which is a security risk.
2255 if (this.session === null) {
2255 if (this.session === null) {
2256 var kernelspec = this.metadata.kernelspec || {};
2256 var kernelspec = this.metadata.kernelspec || {};
2257 var kernel_name = kernelspec.name;
2257 var kernel_name = kernelspec.name;
2258
2258
2259 this.start_session(kernel_name);
2259 this.start_session(kernel_name);
2260 }
2260 }
2261 // load our checkpoint list
2261 // load our checkpoint list
2262 this.list_checkpoints();
2262 this.list_checkpoints();
2263
2263
2264 // load toolbar state
2264 // load toolbar state
2265 if (this.metadata.celltoolbar) {
2265 if (this.metadata.celltoolbar) {
2266 celltoolbar.CellToolbar.global_show();
2266 celltoolbar.CellToolbar.global_show();
2267 celltoolbar.CellToolbar.activate_preset(this.metadata.celltoolbar);
2267 celltoolbar.CellToolbar.activate_preset(this.metadata.celltoolbar);
2268 } else {
2268 } else {
2269 celltoolbar.CellToolbar.global_hide();
2269 celltoolbar.CellToolbar.global_hide();
2270 }
2270 }
2271
2271
2272 // now that we're fully loaded, it is safe to restore save functionality
2272 // now that we're fully loaded, it is safe to restore save functionality
2273 delete(this.save_notebook);
2273 delete(this.save_notebook);
2274 this.events.trigger('notebook_loaded.Notebook');
2274 this.events.trigger('notebook_loaded.Notebook');
2275 };
2275 };
2276
2276
2277 /**
2277 /**
2278 * Failure callback for loading a notebook from the server.
2278 * Failure callback for loading a notebook from the server.
2279 *
2279 *
2280 * @method load_notebook_error
2280 * @method load_notebook_error
2281 * @param {jqXHR} xhr jQuery Ajax object
2281 * @param {jqXHR} xhr jQuery Ajax object
2282 * @param {String} status Description of response status
2282 * @param {String} status Description of response status
2283 * @param {String} error HTTP error message
2283 * @param {String} error HTTP error message
2284 */
2284 */
2285 Notebook.prototype.load_notebook_error = function (xhr, status, error) {
2285 Notebook.prototype.load_notebook_error = function (xhr, status, error) {
2286 this.events.trigger('notebook_load_failed.Notebook', [xhr, status, error]);
2286 this.events.trigger('notebook_load_failed.Notebook', [xhr, status, error]);
2287 utils.log_ajax_error(xhr, status, error);
2287 utils.log_ajax_error(xhr, status, error);
2288 var msg = $("<div>");
2288 var msg = $("<div>");
2289 if (xhr.status === 400) {
2289 if (xhr.status === 400) {
2290 msg.text(utils.ajax_error_msg(xhr));
2290 msg.text(utils.ajax_error_msg(xhr));
2291 } else if (xhr.status === 500) {
2291 } else if (xhr.status === 500) {
2292 msg.text("An unknown error occurred while loading this notebook. " +
2292 msg.text("An unknown error occurred while loading this notebook. " +
2293 "This version can load notebook formats " +
2293 "This version can load notebook formats " +
2294 "v" + this.nbformat + " or earlier. See the server log for details.");
2294 "v" + this.nbformat + " or earlier. See the server log for details.");
2295 }
2295 }
2296 dialog.modal({
2296 dialog.modal({
2297 notebook: this,
2297 notebook: this,
2298 keyboard_manager: this.keyboard_manager,
2298 keyboard_manager: this.keyboard_manager,
2299 title: "Error loading notebook",
2299 title: "Error loading notebook",
2300 body : msg,
2300 body : msg,
2301 buttons : {
2301 buttons : {
2302 "OK": {}
2302 "OK": {}
2303 }
2303 }
2304 });
2304 });
2305 };
2305 };
2306
2306
2307 /********************* checkpoint-related *********************/
2307 /********************* checkpoint-related *********************/
2308
2308
2309 /**
2309 /**
2310 * Save the notebook then immediately create a checkpoint.
2310 * Save the notebook then immediately create a checkpoint.
2311 *
2311 *
2312 * @method save_checkpoint
2312 * @method save_checkpoint
2313 */
2313 */
2314 Notebook.prototype.save_checkpoint = function () {
2314 Notebook.prototype.save_checkpoint = function () {
2315 this._checkpoint_after_save = true;
2315 this._checkpoint_after_save = true;
2316 this.save_notebook();
2316 this.save_notebook();
2317 };
2317 };
2318
2318
2319 /**
2319 /**
2320 * Add a checkpoint for this notebook.
2320 * Add a checkpoint for this notebook.
2321 * for use as a callback from checkpoint creation.
2321 * for use as a callback from checkpoint creation.
2322 *
2322 *
2323 * @method add_checkpoint
2323 * @method add_checkpoint
2324 */
2324 */
2325 Notebook.prototype.add_checkpoint = function (checkpoint) {
2325 Notebook.prototype.add_checkpoint = function (checkpoint) {
2326 var found = false;
2326 var found = false;
2327 for (var i = 0; i < this.checkpoints.length; i++) {
2327 for (var i = 0; i < this.checkpoints.length; i++) {
2328 var existing = this.checkpoints[i];
2328 var existing = this.checkpoints[i];
2329 if (existing.id == checkpoint.id) {
2329 if (existing.id == checkpoint.id) {
2330 found = true;
2330 found = true;
2331 this.checkpoints[i] = checkpoint;
2331 this.checkpoints[i] = checkpoint;
2332 break;
2332 break;
2333 }
2333 }
2334 }
2334 }
2335 if (!found) {
2335 if (!found) {
2336 this.checkpoints.push(checkpoint);
2336 this.checkpoints.push(checkpoint);
2337 }
2337 }
2338 this.last_checkpoint = this.checkpoints[this.checkpoints.length - 1];
2338 this.last_checkpoint = this.checkpoints[this.checkpoints.length - 1];
2339 };
2339 };
2340
2340
2341 /**
2341 /**
2342 * List checkpoints for this notebook.
2342 * List checkpoints for this notebook.
2343 *
2343 *
2344 * @method list_checkpoints
2344 * @method list_checkpoints
2345 */
2345 */
2346 Notebook.prototype.list_checkpoints = function () {
2346 Notebook.prototype.list_checkpoints = function () {
2347 var url = utils.url_join_encode(
2347 var url = utils.url_join_encode(
2348 this.base_url,
2348 this.base_url,
2349 'api/contents',
2349 'api/contents',
2350 this.notebook_path,
2350 this.notebook_path,
2351 this.notebook_name,
2351 this.notebook_name,
2352 'checkpoints'
2352 'checkpoints'
2353 );
2353 );
2354 $.get(url).done(
2354 $.get(url).done(
2355 $.proxy(this.list_checkpoints_success, this)
2355 $.proxy(this.list_checkpoints_success, this)
2356 ).fail(
2356 ).fail(
2357 $.proxy(this.list_checkpoints_error, this)
2357 $.proxy(this.list_checkpoints_error, this)
2358 );
2358 );
2359 };
2359 };
2360
2360
2361 /**
2361 /**
2362 * Success callback for listing checkpoints.
2362 * Success callback for listing checkpoints.
2363 *
2363 *
2364 * @method list_checkpoint_success
2364 * @method list_checkpoint_success
2365 * @param {Object} data JSON representation of a checkpoint
2365 * @param {Object} data JSON representation of a checkpoint
2366 * @param {String} status Description of response status
2366 * @param {String} status Description of response status
2367 * @param {jqXHR} xhr jQuery Ajax object
2367 * @param {jqXHR} xhr jQuery Ajax object
2368 */
2368 */
2369 Notebook.prototype.list_checkpoints_success = function (data, status, xhr) {
2369 Notebook.prototype.list_checkpoints_success = function (data, status, xhr) {
2370 data = $.parseJSON(data);
2370 data = $.parseJSON(data);
2371 this.checkpoints = data;
2371 this.checkpoints = data;
2372 if (data.length) {
2372 if (data.length) {
2373 this.last_checkpoint = data[data.length - 1];
2373 this.last_checkpoint = data[data.length - 1];
2374 } else {
2374 } else {
2375 this.last_checkpoint = null;
2375 this.last_checkpoint = null;
2376 }
2376 }
2377 this.events.trigger('checkpoints_listed.Notebook', [data]);
2377 this.events.trigger('checkpoints_listed.Notebook', [data]);
2378 };
2378 };
2379
2379
2380 /**
2380 /**
2381 * Failure callback for listing a checkpoint.
2381 * Failure callback for listing a checkpoint.
2382 *
2382 *
2383 * @method list_checkpoint_error
2383 * @method list_checkpoint_error
2384 * @param {jqXHR} xhr jQuery Ajax object
2384 * @param {jqXHR} xhr jQuery Ajax object
2385 * @param {String} status Description of response status
2385 * @param {String} status Description of response status
2386 * @param {String} error_msg HTTP error message
2386 * @param {String} error_msg HTTP error message
2387 */
2387 */
2388 Notebook.prototype.list_checkpoints_error = function (xhr, status, error_msg) {
2388 Notebook.prototype.list_checkpoints_error = function (xhr, status, error_msg) {
2389 this.events.trigger('list_checkpoints_failed.Notebook');
2389 this.events.trigger('list_checkpoints_failed.Notebook');
2390 };
2390 };
2391
2391
2392 /**
2392 /**
2393 * Create a checkpoint of this notebook on the server from the most recent save.
2393 * Create a checkpoint of this notebook on the server from the most recent save.
2394 *
2394 *
2395 * @method create_checkpoint
2395 * @method create_checkpoint
2396 */
2396 */
2397 Notebook.prototype.create_checkpoint = function () {
2397 Notebook.prototype.create_checkpoint = function () {
2398 var url = utils.url_join_encode(
2398 var url = utils.url_join_encode(
2399 this.base_url,
2399 this.base_url,
2400 'api/contents',
2400 'api/contents',
2401 this.notebook_path,
2401 this.notebook_path,
2402 this.notebook_name,
2402 this.notebook_name,
2403 'checkpoints'
2403 'checkpoints'
2404 );
2404 );
2405 $.post(url).done(
2405 $.post(url).done(
2406 $.proxy(this.create_checkpoint_success, this)
2406 $.proxy(this.create_checkpoint_success, this)
2407 ).fail(
2407 ).fail(
2408 $.proxy(this.create_checkpoint_error, this)
2408 $.proxy(this.create_checkpoint_error, this)
2409 );
2409 );
2410 };
2410 };
2411
2411
2412 /**
2412 /**
2413 * Success callback for creating a checkpoint.
2413 * Success callback for creating a checkpoint.
2414 *
2414 *
2415 * @method create_checkpoint_success
2415 * @method create_checkpoint_success
2416 * @param {Object} data JSON representation of a checkpoint
2416 * @param {Object} data JSON representation of a checkpoint
2417 * @param {String} status Description of response status
2417 * @param {String} status Description of response status
2418 * @param {jqXHR} xhr jQuery Ajax object
2418 * @param {jqXHR} xhr jQuery Ajax object
2419 */
2419 */
2420 Notebook.prototype.create_checkpoint_success = function (data, status, xhr) {
2420 Notebook.prototype.create_checkpoint_success = function (data, status, xhr) {
2421 data = $.parseJSON(data);
2421 data = $.parseJSON(data);
2422 this.add_checkpoint(data);
2422 this.add_checkpoint(data);
2423 this.events.trigger('checkpoint_created.Notebook', data);
2423 this.events.trigger('checkpoint_created.Notebook', data);
2424 };
2424 };
2425
2425
2426 /**
2426 /**
2427 * Failure callback for creating a checkpoint.
2427 * Failure callback for creating a checkpoint.
2428 *
2428 *
2429 * @method create_checkpoint_error
2429 * @method create_checkpoint_error
2430 * @param {jqXHR} xhr jQuery Ajax object
2430 * @param {jqXHR} xhr jQuery Ajax object
2431 * @param {String} status Description of response status
2431 * @param {String} status Description of response status
2432 * @param {String} error_msg HTTP error message
2432 * @param {String} error_msg HTTP error message
2433 */
2433 */
2434 Notebook.prototype.create_checkpoint_error = function (xhr, status, error_msg) {
2434 Notebook.prototype.create_checkpoint_error = function (xhr, status, error_msg) {
2435 this.events.trigger('checkpoint_failed.Notebook');
2435 this.events.trigger('checkpoint_failed.Notebook');
2436 };
2436 };
2437
2437
2438 Notebook.prototype.restore_checkpoint_dialog = function (checkpoint) {
2438 Notebook.prototype.restore_checkpoint_dialog = function (checkpoint) {
2439 var that = this;
2439 var that = this;
2440 checkpoint = checkpoint || this.last_checkpoint;
2440 checkpoint = checkpoint || this.last_checkpoint;
2441 if ( ! checkpoint ) {
2441 if ( ! checkpoint ) {
2442 console.log("restore dialog, but no checkpoint to restore to!");
2442 console.log("restore dialog, but no checkpoint to restore to!");
2443 return;
2443 return;
2444 }
2444 }
2445 var body = $('<div/>').append(
2445 var body = $('<div/>').append(
2446 $('<p/>').addClass("p-space").text(
2446 $('<p/>').addClass("p-space").text(
2447 "Are you sure you want to revert the notebook to " +
2447 "Are you sure you want to revert the notebook to " +
2448 "the latest checkpoint?"
2448 "the latest checkpoint?"
2449 ).append(
2449 ).append(
2450 $("<strong/>").text(
2450 $("<strong/>").text(
2451 " This cannot be undone."
2451 " This cannot be undone."
2452 )
2452 )
2453 )
2453 )
2454 ).append(
2454 ).append(
2455 $('<p/>').addClass("p-space").text("The checkpoint was last updated at:")
2455 $('<p/>').addClass("p-space").text("The checkpoint was last updated at:")
2456 ).append(
2456 ).append(
2457 $('<p/>').addClass("p-space").text(
2457 $('<p/>').addClass("p-space").text(
2458 Date(checkpoint.last_modified)
2458 Date(checkpoint.last_modified)
2459 ).css("text-align", "center")
2459 ).css("text-align", "center")
2460 );
2460 );
2461
2461
2462 dialog.modal({
2462 dialog.modal({
2463 notebook: this,
2463 notebook: this,
2464 keyboard_manager: this.keyboard_manager,
2464 keyboard_manager: this.keyboard_manager,
2465 title : "Revert notebook to checkpoint",
2465 title : "Revert notebook to checkpoint",
2466 body : body,
2466 body : body,
2467 buttons : {
2467 buttons : {
2468 Revert : {
2468 Revert : {
2469 class : "btn-danger",
2469 class : "btn-danger",
2470 click : function () {
2470 click : function () {
2471 that.restore_checkpoint(checkpoint.id);
2471 that.restore_checkpoint(checkpoint.id);
2472 }
2472 }
2473 },
2473 },
2474 Cancel : {}
2474 Cancel : {}
2475 }
2475 }
2476 });
2476 });
2477 };
2477 };
2478
2478
2479 /**
2479 /**
2480 * Restore the notebook to a checkpoint state.
2480 * Restore the notebook to a checkpoint state.
2481 *
2481 *
2482 * @method restore_checkpoint
2482 * @method restore_checkpoint
2483 * @param {String} checkpoint ID
2483 * @param {String} checkpoint ID
2484 */
2484 */
2485 Notebook.prototype.restore_checkpoint = function (checkpoint) {
2485 Notebook.prototype.restore_checkpoint = function (checkpoint) {
2486 this.events.trigger('notebook_restoring.Notebook', checkpoint);
2486 this.events.trigger('notebook_restoring.Notebook', checkpoint);
2487 var url = utils.url_join_encode(
2487 var url = utils.url_join_encode(
2488 this.base_url,
2488 this.base_url,
2489 'api/contents',
2489 'api/contents',
2490 this.notebook_path,
2490 this.notebook_path,
2491 this.notebook_name,
2491 this.notebook_name,
2492 'checkpoints',
2492 'checkpoints',
2493 checkpoint
2493 checkpoint
2494 );
2494 );
2495 $.post(url).done(
2495 $.post(url).done(
2496 $.proxy(this.restore_checkpoint_success, this)
2496 $.proxy(this.restore_checkpoint_success, this)
2497 ).fail(
2497 ).fail(
2498 $.proxy(this.restore_checkpoint_error, this)
2498 $.proxy(this.restore_checkpoint_error, this)
2499 );
2499 );
2500 };
2500 };
2501
2501
2502 /**
2502 /**
2503 * Success callback for restoring a notebook to a checkpoint.
2503 * Success callback for restoring a notebook to a checkpoint.
2504 *
2504 *
2505 * @method restore_checkpoint_success
2505 * @method restore_checkpoint_success
2506 * @param {Object} data (ignored, should be empty)
2506 * @param {Object} data (ignored, should be empty)
2507 * @param {String} status Description of response status
2507 * @param {String} status Description of response status
2508 * @param {jqXHR} xhr jQuery Ajax object
2508 * @param {jqXHR} xhr jQuery Ajax object
2509 */
2509 */
2510 Notebook.prototype.restore_checkpoint_success = function (data, status, xhr) {
2510 Notebook.prototype.restore_checkpoint_success = function (data, status, xhr) {
2511 this.events.trigger('checkpoint_restored.Notebook');
2511 this.events.trigger('checkpoint_restored.Notebook');
2512 this.load_notebook(this.notebook_name, this.notebook_path);
2512 this.load_notebook(this.notebook_name, this.notebook_path);
2513 };
2513 };
2514
2514
2515 /**
2515 /**
2516 * Failure callback for restoring a notebook to a checkpoint.
2516 * Failure callback for restoring a notebook to a checkpoint.
2517 *
2517 *
2518 * @method restore_checkpoint_error
2518 * @method restore_checkpoint_error
2519 * @param {jqXHR} xhr jQuery Ajax object
2519 * @param {jqXHR} xhr jQuery Ajax object
2520 * @param {String} status Description of response status
2520 * @param {String} status Description of response status
2521 * @param {String} error_msg HTTP error message
2521 * @param {String} error_msg HTTP error message
2522 */
2522 */
2523 Notebook.prototype.restore_checkpoint_error = function (xhr, status, error_msg) {
2523 Notebook.prototype.restore_checkpoint_error = function (xhr, status, error_msg) {
2524 this.events.trigger('checkpoint_restore_failed.Notebook');
2524 this.events.trigger('checkpoint_restore_failed.Notebook');
2525 };
2525 };
2526
2526
2527 /**
2527 /**
2528 * Delete a notebook checkpoint.
2528 * Delete a notebook checkpoint.
2529 *
2529 *
2530 * @method delete_checkpoint
2530 * @method delete_checkpoint
2531 * @param {String} checkpoint ID
2531 * @param {String} checkpoint ID
2532 */
2532 */
2533 Notebook.prototype.delete_checkpoint = function (checkpoint) {
2533 Notebook.prototype.delete_checkpoint = function (checkpoint) {
2534 this.events.trigger('notebook_restoring.Notebook', checkpoint);
2534 this.events.trigger('notebook_restoring.Notebook', checkpoint);
2535 var url = utils.url_join_encode(
2535 var url = utils.url_join_encode(
2536 this.base_url,
2536 this.base_url,
2537 'api/contents',
2537 'api/contents',
2538 this.notebook_path,
2538 this.notebook_path,
2539 this.notebook_name,
2539 this.notebook_name,
2540 'checkpoints',
2540 'checkpoints',
2541 checkpoint
2541 checkpoint
2542 );
2542 );
2543 $.ajax(url, {
2543 $.ajax(url, {
2544 type: 'DELETE',
2544 type: 'DELETE',
2545 success: $.proxy(this.delete_checkpoint_success, this),
2545 success: $.proxy(this.delete_checkpoint_success, this),
2546 error: $.proxy(this.delete_checkpoint_error, this)
2546 error: $.proxy(this.delete_checkpoint_error, this)
2547 });
2547 });
2548 };
2548 };
2549
2549
2550 /**
2550 /**
2551 * Success callback for deleting a notebook checkpoint
2551 * Success callback for deleting a notebook checkpoint
2552 *
2552 *
2553 * @method delete_checkpoint_success
2553 * @method delete_checkpoint_success
2554 * @param {Object} data (ignored, should be empty)
2554 * @param {Object} data (ignored, should be empty)
2555 * @param {String} status Description of response status
2555 * @param {String} status Description of response status
2556 * @param {jqXHR} xhr jQuery Ajax object
2556 * @param {jqXHR} xhr jQuery Ajax object
2557 */
2557 */
2558 Notebook.prototype.delete_checkpoint_success = function (data, status, xhr) {
2558 Notebook.prototype.delete_checkpoint_success = function (data, status, xhr) {
2559 this.events.trigger('checkpoint_deleted.Notebook', data);
2559 this.events.trigger('checkpoint_deleted.Notebook', data);
2560 this.load_notebook(this.notebook_name, this.notebook_path);
2560 this.load_notebook(this.notebook_name, this.notebook_path);
2561 };
2561 };
2562
2562
2563 /**
2563 /**
2564 * Failure callback for deleting a notebook checkpoint.
2564 * Failure callback for deleting a notebook checkpoint.
2565 *
2565 *
2566 * @method delete_checkpoint_error
2566 * @method delete_checkpoint_error
2567 * @param {jqXHR} xhr jQuery Ajax object
2567 * @param {jqXHR} xhr jQuery Ajax object
2568 * @param {String} status Description of response status
2568 * @param {String} status Description of response status
2569 * @param {String} error HTTP error message
2569 * @param {String} error HTTP error message
2570 */
2570 */
2571 Notebook.prototype.delete_checkpoint_error = function (xhr, status, error) {
2571 Notebook.prototype.delete_checkpoint_error = function (xhr, status, error) {
2572 this.events.trigger('checkpoint_delete_failed.Notebook', [xhr, status, error]);
2572 this.events.trigger('checkpoint_delete_failed.Notebook', [xhr, status, error]);
2573 };
2573 };
2574
2574
2575
2575
2576 // For backwards compatability.
2576 // For backwards compatability.
2577 IPython.Notebook = Notebook;
2577 IPython.Notebook = Notebook;
2578
2578
2579 return {'Notebook': Notebook};
2579 return {'Notebook': Notebook};
2580 });
2580 });
@@ -1,481 +1,480 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
13 // Constructor
14 //
14 //
15 // Parameters:
15 // Parameters:
16 // selector: string
16 // selector: string
17 // options: dictionary
17 // options: dictionary
18 // Dictionary of keyword arguments.
18 // Dictionary of keyword arguments.
19 // session_list: SessionList instance
19 // session_list: SessionList instance
20 // element_name: string
20 // element_name: string
21 // base_url: string
21 // base_url: string
22 // notebook_path: string
22 // notebook_path: string
23 var that = this;
23 var that = this;
24 this.session_list = options.session_list;
24 this.session_list = options.session_list;
25 // 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
26 this.element_name = options.element_name || 'notebook';
26 this.element_name = options.element_name || 'notebook';
27 this.selector = selector;
27 this.selector = selector;
28 if (this.selector !== undefined) {
28 if (this.selector !== undefined) {
29 this.element = $(selector);
29 this.element = $(selector);
30 this.style();
30 this.style();
31 this.bind_events();
31 this.bind_events();
32 }
32 }
33 this.notebooks_list = [];
33 this.notebooks_list = [];
34 this.sessions = {};
34 this.sessions = {};
35 this.base_url = options.base_url || utils.get_body_data("baseUrl");
35 this.base_url = options.base_url || utils.get_body_data("baseUrl");
36 this.notebook_path = options.notebook_path || utils.get_body_data("notebookPath");
36 this.notebook_path = options.notebook_path || utils.get_body_data("notebookPath");
37 this.content_manager = options.content_manager;
37 this.content_manager = options.content_manager;
38 if (this.session_list && this.session_list.events) {
38 if (this.session_list && this.session_list.events) {
39 this.session_list.events.on('sessions_loaded.Dashboard',
39 this.session_list.events.on('sessions_loaded.Dashboard',
40 function(e, d) { that.sessions_loaded(d); });
40 function(e, d) { that.sessions_loaded(d); });
41 }
41 }
42 };
42 };
43
43
44 NotebookList.prototype.style = function () {
44 NotebookList.prototype.style = function () {
45 var prefix = '#' + this.element_name;
45 var prefix = '#' + this.element_name;
46 $(prefix + '_toolbar').addClass('list_toolbar');
46 $(prefix + '_toolbar').addClass('list_toolbar');
47 $(prefix + '_list_info').addClass('toolbar_info');
47 $(prefix + '_list_info').addClass('toolbar_info');
48 $(prefix + '_buttons').addClass('toolbar_buttons');
48 $(prefix + '_buttons').addClass('toolbar_buttons');
49 $(prefix + '_list_header').addClass('list_header');
49 $(prefix + '_list_header').addClass('list_header');
50 this.element.addClass("list_container");
50 this.element.addClass("list_container");
51 };
51 };
52
52
53
53
54 NotebookList.prototype.bind_events = function () {
54 NotebookList.prototype.bind_events = function () {
55 var that = this;
55 var that = this;
56 $('#refresh_' + this.element_name + '_list').click(function () {
56 $('#refresh_' + this.element_name + '_list').click(function () {
57 that.load_sessions();
57 that.load_sessions();
58 });
58 });
59 this.element.bind('dragover', function () {
59 this.element.bind('dragover', function () {
60 return false;
60 return false;
61 });
61 });
62 this.element.bind('drop', function(event){
62 this.element.bind('drop', function(event){
63 that.handleFilesUpload(event,'drop');
63 that.handleFilesUpload(event,'drop');
64 return false;
64 return false;
65 });
65 });
66 };
66 };
67
67
68 NotebookList.prototype.handleFilesUpload = function(event, dropOrForm) {
68 NotebookList.prototype.handleFilesUpload = function(event, dropOrForm) {
69 var that = this;
69 var that = this;
70 var files;
70 var files;
71 if(dropOrForm =='drop'){
71 if(dropOrForm =='drop'){
72 files = event.originalEvent.dataTransfer.files;
72 files = event.originalEvent.dataTransfer.files;
73 } else
73 } else
74 {
74 {
75 files = event.originalEvent.target.files;
75 files = event.originalEvent.target.files;
76 }
76 }
77 for (var i = 0; i < files.length; i++) {
77 for (var i = 0; i < files.length; i++) {
78 var f = files[i];
78 var f = files[i];
79 var name_and_ext = utils.splitext(f.name);
79 var name_and_ext = utils.splitext(f.name);
80 var file_ext = name_and_ext[1];
80 var file_ext = name_and_ext[1];
81
81
82 var reader = new FileReader();
82 var reader = new FileReader();
83 if (file_ext === '.ipynb') {
83 if (file_ext === '.ipynb') {
84 reader.readAsText(f);
84 reader.readAsText(f);
85 } else {
85 } else {
86 // read non-notebook files as binary
86 // read non-notebook files as binary
87 reader.readAsArrayBuffer(f);
87 reader.readAsArrayBuffer(f);
88 }
88 }
89 var item = that.new_item(0);
89 var item = that.new_item(0);
90 item.addClass('new-file');
90 item.addClass('new-file');
91 that.add_name_input(f.name, item, file_ext == '.ipynb' ? 'notebook' : 'file');
91 that.add_name_input(f.name, item, file_ext == '.ipynb' ? 'notebook' : 'file');
92 // Store the list item in the reader so we can use it later
92 // Store the list item in the reader so we can use it later
93 // to know which item it belongs to.
93 // to know which item it belongs to.
94 $(reader).data('item', item);
94 $(reader).data('item', item);
95 reader.onload = function (event) {
95 reader.onload = function (event) {
96 var item = $(event.target).data('item');
96 var item = $(event.target).data('item');
97 that.add_file_data(event.target.result, item);
97 that.add_file_data(event.target.result, item);
98 that.add_upload_button(item);
98 that.add_upload_button(item);
99 };
99 };
100 reader.onerror = function (event) {
100 reader.onerror = function (event) {
101 var item = $(event.target).data('item');
101 var item = $(event.target).data('item');
102 var name = item.data('name')
102 var name = item.data('name')
103 item.remove();
103 item.remove();
104 dialog.modal({
104 dialog.modal({
105 title : 'Failed to read file',
105 title : 'Failed to read file',
106 body : "Failed to read file '" + name + "'",
106 body : "Failed to read file '" + name + "'",
107 buttons : {'OK' : { 'class' : 'btn-primary' }}
107 buttons : {'OK' : { 'class' : 'btn-primary' }}
108 });
108 });
109 };
109 };
110 }
110 }
111 // Replace the file input form wth a clone of itself. This is required to
111 // Replace the file input form wth a clone of itself. This is required to
112 // reset the form. Otherwise, if you upload a file, delete it and try to
112 // reset the form. Otherwise, if you upload a file, delete it and try to
113 // upload it again, the changed event won't fire.
113 // upload it again, the changed event won't fire.
114 var form = $('input.fileinput');
114 var form = $('input.fileinput');
115 form.replaceWith(form.clone(true));
115 form.replaceWith(form.clone(true));
116 return false;
116 return false;
117 };
117 };
118
118
119 NotebookList.prototype.clear_list = function (remove_uploads) {
119 NotebookList.prototype.clear_list = function (remove_uploads) {
120 // Clears the navigation tree.
120 // Clears the navigation tree.
121 //
121 //
122 // Parameters
122 // Parameters
123 // remove_uploads: bool=False
123 // remove_uploads: bool=False
124 // Should upload prompts also be removed from the tree.
124 // Should upload prompts also be removed from the tree.
125 if (remove_uploads) {
125 if (remove_uploads) {
126 this.element.children('.list_item').remove();
126 this.element.children('.list_item').remove();
127 } else {
127 } else {
128 this.element.children('.list_item:not(.new-file)').remove();
128 this.element.children('.list_item:not(.new-file)').remove();
129 }
129 }
130 };
130 };
131
131
132 NotebookList.prototype.load_sessions = function(){
132 NotebookList.prototype.load_sessions = function(){
133 this.session_list.load_sessions();
133 this.session_list.load_sessions();
134 };
134 };
135
135
136
136
137 NotebookList.prototype.sessions_loaded = function(data){
137 NotebookList.prototype.sessions_loaded = function(data){
138 this.sessions = data;
138 this.sessions = data;
139 this.load_list();
139 this.load_list();
140 };
140 };
141
141
142 NotebookList.prototype.load_list = function () {
142 NotebookList.prototype.load_list = function () {
143 var that = this;
143 var that = this;
144 var settings = {
144 var settings = {
145 processData : false,
145 processData : false,
146 cache : false,
146 cache : false,
147 type : "GET",
147 type : "GET",
148 dataType : "json",
148 dataType : "json",
149 success : $.proxy(this.list_loaded, this),
149 success : $.proxy(this.list_loaded, this),
150 error : $.proxy( function(xhr, status, error){
150 error : $.proxy( function(xhr, status, error){
151 utils.log_ajax_error(xhr, status, error);
151 utils.log_ajax_error(xhr, status, error);
152 that.list_loaded([], null, null, {msg:"Error connecting to server."});
152 that.list_loaded([], null, null, {msg:"Error connecting to server."});
153 },this)
153 },this)
154 };
154 };
155
155
156 var url = utils.url_join_encode(
156 var url = utils.url_join_encode(
157 this.base_url,
157 this.base_url,
158 'api',
158 'api',
159 'contents',
159 'contents',
160 this.notebook_path
160 this.notebook_path
161 );
161 );
162 $.ajax(url, settings);
162 $.ajax(url, settings);
163 };
163 };
164
164
165
165
166 NotebookList.prototype.list_loaded = function (data, status, xhr, param) {
166 NotebookList.prototype.list_loaded = function (data, status, xhr, param) {
167 var message = 'Notebook list empty.';
167 var message = 'Notebook list empty.';
168 if (param !== undefined && param.msg) {
168 if (param !== undefined && param.msg) {
169 message = param.msg;
169 message = param.msg;
170 }
170 }
171 var item = null;
171 var item = null;
172 var model = null;
172 var model = null;
173 var list = data.content;
173 var list = data.content;
174 var len = list.length;
174 var len = list.length;
175 this.clear_list();
175 this.clear_list();
176 var n_uploads = this.element.children('.list_item').length;
176 var n_uploads = this.element.children('.list_item').length;
177 if (len === 0) {
177 if (len === 0) {
178 item = this.new_item(0);
178 item = this.new_item(0);
179 var span12 = item.children().first();
179 var span12 = item.children().first();
180 span12.empty();
180 span12.empty();
181 span12.append($('<div style="margin:auto;text-align:center;color:grey"/>').text(message));
181 span12.append($('<div style="margin:auto;text-align:center;color:grey"/>').text(message));
182 }
182 }
183 var path = this.notebook_path;
183 var path = this.notebook_path;
184 var offset = n_uploads;
184 var offset = n_uploads;
185 if (path !== '') {
185 if (path !== '') {
186 item = this.new_item(offset);
186 item = this.new_item(offset);
187 model = {
187 model = {
188 type: 'directory',
188 type: 'directory',
189 name: '..',
189 name: '..',
190 path: path,
190 path: path,
191 };
191 };
192 this.add_link(model, item);
192 this.add_link(model, item);
193 offset += 1;
193 offset += 1;
194 }
194 }
195 for (var i=0; i<len; i++) {
195 for (var i=0; i<len; i++) {
196 model = list[i];
196 model = list[i];
197 item = this.new_item(i+offset);
197 item = this.new_item(i+offset);
198 this.add_link(model, item);
198 this.add_link(model, item);
199 }
199 }
200 };
200 };
201
201
202
202
203 NotebookList.prototype.new_item = function (index) {
203 NotebookList.prototype.new_item = function (index) {
204 var item = $('<div/>').addClass("list_item").addClass("row");
204 var item = $('<div/>').addClass("list_item").addClass("row");
205 // item.addClass('list_item ui-widget ui-widget-content ui-helper-clearfix');
205 // item.addClass('list_item ui-widget ui-widget-content ui-helper-clearfix');
206 // item.css('border-top-style','none');
206 // item.css('border-top-style','none');
207 item.append($("<div/>").addClass("col-md-12").append(
207 item.append($("<div/>").addClass("col-md-12").append(
208 $('<i/>').addClass('item_icon')
208 $('<i/>').addClass('item_icon')
209 ).append(
209 ).append(
210 $("<a/>").addClass("item_link").append(
210 $("<a/>").addClass("item_link").append(
211 $("<span/>").addClass("item_name")
211 $("<span/>").addClass("item_name")
212 )
212 )
213 ).append(
213 ).append(
214 $('<div/>').addClass("item_buttons btn-group pull-right")
214 $('<div/>').addClass("item_buttons btn-group pull-right")
215 ));
215 ));
216
216
217 if (index === -1) {
217 if (index === -1) {
218 this.element.append(item);
218 this.element.append(item);
219 } else {
219 } else {
220 this.element.children().eq(index).after(item);
220 this.element.children().eq(index).after(item);
221 }
221 }
222 return item;
222 return item;
223 };
223 };
224
224
225
225
226 NotebookList.icons = {
226 NotebookList.icons = {
227 directory: 'folder_icon',
227 directory: 'folder_icon',
228 notebook: 'notebook_icon',
228 notebook: 'notebook_icon',
229 file: 'file_icon',
229 file: 'file_icon',
230 };
230 };
231
231
232 NotebookList.uri_prefixes = {
232 NotebookList.uri_prefixes = {
233 directory: 'tree',
233 directory: 'tree',
234 notebook: 'notebooks',
234 notebook: 'notebooks',
235 file: 'files',
235 file: 'files',
236 };
236 };
237
237
238
238
239 NotebookList.prototype.add_link = function (model, item) {
239 NotebookList.prototype.add_link = function (model, item) {
240 var path = model.path,
240 var path = model.path,
241 name = model.name;
241 name = model.name;
242 item.data('name', name);
242 item.data('name', name);
243 item.data('path', path);
243 item.data('path', path);
244 item.find(".item_name").text(name);
244 item.find(".item_name").text(name);
245 var icon = NotebookList.icons[model.type];
245 var icon = NotebookList.icons[model.type];
246 var uri_prefix = NotebookList.uri_prefixes[model.type];
246 var uri_prefix = NotebookList.uri_prefixes[model.type];
247 item.find(".item_icon").addClass(icon).addClass('icon-fixed-width');
247 item.find(".item_icon").addClass(icon).addClass('icon-fixed-width');
248 var link = item.find("a.item_link")
248 var link = item.find("a.item_link")
249 .attr('href',
249 .attr('href',
250 utils.url_join_encode(
250 utils.url_join_encode(
251 this.base_url,
251 this.base_url,
252 uri_prefix,
252 uri_prefix,
253 path,
253 path,
254 name
254 name
255 )
255 )
256 );
256 );
257 // directory nav doesn't open new tabs
257 // directory nav doesn't open new tabs
258 // files, notebooks do
258 // files, notebooks do
259 if (model.type !== "directory") {
259 if (model.type !== "directory") {
260 link.attr('target','_blank');
260 link.attr('target','_blank');
261 }
261 }
262 var path_name = utils.url_path_join(path, name);
262 var path_name = utils.url_path_join(path, name);
263 if (model.type == 'file') {
263 if (model.type == 'file') {
264 this.add_delete_button(item);
264 this.add_delete_button(item);
265 } else if (model.type == 'notebook') {
265 } else if (model.type == 'notebook') {
266 if(this.sessions[path_name] === undefined){
266 if(this.sessions[path_name] === undefined){
267 this.add_delete_button(item);
267 this.add_delete_button(item);
268 } else {
268 } else {
269 this.add_shutdown_button(item, this.sessions[path_name]);
269 this.add_shutdown_button(item, this.sessions[path_name]);
270 }
270 }
271 }
271 }
272 };
272 };
273
273
274
274
275 NotebookList.prototype.add_name_input = function (name, item, icon_type) {
275 NotebookList.prototype.add_name_input = function (name, item, icon_type) {
276 item.data('name', name);
276 item.data('name', name);
277 item.find(".item_icon").addClass(NotebookList.icons[icon_type]).addClass('icon-fixed-width');
277 item.find(".item_icon").addClass(NotebookList.icons[icon_type]).addClass('icon-fixed-width');
278 item.find(".item_name").empty().append(
278 item.find(".item_name").empty().append(
279 $('<input/>')
279 $('<input/>')
280 .addClass("filename_input")
280 .addClass("filename_input")
281 .attr('value', name)
281 .attr('value', name)
282 .attr('size', '30')
282 .attr('size', '30')
283 .attr('type', 'text')
283 .attr('type', 'text')
284 .keyup(function(event){
284 .keyup(function(event){
285 if(event.keyCode == 13){item.find('.upload_button').click();}
285 if(event.keyCode == 13){item.find('.upload_button').click();}
286 else if(event.keyCode == 27){item.remove();}
286 else if(event.keyCode == 27){item.remove();}
287 })
287 })
288 );
288 );
289 };
289 };
290
290
291
291
292 NotebookList.prototype.add_file_data = function (data, item) {
292 NotebookList.prototype.add_file_data = function (data, item) {
293 item.data('filedata', data);
293 item.data('filedata', data);
294 };
294 };
295
295
296
296
297 NotebookList.prototype.add_shutdown_button = function (item, session) {
297 NotebookList.prototype.add_shutdown_button = function (item, session) {
298 var that = this;
298 var that = this;
299 var shutdown_button = $("<button/>").text("Shutdown").addClass("btn btn-xs btn-danger").
299 var shutdown_button = $("<button/>").text("Shutdown").addClass("btn btn-xs btn-danger").
300 click(function (e) {
300 click(function (e) {
301 var settings = {
301 var settings = {
302 processData : false,
302 processData : false,
303 cache : false,
303 cache : false,
304 type : "DELETE",
304 type : "DELETE",
305 dataType : "json",
305 dataType : "json",
306 success : function () {
306 success : function () {
307 that.load_sessions();
307 that.load_sessions();
308 },
308 },
309 error : utils.log_ajax_error,
309 error : utils.log_ajax_error,
310 };
310 };
311 var url = utils.url_join_encode(
311 var url = utils.url_join_encode(
312 that.base_url,
312 that.base_url,
313 'api/sessions',
313 'api/sessions',
314 session
314 session
315 );
315 );
316 $.ajax(url, settings);
316 $.ajax(url, settings);
317 return false;
317 return false;
318 });
318 });
319 // var new_buttons = item.find('a'); // shutdown_button;
319 // var new_buttons = item.find('a'); // shutdown_button;
320 item.find(".item_buttons").text("").append(shutdown_button);
320 item.find(".item_buttons").text("").append(shutdown_button);
321 };
321 };
322
322
323 NotebookList.prototype.add_delete_button = function (item) {
323 NotebookList.prototype.add_delete_button = function (item) {
324 var new_buttons = $('<span/>').addClass("btn-group pull-right");
324 var new_buttons = $('<span/>').addClass("btn-group pull-right");
325 var notebooklist = this;
325 var notebooklist = this;
326 var delete_button = $("<button/>").text("Delete").addClass("btn btn-default btn-xs").
326 var delete_button = $("<button/>").text("Delete").addClass("btn btn-default btn-xs").
327 click(function (e) {
327 click(function (e) {
328 // $(this) is the button that was clicked.
328 // $(this) is the button that was clicked.
329 var that = $(this);
329 var that = $(this);
330 // We use the filename from the parent list_item element's
330 // We use the filename from the parent list_item element's
331 // data because the outer scope's values change as we iterate through the loop.
331 // data because the outer scope's values change as we iterate through the loop.
332 var parent_item = that.parents('div.list_item');
332 var parent_item = that.parents('div.list_item');
333 var nbname = parent_item.data('nbname');
333 var nbname = parent_item.data('nbname');
334 var path = parent_item.data('path');
334 var path = parent_item.data('path');
335 var base_url = utils.get_body_data("baseUrl");
336 var message = 'Are you sure you want to permanently delete the notebook: ' + nbname + '?';
335 var message = 'Are you sure you want to permanently delete the notebook: ' + nbname + '?';
337 dialog.modal({
336 dialog.modal({
338 title : "Delete file",
337 title : "Delete file",
339 body : message,
338 body : message,
340 buttons : {
339 buttons : {
341 Delete : {
340 Delete : {
342 class: "btn-danger",
341 class: "btn-danger",
343 click: function() {
342 click: function() {
344 notebooklist.content_manager.delete_notebook(nbname, path, base_url);
343 notebooklist.content_manager.delete_notebook(nbname, path);
345 }
344 }
346 },
345 },
347 Cancel : {}
346 Cancel : {}
348 }
347 }
349 });
348 });
350 return false;
349 return false;
351 });
350 });
352 item.find(".item_buttons").text("").append(delete_button);
351 item.find(".item_buttons").text("").append(delete_button);
353 };
352 };
354
353
355
354
356 NotebookList.prototype.add_upload_button = function (item, type) {
355 NotebookList.prototype.add_upload_button = function (item, type) {
357 var that = this;
356 var that = this;
358 var upload_button = $('<button/>').text("Upload")
357 var upload_button = $('<button/>').text("Upload")
359 .addClass('btn btn-primary btn-xs upload_button')
358 .addClass('btn btn-primary btn-xs upload_button')
360 .click(function (e) {
359 .click(function (e) {
361 var path = that.notebook_path;
360 var path = that.notebook_path;
362 var filename = item.find('.item_name > input').val();
361 var filename = item.find('.item_name > input').val();
363 var filedata = item.data('filedata');
362 var filedata = item.data('filedata');
364 var format = 'text';
363 var format = 'text';
365 if (filename.length === 0 || filename[0] === '.') {
364 if (filename.length === 0 || filename[0] === '.') {
366 dialog.modal({
365 dialog.modal({
367 title : 'Invalid file name',
366 title : 'Invalid file name',
368 body : "File names must be at least one character and not start with a dot",
367 body : "File names must be at least one character and not start with a dot",
369 buttons : {'OK' : { 'class' : 'btn-primary' }}
368 buttons : {'OK' : { 'class' : 'btn-primary' }}
370 });
369 });
371 return false;
370 return false;
372 }
371 }
373 if (filedata instanceof ArrayBuffer) {
372 if (filedata instanceof ArrayBuffer) {
374 // base64-encode binary file data
373 // base64-encode binary file data
375 var bytes = '';
374 var bytes = '';
376 var buf = new Uint8Array(filedata);
375 var buf = new Uint8Array(filedata);
377 var nbytes = buf.byteLength;
376 var nbytes = buf.byteLength;
378 for (var i=0; i<nbytes; i++) {
377 for (var i=0; i<nbytes; i++) {
379 bytes += String.fromCharCode(buf[i]);
378 bytes += String.fromCharCode(buf[i]);
380 }
379 }
381 filedata = btoa(bytes);
380 filedata = btoa(bytes);
382 format = 'base64';
381 format = 'base64';
383 }
382 }
384 var model = {
383 var model = {
385 path: path,
384 path: path,
386 name: filename
385 name: filename
387 };
386 };
388
387
389 var name_and_ext = utils.splitext(filename);
388 var name_and_ext = utils.splitext(filename);
390 var file_ext = name_and_ext[1];
389 var file_ext = name_and_ext[1];
391 var content_type;
390 var content_type;
392 if (file_ext === '.ipynb') {
391 if (file_ext === '.ipynb') {
393 model.type = 'notebook';
392 model.type = 'notebook';
394 model.format = 'json';
393 model.format = 'json';
395 try {
394 try {
396 model.content = JSON.parse(filedata);
395 model.content = JSON.parse(filedata);
397 } catch (e) {
396 } catch (e) {
398 dialog.modal({
397 dialog.modal({
399 title : 'Cannot upload invalid Notebook',
398 title : 'Cannot upload invalid Notebook',
400 body : "The error was: " + e,
399 body : "The error was: " + e,
401 buttons : {'OK' : {
400 buttons : {'OK' : {
402 'class' : 'btn-primary',
401 'class' : 'btn-primary',
403 click: function () {
402 click: function () {
404 item.remove();
403 item.remove();
405 }
404 }
406 }}
405 }}
407 });
406 });
408 return false;
407 return false;
409 }
408 }
410 content_type = 'application/json';
409 content_type = 'application/json';
411 } else {
410 } else {
412 model.type = 'file';
411 model.type = 'file';
413 model.format = format;
412 model.format = format;
414 model.content = filedata;
413 model.content = filedata;
415 content_type = 'application/octet-stream';
414 content_type = 'application/octet-stream';
416 }
415 }
417 var filedata = item.data('filedata');
416 var filedata = item.data('filedata');
418
417
419 var settings = {
418 var settings = {
420 processData : false,
419 processData : false,
421 cache : false,
420 cache : false,
422 type : 'PUT',
421 type : 'PUT',
423 data : JSON.stringify(model),
422 data : JSON.stringify(model),
424 contentType: content_type,
423 contentType: content_type,
425 success : function (data, status, xhr) {
424 success : function (data, status, xhr) {
426 item.removeClass('new-file');
425 item.removeClass('new-file');
427 that.add_link(model, item);
426 that.add_link(model, item);
428 that.add_delete_button(item);
427 that.add_delete_button(item);
429 that.session_list.load_sessions();
428 that.session_list.load_sessions();
430 },
429 },
431 error : utils.log_ajax_error,
430 error : utils.log_ajax_error,
432 };
431 };
433
432
434 var url = utils.url_join_encode(
433 var url = utils.url_join_encode(
435 that.base_url,
434 that.base_url,
436 'api/contents',
435 'api/contents',
437 that.notebook_path,
436 that.notebook_path,
438 filename
437 filename
439 );
438 );
440
439
441 var exists = false;
440 var exists = false;
442 $.each(that.element.find('.list_item:not(.new-file)'), function(k,v){
441 $.each(that.element.find('.list_item:not(.new-file)'), function(k,v){
443 if ($(v).data('name') === filename) { exists = true; return false; }
442 if ($(v).data('name') === filename) { exists = true; return false; }
444 });
443 });
445 if (exists) {
444 if (exists) {
446 dialog.modal({
445 dialog.modal({
447 title : "Replace file",
446 title : "Replace file",
448 body : 'There is already a file named ' + filename + ', do you want to replace it?',
447 body : 'There is already a file named ' + filename + ', do you want to replace it?',
449 buttons : {
448 buttons : {
450 Overwrite : {
449 Overwrite : {
451 class: "btn-danger",
450 class: "btn-danger",
452 click: function() { $.ajax(url, settings); }
451 click: function() { $.ajax(url, settings); }
453 },
452 },
454 Cancel : {
453 Cancel : {
455 click: function() { item.remove(); }
454 click: function() { item.remove(); }
456 }
455 }
457 }
456 }
458 });
457 });
459 } else {
458 } else {
460 $.ajax(url, settings);
459 $.ajax(url, settings);
461 }
460 }
462
461
463 return false;
462 return false;
464 });
463 });
465 var cancel_button = $('<button/>').text("Cancel")
464 var cancel_button = $('<button/>').text("Cancel")
466 .addClass("btn btn-default btn-xs")
465 .addClass("btn btn-default btn-xs")
467 .click(function (e) {
466 .click(function (e) {
468 item.remove();
467 item.remove();
469 return false;
468 return false;
470 });
469 });
471 item.find(".item_buttons").empty()
470 item.find(".item_buttons").empty()
472 .append(upload_button)
471 .append(upload_button)
473 .append(cancel_button);
472 .append(cancel_button);
474 };
473 };
475
474
476
475
477 // Backwards compatability.
476 // Backwards compatability.
478 IPython.NotebookList = NotebookList;
477 IPython.NotebookList = NotebookList;
479
478
480 return {'NotebookList': NotebookList};
479 return {'NotebookList': NotebookList};
481 });
480 });
General Comments 0
You need to be logged in to leave comments. Login now