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