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