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