##// END OF EJS Templates
"use strict" in most (if not all) our javascript...
Matthias BUSSONNIER -
Show More
@@ -1,83 +1,84
1 1 //----------------------------------------------------------------------------
2 2 // Copyright (C) 2013 The IPython Development Team
3 3 //
4 4 // Distributed under the terms of the BSD License. The full license is in
5 5 // the file COPYING, distributed as part of this software.
6 6 //----------------------------------------------------------------------------
7 7
8 8 //============================================================================
9 9 // Utility for modal dialogs with bootstrap
10 10 //============================================================================
11 11
12 12 IPython.namespace('IPython.dialog');
13 13
14 14 IPython.dialog = (function (IPython) {
15 "use strict";
15 16
16 17 var modal = function (options) {
17 18 var dialog = $("<div/>").addClass("modal").attr("role", "dialog");
18 19 dialog.append(
19 20 $("<div/>")
20 21 .addClass("modal-header")
21 22 .append($("<button>")
22 23 .addClass("close")
23 24 .attr("data-dismiss", "modal")
24 25 .html("&times;")
25 26 ).append(
26 27 $("<h3/>").text(options.title || "")
27 28 )
28 29 ).append(
29 30 $("<div/>").addClass("modal-body").append(
30 31 options.body || $("<p/>")
31 32 )
32 33 );
33 34
34 35 var footer = $("<div/>").addClass("modal-footer");
35 36
36 37 for (var label in options.buttons) {
37 38 var btn_opts = options.buttons[label];
38 39 var button = $("<button/>")
39 40 .addClass("btn")
40 41 .attr("data-dismiss", "modal")
41 42 .text(label);
42 43 if (btn_opts.click) {
43 44 button.click($.proxy(btn_opts.click, dialog));
44 45 }
45 46 if (btn_opts.class) {
46 47 button.addClass(btn_opts.class);
47 48 }
48 49 footer.append(button);
49 50 }
50 51 dialog.append(footer);
51 52 // hook up on-open event
52 53 dialog.on("shown", function() {
53 54 setTimeout(function() {
54 55 footer.find("button").last().focus();
55 56 if (options.open) {
56 57 $.proxy(options.open, dialog)();
57 58 }
58 59 }, 0);
59 60 });
60 61
61 62 // destroy dialog on hide, unless explicitly asked not to
62 63 if (options.destroy == undefined || options.destroy) {
63 64 dialog.on("hidden", function () {
64 65 dialog.remove();
65 66 });
66 67 }
67 68 if (options.reselect_cell !== false) {
68 69 dialog.on("hidden", function () {
69 70 if (IPython.notebook) {
70 71 cell = IPython.notebook.get_selected_cell();
71 72 if (cell) cell.select();
72 73 }
73 74 });
74 75 }
75 76
76 77 return dialog.modal(options);
77 78 }
78 79
79 80 return {
80 81 modal : modal,
81 82 };
82 83
83 84 }(IPython));
@@ -1,31 +1,32
1 1 //----------------------------------------------------------------------------
2 2 // Copyright (C) 2008-2011 The IPython Development Team
3 3 //
4 4 // Distributed under the terms of the BSD License. The full license is in
5 5 // the file COPYING, distributed as part of this software.
6 6 //----------------------------------------------------------------------------
7 7
8 8 //============================================================================
9 9 // Events
10 10 //============================================================================
11 11
12 12 // Give us an object to bind all events to. This object should be created
13 13 // before all other objects so it exists when others register event handlers.
14 14 // To trigger an event handler:
15 15 // $([IPython.events]).trigger('event.Namespace');
16 16 // To handle it:
17 17 // $([IPython.events]).on('event.Namespace',function () {});
18 18
19 19 var IPython = (function (IPython) {
20 "use strict";
20 21
21 22 var utils = IPython.utils;
22 23
23 24 var Events = function () {};
24 25
25 26 IPython.Events = Events;
26 27 IPython.events = new Events();
27 28
28 29 return IPython;
29 30
30 31 }(IPython));
31 32
@@ -1,30 +1,32
1 1 //----------------------------------------------------------------------------
2 2 // Copyright (C) 2008-2011 The IPython Development Team
3 3 //
4 4 // Distributed under the terms of the BSD License. The full license is in
5 5 // the file COPYING, distributed as part of this software.
6 6 //----------------------------------------------------------------------------
7 7
8 8 var IPython = IPython || {};
9 9
10 10 IPython.namespace = function (ns_string) {
11 "use strict";
12
11 13 var parts = ns_string.split('.'),
12 14 parent = IPython,
13 15 i;
14 16
15 17 // String redundant leading global
16 18 if (parts[0] === "IPython") {
17 19 parts = parts.slice(1);
18 20 }
19 21
20 22 for (i=0; i<parts.length; i+=1) {
21 23 // Create property if it doesn't exist
22 24 if (typeof parent[parts[i]] === "undefined") {
23 25 parent[parts[i]] = {};
24 26 }
25 27 }
26 28 return parent;
27 29 };
28 30
29 31
30 32
@@ -1,55 +1,56
1 1 //----------------------------------------------------------------------------
2 2 // Copyright (C) 2008-2011 The IPython Development Team
3 3 //
4 4 // Distributed under the terms of the BSD License. The full license is in
5 5 // the file COPYING, distributed as part of this software.
6 6 //----------------------------------------------------------------------------
7 7
8 8 //============================================================================
9 9 // Global header/site setup.
10 10 //============================================================================
11 11
12 12 var IPython = (function (IPython) {
13 "use strict";
13 14
14 15 var Page = function () {
15 16 this.style();
16 17 this.bind_events();
17 18 };
18 19
19 20 Page.prototype.style = function () {
20 21 $('div#header').addClass('border-box-sizing');
21 22 $('div#site').addClass('border-box-sizing');
22 23 };
23 24
24 25
25 26 Page.prototype.bind_events = function () {
26 27 };
27 28
28 29
29 30 Page.prototype.show = function () {
30 31 // The header and site divs start out hidden to prevent FLOUC.
31 32 // Main scripts should call this method after styling everything.
32 33 this.show_header();
33 34 this.show_site();
34 35 };
35 36
36 37
37 38 Page.prototype.show_header = function () {
38 39 // The header and site divs start out hidden to prevent FLOUC.
39 40 // Main scripts should call this method after styling everything.
40 41 $('div#header').css('display','block');
41 42 };
42 43
43 44
44 45 Page.prototype.show_site = function () {
45 46 // The header and site divs start out hidden to prevent FLOUC.
46 47 // Main scripts should call this method after styling everything.
47 48 $('div#site').css('display','block');
48 49 };
49 50
50 51
51 52 IPython.Page = Page;
52 53
53 54 return IPython;
54 55
55 56 }(IPython));
@@ -1,19 +1,20
1 1 //----------------------------------------------------------------------------
2 2 // Copyright (C) 2008-2011 The IPython Development Team
3 3 //
4 4 // Distributed under the terms of the BSD License. The full license is in
5 5 // the file COPYING, distributed as part of this software.
6 6 //----------------------------------------------------------------------------
7 7
8 8 //============================================================================
9 9 // On document ready
10 10 //============================================================================
11 11
12 12
13 13 $(document).ready(function () {
14 "use strict";
14 15
15 16 IPython.page = new IPython.Page();
16 17 IPython.page.show();
17 18
18 19 });
19 20
@@ -1,390 +1,391
1 1 //----------------------------------------------------------------------------
2 2 // Copyright (C) 2008-2012 The IPython Development Team
3 3 //
4 4 // Distributed under the terms of the BSD License. The full license is in
5 5 // the file COPYING, distributed as part of this software.
6 6 //----------------------------------------------------------------------------
7 7
8 8 //============================================================================
9 9 // Utilities
10 10 //============================================================================
11 11 IPython.namespace('IPython.utils');
12 12
13 13 IPython.utils = (function (IPython) {
14 "use strict";
14 15
15 16 //============================================================================
16 17 // Cross-browser RegEx Split
17 18 //============================================================================
18 19
19 20 // This code has been MODIFIED from the code licensed below to not replace the
20 21 // default browser split. The license is reproduced here.
21 22
22 23 // see http://blog.stevenlevithan.com/archives/cross-browser-split for more info:
23 24 /*!
24 25 * Cross-Browser Split 1.1.1
25 26 * Copyright 2007-2012 Steven Levithan <stevenlevithan.com>
26 27 * Available under the MIT License
27 28 * ECMAScript compliant, uniform cross-browser split method
28 29 */
29 30
30 31 /**
31 32 * Splits a string into an array of strings using a regex or string
32 33 * separator. Matches of the separator are not included in the result array.
33 34 * However, if `separator` is a regex that contains capturing groups,
34 35 * backreferences are spliced into the result each time `separator` is
35 36 * matched. Fixes browser bugs compared to the native
36 37 * `String.prototype.split` and can be used reliably cross-browser.
37 38 * @param {String} str String to split.
38 39 * @param {RegExp|String} separator Regex or string to use for separating
39 40 * the string.
40 41 * @param {Number} [limit] Maximum number of items to include in the result
41 42 * array.
42 43 * @returns {Array} Array of substrings.
43 44 * @example
44 45 *
45 46 * // Basic use
46 47 * regex_split('a b c d', ' ');
47 48 * // -> ['a', 'b', 'c', 'd']
48 49 *
49 50 * // With limit
50 51 * regex_split('a b c d', ' ', 2);
51 52 * // -> ['a', 'b']
52 53 *
53 54 * // Backreferences in result array
54 55 * regex_split('..word1 word2..', /([a-z]+)(\d+)/i);
55 56 * // -> ['..', 'word', '1', ' ', 'word', '2', '..']
56 57 */
57 58 var regex_split = function (str, separator, limit) {
58 59 // If `separator` is not a regex, use `split`
59 60 if (Object.prototype.toString.call(separator) !== "[object RegExp]") {
60 61 return split.call(str, separator, limit);
61 62 }
62 63 var output = [],
63 64 flags = (separator.ignoreCase ? "i" : "") +
64 65 (separator.multiline ? "m" : "") +
65 66 (separator.extended ? "x" : "") + // Proposed for ES6
66 67 (separator.sticky ? "y" : ""), // Firefox 3+
67 68 lastLastIndex = 0,
68 69 // Make `global` and avoid `lastIndex` issues by working with a copy
69 70 separator = new RegExp(separator.source, flags + "g"),
70 71 separator2, match, lastIndex, lastLength;
71 72 str += ""; // Type-convert
72 73
73 74 var compliantExecNpcg = typeof(/()??/.exec("")[1]) === "undefined";
74 75 if (!compliantExecNpcg) {
75 76 // Doesn't need flags gy, but they don't hurt
76 77 separator2 = new RegExp("^" + separator.source + "$(?!\\s)", flags);
77 78 }
78 79 /* Values for `limit`, per the spec:
79 80 * If undefined: 4294967295 // Math.pow(2, 32) - 1
80 81 * If 0, Infinity, or NaN: 0
81 82 * If positive number: limit = Math.floor(limit); if (limit > 4294967295) limit -= 4294967296;
82 83 * If negative number: 4294967296 - Math.floor(Math.abs(limit))
83 84 * If other: Type-convert, then use the above rules
84 85 */
85 86 limit = typeof(limit) === "undefined" ?
86 87 -1 >>> 0 : // Math.pow(2, 32) - 1
87 88 limit >>> 0; // ToUint32(limit)
88 89 while (match = separator.exec(str)) {
89 90 // `separator.lastIndex` is not reliable cross-browser
90 91 lastIndex = match.index + match[0].length;
91 92 if (lastIndex > lastLastIndex) {
92 93 output.push(str.slice(lastLastIndex, match.index));
93 94 // Fix browsers whose `exec` methods don't consistently return `undefined` for
94 95 // nonparticipating capturing groups
95 96 if (!compliantExecNpcg && match.length > 1) {
96 97 match[0].replace(separator2, function () {
97 98 for (var i = 1; i < arguments.length - 2; i++) {
98 99 if (typeof(arguments[i]) === "undefined") {
99 100 match[i] = undefined;
100 101 }
101 102 }
102 103 });
103 104 }
104 105 if (match.length > 1 && match.index < str.length) {
105 106 Array.prototype.push.apply(output, match.slice(1));
106 107 }
107 108 lastLength = match[0].length;
108 109 lastLastIndex = lastIndex;
109 110 if (output.length >= limit) {
110 111 break;
111 112 }
112 113 }
113 114 if (separator.lastIndex === match.index) {
114 115 separator.lastIndex++; // Avoid an infinite loop
115 116 }
116 117 }
117 118 if (lastLastIndex === str.length) {
118 119 if (lastLength || !separator.test("")) {
119 120 output.push("");
120 121 }
121 122 } else {
122 123 output.push(str.slice(lastLastIndex));
123 124 }
124 125 return output.length > limit ? output.slice(0, limit) : output;
125 126 };
126 127
127 128 //============================================================================
128 129 // End contributed Cross-browser RegEx Split
129 130 //============================================================================
130 131
131 132
132 133 var uuid = function () {
133 134 // http://www.ietf.org/rfc/rfc4122.txt
134 135 var s = [];
135 136 var hexDigits = "0123456789ABCDEF";
136 137 for (var i = 0; i < 32; i++) {
137 138 s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
138 139 }
139 140 s[12] = "4"; // bits 12-15 of the time_hi_and_version field to 0010
140 141 s[16] = hexDigits.substr((s[16] & 0x3) | 0x8, 1); // bits 6-7 of the clock_seq_hi_and_reserved to 01
141 142
142 143 var uuid = s.join("");
143 144 return uuid;
144 145 };
145 146
146 147
147 148 //Fix raw text to parse correctly in crazy XML
148 149 function xmlencode(string) {
149 150 return string.replace(/\&/g,'&'+'amp;')
150 151 .replace(/</g,'&'+'lt;')
151 152 .replace(/>/g,'&'+'gt;')
152 153 .replace(/\'/g,'&'+'apos;')
153 154 .replace(/\"/g,'&'+'quot;')
154 155 .replace(/`/g,'&'+'#96;');
155 156 }
156 157
157 158
158 159 //Map from terminal commands to CSS classes
159 160 var ansi_colormap = {
160 161 "01":"ansibold",
161 162
162 163 "30":"ansiblack",
163 164 "31":"ansired",
164 165 "32":"ansigreen",
165 166 "33":"ansiyellow",
166 167 "34":"ansiblue",
167 168 "35":"ansipurple",
168 169 "36":"ansicyan",
169 170 "37":"ansigray",
170 171
171 172 "40":"ansibgblack",
172 173 "41":"ansibgred",
173 174 "42":"ansibggreen",
174 175 "43":"ansibgyellow",
175 176 "44":"ansibgblue",
176 177 "45":"ansibgpurple",
177 178 "46":"ansibgcyan",
178 179 "47":"ansibggray"
179 180 };
180 181
181 182 function _process_numbers(attrs, numbers) {
182 183 // process ansi escapes
183 184 var n = numbers.shift();
184 185 if (ansi_colormap[n]) {
185 186 if ( ! attrs["class"] ) {
186 187 attrs["class"] = ansi_colormap[n];
187 188 } else {
188 189 attrs["class"] += " " + ansi_colormap[n];
189 190 }
190 191 } else if (n == "38" || n == "48") {
191 192 // VT100 256 color or 24 bit RGB
192 193 if (numbers.length < 2) {
193 194 console.log("Not enough fields for VT100 color", numbers);
194 195 return;
195 196 }
196 197
197 198 var index_or_rgb = numbers.shift();
198 199 var r,g,b;
199 200 if (index_or_rgb == "5") {
200 201 // 256 color
201 202 var idx = parseInt(numbers.shift());
202 203 if (idx < 16) {
203 204 // indexed ANSI
204 205 // ignore bright / non-bright distinction
205 206 idx = idx % 8;
206 207 var ansiclass = ansi_colormap[n[0] + (idx % 8).toString()];
207 208 if ( ! attrs["class"] ) {
208 209 attrs["class"] = ansiclass;
209 210 } else {
210 211 attrs["class"] += " " + ansiclass;
211 212 }
212 213 return;
213 214 } else if (idx < 232) {
214 215 // 216 color 6x6x6 RGB
215 216 idx = idx - 16;
216 217 b = idx % 6;
217 218 g = Math.floor(idx / 6) % 6;
218 219 r = Math.floor(idx / 36) % 6;
219 220 // convert to rgb
220 221 r = (r * 51);
221 222 g = (g * 51);
222 223 b = (b * 51);
223 224 } else {
224 225 // grayscale
225 226 idx = idx - 231;
226 227 // it's 1-24 and should *not* include black or white,
227 228 // so a 26 point scale
228 229 r = g = b = Math.floor(idx * 256 / 26);
229 230 }
230 231 } else if (index_or_rgb == "2") {
231 232 // Simple 24 bit RGB
232 233 if (numbers.length > 3) {
233 234 console.log("Not enough fields for RGB", numbers);
234 235 return;
235 236 }
236 237 r = numbers.shift();
237 238 g = numbers.shift();
238 239 b = numbers.shift();
239 240 } else {
240 241 console.log("unrecognized control", numbers);
241 242 return;
242 243 }
243 244 if (r !== undefined) {
244 245 // apply the rgb color
245 246 var line;
246 247 if (n == "38") {
247 248 line = "color: ";
248 249 } else {
249 250 line = "background-color: ";
250 251 }
251 252 line = line + "rgb(" + r + "," + g + "," + b + ");"
252 253 if ( !attrs["style"] ) {
253 254 attrs["style"] = line;
254 255 } else {
255 256 attrs["style"] += " " + line;
256 257 }
257 258 }
258 259 }
259 260 }
260 261
261 262 function ansispan(str) {
262 263 // ansispan function adapted from github.com/mmalecki/ansispan (MIT License)
263 264 // regular ansi escapes (using the table above)
264 265 return str.replace(/\033\[(0?[01]|22|39)?([;\d]+)?m/g, function(match, prefix, pattern) {
265 266 if (!pattern) {
266 267 // [(01|22|39|)m close spans
267 268 return "</span>";
268 269 }
269 270 // consume sequence of color escapes
270 271 var numbers = pattern.match(/\d+/g);
271 272 var attrs = {};
272 273 while (numbers.length > 0) {
273 274 _process_numbers(attrs, numbers);
274 275 }
275 276
276 277 var span = "<span ";
277 278 for (var attr in attrs) {
278 279 var value = attrs[attr];
279 280 span = span + " " + attr + '="' + attrs[attr] + '"';
280 281 }
281 282 return span + ">";
282 283 });
283 284 };
284 285
285 286 // Transform ANSI color escape codes into HTML <span> tags with css
286 287 // classes listed in the above ansi_colormap object. The actual color used
287 288 // are set in the css file.
288 289 function fixConsole(txt) {
289 290 txt = xmlencode(txt);
290 291 var re = /\033\[([\dA-Fa-f;]*?)m/;
291 292 var opened = false;
292 293 var cmds = [];
293 294 var opener = "";
294 295 var closer = "";
295 296
296 297 // Strip all ANSI codes that are not color related. Matches
297 298 // all ANSI codes that do not end with "m".
298 299 var ignored_re = /(?=(\033\[[\d;=]*[a-ln-zA-Z]{1}))\1(?!m)/g;
299 300 txt = txt.replace(ignored_re, "");
300 301
301 302 // color ansi codes
302 303 txt = ansispan(txt);
303 304 return txt;
304 305 }
305 306
306 307 // Remove chunks that should be overridden by the effect of
307 308 // carriage return characters
308 309 function fixCarriageReturn(txt) {
309 310 var tmp = txt;
310 311 do {
311 312 txt = tmp;
312 313 tmp = txt.replace(/\r+\n/gm, '\n'); // \r followed by \n --> newline
313 314 tmp = tmp.replace(/^.*\r+/gm, ''); // Other \r --> clear line
314 315 } while (tmp.length < txt.length);
315 316 return txt;
316 317 }
317 318
318 319 // Locate any URLs and convert them to a anchor tag
319 320 function autoLinkUrls(txt) {
320 321 return txt.replace(/(^|\s)(https?|ftp)(:[^'">\s]+)/gi,
321 322 "$1<a target=\"_blank\" href=\"$2$3\">$2$3</a>");
322 323 }
323 324
324 325 // some keycodes that seem to be platform/browser independent
325 326 var keycodes = {
326 327 BACKSPACE: 8,
327 328 TAB : 9,
328 329 ENTER : 13,
329 330 SHIFT : 16,
330 331 CTRL : 17,
331 332 CONTROL : 17,
332 333 ALT : 18,
333 334 CAPS_LOCK: 20,
334 335 ESC : 27,
335 336 SPACE : 32,
336 337 PGUP : 33,
337 338 PGDOWN : 34,
338 339 END : 35,
339 340 HOME : 36,
340 341 LEFT_ARROW: 37,
341 342 LEFTARROW: 37,
342 343 LEFT : 37,
343 344 UP_ARROW : 38,
344 345 UPARROW : 38,
345 346 UP : 38,
346 347 RIGHT_ARROW:39,
347 348 RIGHTARROW:39,
348 349 RIGHT : 39,
349 350 DOWN_ARROW: 40,
350 351 DOWNARROW: 40,
351 352 DOWN : 40,
352 353 // all three of these keys may be COMMAND on OS X:
353 354 LEFT_SUPER : 91,
354 355 RIGHT_SUPER : 92,
355 356 COMMAND : 93,
356 357 };
357 358
358 359
359 360 var points_to_pixels = function (points) {
360 361 // A reasonably good way of converting between points and pixels.
361 362 var test = $('<div style="display: none; width: 10000pt; padding:0; border:0;"></div>');
362 363 $(body).append(test);
363 364 var pixel_per_point = test.width()/10000;
364 365 test.remove();
365 366 return Math.floor(points*pixel_per_point);
366 367 };
367 368
368 369 // http://stackoverflow.com/questions/2400935/browser-detection-in-javascript
369 browser = (function() {
370 var browser = (function() {
370 371 var N= navigator.appName, ua= navigator.userAgent, tem;
371 372 var M= ua.match(/(opera|chrome|safari|firefox|msie)\/?\s*(\.?\d+(\.\d+)*)/i);
372 373 if (M && (tem= ua.match(/version\/([\.\d]+)/i))!= null) M[2]= tem[1];
373 374 M= M? [M[1], M[2]]: [N, navigator.appVersion,'-?'];
374 375 return M;
375 376 })();
376 377
377 378
378 379 return {
379 380 regex_split : regex_split,
380 381 uuid : uuid,
381 382 fixConsole : fixConsole,
382 383 keycodes : keycodes,
383 384 fixCarriageReturn : fixCarriageReturn,
384 385 autoLinkUrls : autoLinkUrls,
385 386 points_to_pixels : points_to_pixels,
386 387 browser : browser
387 388 };
388 389
389 390 }(IPython));
390 391
@@ -1,335 +1,336
1 1 //----------------------------------------------------------------------------
2 2 // Copyright (C) 2008-2011 The IPython Development Team
3 3 //
4 4 // Distributed under the terms of the BSD License. The full license is in
5 5 // the file COPYING, distributed as part of this software.
6 6 //----------------------------------------------------------------------------
7 7
8 8 //============================================================================
9 9 // Cell
10 10 //============================================================================
11 11 /**
12 12 * An extendable module that provide base functionnality to create cell for notebook.
13 13 * @module IPython
14 14 * @namespace IPython
15 15 * @submodule Cell
16 16 */
17 17
18 18 var IPython = (function (IPython) {
19 "use strict";
19 20
20 21 var utils = IPython.utils;
21 22
22 23 /**
23 24 * The Base `Cell` class from which to inherit
24 25 * @class Cell
25 26 **/
26 27
27 28 /*
28 29 * @constructor
29 30 *
30 31 * @param {object|undefined} [options]
31 32 * @param [options.cm_config] {object} config to pass to CodeMirror, will extend default parameters
32 33 */
33 34 var Cell = function (options) {
34 35
35 36 options = this.mergeopt(Cell, options)
36 37 // superclass default overwrite our default
37 38
38 39 this.placeholder = options.placeholder || '';
39 40 this.read_only = options.cm_config.readOnly;
40 41 this.selected = false;
41 42 this.element = null;
42 43 this.metadata = {};
43 44 // load this from metadata later ?
44 45 this.user_highlight = 'auto';
45 46 this.cm_config = options.cm_config;
46 47 this.create_element();
47 48 if (this.element !== null) {
48 49 this.element.data("cell", this);
49 50 this.bind_events();
50 51 }
51 52 this.cell_id = utils.uuid();
52 53 this._options = options;
53 54 };
54 55
55 56 Cell.options_default = {
56 57 cm_config : {
57 58 indentUnit : 4,
58 59 readOnly: false,
59 60 theme: "default"
60 61 }
61 62 };
62 63
63 64 // FIXME: Workaround CM Bug #332 (Safari segfault on drag)
64 65 // by disabling drag/drop altogether on Safari
65 66 // https://github.com/marijnh/CodeMirror/issues/332
66 67
67 68 if (utils.browser[0] == "Safari") {
68 69 Cell.options_default.cm_config.dragDrop = false;
69 70 }
70 71
71 72 Cell.prototype.mergeopt = function(_class, options, overwrite){
72 73 overwrite = overwrite || {};
73 74 return $.extend(true, {}, _class.options_default, options, overwrite)
74 75
75 76 }
76 77
77 78
78 79
79 80 /**
80 81 * Empty. Subclasses must implement create_element.
81 82 * This should contain all the code to create the DOM element in notebook
82 83 * and will be called by Base Class constructor.
83 84 * @method create_element
84 85 */
85 86 Cell.prototype.create_element = function () {
86 87 };
87 88
88 89
89 90 /**
90 91 * Subclasses can implement override bind_events.
91 92 * Be carefull to call the parent method when overwriting as it fires event.
92 93 * this will be triggerd after create_element in constructor.
93 94 * @method bind_events
94 95 */
95 96 Cell.prototype.bind_events = function () {
96 97 var that = this;
97 98 // We trigger events so that Cell doesn't have to depend on Notebook.
98 99 that.element.click(function (event) {
99 100 if (that.selected === false) {
100 101 $([IPython.events]).trigger('select.Cell', {'cell':that});
101 102 }
102 103 });
103 104 that.element.focusin(function (event) {
104 105 if (that.selected === false) {
105 106 $([IPython.events]).trigger('select.Cell', {'cell':that});
106 107 }
107 108 });
108 109 if (this.code_mirror) {
109 110 this.code_mirror.on("change", function(cm, change) {
110 111 $([IPython.events]).trigger("set_dirty.Notebook", {value: true});
111 112 });
112 113 }
113 114 };
114 115
115 116 /**
116 117 * Triger typsetting of math by mathjax on current cell element
117 118 * @method typeset
118 119 */
119 120 Cell.prototype.typeset = function () {
120 121 if (window.MathJax){
121 122 var cell_math = this.element.get(0);
122 123 MathJax.Hub.Queue(["Typeset", MathJax.Hub, cell_math]);
123 124 }
124 125 };
125 126
126 127 /**
127 128 * should be triggerd when cell is selected
128 129 * @method select
129 130 */
130 131 Cell.prototype.select = function () {
131 132 this.element.addClass('selected');
132 133 this.selected = true;
133 134 };
134 135
135 136
136 137 /**
137 138 * should be triggerd when cell is unselected
138 139 * @method unselect
139 140 */
140 141 Cell.prototype.unselect = function () {
141 142 this.element.removeClass('selected');
142 143 this.selected = false;
143 144 };
144 145
145 146 /**
146 147 * should be overritten by subclass
147 148 * @method get_text
148 149 */
149 150 Cell.prototype.get_text = function () {
150 151 };
151 152
152 153 /**
153 154 * should be overritten by subclass
154 155 * @method set_text
155 156 * @param {string} text
156 157 */
157 158 Cell.prototype.set_text = function (text) {
158 159 };
159 160
160 161 /**
161 162 * Refresh codemirror instance
162 163 * @method refresh
163 164 */
164 165 Cell.prototype.refresh = function () {
165 166 this.code_mirror.refresh();
166 167 };
167 168
168 169
169 170 /**
170 171 * should be overritten by subclass
171 172 * @method edit
172 173 **/
173 174 Cell.prototype.edit = function () {
174 175 };
175 176
176 177
177 178 /**
178 179 * should be overritten by subclass
179 180 * @method render
180 181 **/
181 182 Cell.prototype.render = function () {
182 183 };
183 184
184 185 /**
185 186 * should be overritten by subclass
186 187 * serialise cell to json.
187 188 * @method toJSON
188 189 **/
189 190 Cell.prototype.toJSON = function () {
190 191 var data = {};
191 192 data.metadata = this.metadata;
192 193 return data;
193 194 };
194 195
195 196
196 197 /**
197 198 * should be overritten by subclass
198 199 * @method fromJSON
199 200 **/
200 201 Cell.prototype.fromJSON = function (data) {
201 202 if (data.metadata !== undefined) {
202 203 this.metadata = data.metadata;
203 204 }
204 205 this.celltoolbar.rebuild();
205 206 };
206 207
207 208
208 209 /**
209 210 * can the cell be splitted in 2 cells.
210 211 * @method is_splittable
211 212 **/
212 213 Cell.prototype.is_splittable = function () {
213 214 return true;
214 215 };
215 216
216 217
217 218 /**
218 219 * @return {String} - the text before the cursor
219 220 * @method get_pre_cursor
220 221 **/
221 222 Cell.prototype.get_pre_cursor = function () {
222 223 var cursor = this.code_mirror.getCursor();
223 224 var text = this.code_mirror.getRange({line:0, ch:0}, cursor);
224 225 text = text.replace(/^\n+/, '').replace(/\n+$/, '');
225 226 return text;
226 227 }
227 228
228 229
229 230 /**
230 231 * @return {String} - the text after the cursor
231 232 * @method get_post_cursor
232 233 **/
233 234 Cell.prototype.get_post_cursor = function () {
234 235 var cursor = this.code_mirror.getCursor();
235 236 var last_line_num = this.code_mirror.lineCount()-1;
236 237 var last_line_len = this.code_mirror.getLine(last_line_num).length;
237 238 var end = {line:last_line_num, ch:last_line_len}
238 239 var text = this.code_mirror.getRange(cursor, end);
239 240 text = text.replace(/^\n+/, '').replace(/\n+$/, '');
240 241 return text;
241 242 };
242 243
243 244 /**
244 245 * Show/Hide CodeMirror LineNumber
245 246 * @method show_line_numbers
246 247 *
247 248 * @param value {Bool} show (true), or hide (false) the line number in CodeMirror
248 249 **/
249 250 Cell.prototype.show_line_numbers = function (value) {
250 251 this.code_mirror.setOption('lineNumbers', value);
251 252 this.code_mirror.refresh();
252 253 };
253 254
254 255 /**
255 256 * Toggle CodeMirror LineNumber
256 257 * @method toggle_line_numbers
257 258 **/
258 259 Cell.prototype.toggle_line_numbers = function () {
259 260 var val = this.code_mirror.getOption('lineNumbers');
260 261 this.show_line_numbers(!val);
261 262 };
262 263
263 264 /**
264 265 * Force codemirror highlight mode
265 266 * @method force_highlight
266 267 * @param {object} - CodeMirror mode
267 268 **/
268 269 Cell.prototype.force_highlight = function(mode) {
269 270 this.user_highlight = mode;
270 271 this.auto_highlight();
271 272 };
272 273
273 274 /**
274 275 * Try to autodetect cell highlight mode, or use selected mode
275 276 * @methods _auto_highlight
276 277 * @private
277 278 * @param {String|object|undefined} - CodeMirror mode | 'auto'
278 279 **/
279 280 Cell.prototype._auto_highlight = function (modes) {
280 281 //Here we handle manually selected modes
281 282 if( this.user_highlight != undefined && this.user_highlight != 'auto' )
282 283 {
283 284 var mode = this.user_highlight;
284 285 CodeMirror.autoLoadMode(this.code_mirror, mode);
285 286 this.code_mirror.setOption('mode', mode);
286 287 return;
287 288 }
288 289 var first_line = this.code_mirror.getLine(0);
289 290 // loop on every pairs
290 291 for( var mode in modes) {
291 292 var regs = modes[mode]['reg'];
292 293 // only one key every time but regexp can't be keys...
293 294 for(var reg in regs ) {
294 295 // here we handle non magic_modes
295 296 if(first_line.match(regs[reg]) != null) {
296 297 if (mode.search('magic_') != 0) {
297 298 this.code_mirror.setOption('mode', mode);
298 299 CodeMirror.autoLoadMode(this.code_mirror, mode);
299 300 return;
300 301 }
301 302 var open = modes[mode]['open']|| "%%";
302 303 var close = modes[mode]['close']|| "%%end";
303 304 var mmode = mode;
304 305 mode = mmode.substr(6);
305 306 CodeMirror.autoLoadMode(this.code_mirror, mode);
306 307 // create on the fly a mode that swhitch between
307 308 // plain/text and smth else otherwise `%%` is
308 309 // source of some highlight issues.
309 310 // we use patchedGetMode to circumvent a bug in CM
310 311 CodeMirror.defineMode(mmode , function(config) {
311 312 return CodeMirror.multiplexingMode(
312 313 CodeMirror.patchedGetMode(config, 'text/plain'),
313 314 // always set someting on close
314 315 {open: open, close: close,
315 316 mode: CodeMirror.patchedGetMode(config, mode),
316 317 delimStyle: "delimit"
317 318 }
318 319 );
319 320 });
320 321 this.code_mirror.setOption('mode', mmode);
321 322 return;
322 323 }
323 324 }
324 325 }
325 326 // fallback on default (python)
326 327 var default_mode = this.default_mode || 'text/plain';
327 328 this.code_mirror.setOption('mode', default_mode);
328 329 };
329 330
330 331 IPython.Cell = Cell;
331 332
332 333 return IPython;
333 334
334 335 }(IPython));
335 336
@@ -1,16 +1,17
1 1 // IPython mode is just a slightly altered Python Mode with `?` beeing a extra
2 2 // single operator. Here we define `ipython` mode in the require `python`
3 3 // callback to auto-load python mode, which is more likely not the best things
4 4 // to do, but at least the simple one for now.
5 5
6 6 CodeMirror.requireMode('python',function(){
7 "use strict";
7 8
8 9 CodeMirror.defineMode("ipython", function(conf, parserConf) {
9 10
10 11 parserConf.singleOperators = new RegExp("^[\\+\\-\\*/%&|\\^~<>!\\?]");
11 12 parserConf.name = 'python'
12 13 return CodeMirror.getMode(conf, parserConf);
13 14 }, 'python');
14 15
15 16 CodeMirror.defineMIME("text/x-ipython", "ipython");
16 17 })
@@ -1,78 +1,79
1 1 //----------------------------------------------------------------------------
2 2 // Copyright (C) 2012 The IPython Development Team
3 3 //
4 4 // Distributed under the terms of the BSD License. The full license is in
5 5 // the file COPYING, distributed as part of this software.
6 6 //----------------------------------------------------------------------------
7 7
8 8 //============================================================================
9 9 // Notebook
10 10 //============================================================================
11 11
12 12 /**
13 13 * @module IPython
14 14 * @namespace IPython
15 15 **/
16 16
17 17 var IPython = (function (IPython) {
18 "use strict";
18 19 /**
19 20 * A place where some stuff can be confugured.
20 21 *
21 22 * @class config
22 23 * @static
23 24 *
24 25 **/
25 26 var default_config = {
26 27 /**
27 28 * Dictionary of object to autodetect highlight mode for code cell.
28 29 * Item of the dictionnary should take the form :
29 30 *
30 31 * key : {'reg':[list_of_regexp]}
31 32 *
32 33 * where `key` will be the code mirror mode name
33 34 * and `list_of_regexp` should be a list of regext that should match
34 35 * the first line of the cell to trigger this mode.
35 36 *
36 37 * if `key` is prefixed by the `magic_` prefix the codemirror `mode`
37 38 * will be applied only at the end of the first line
38 39 *
39 40 * @attribute cell_magic_highlight
40 41 * @example
41 42 * This would trigger javascript mode
42 43 * from the second line if first line start with `%%javascript` or `%%jsmagic`
43 44 *
44 45 * cell_magic_highlight['magic_javascript'] = {'reg':[/^%%javascript/,/^%%jsmagic/]}
45 46 * @example
46 47 * This would trigger javascript mode
47 48 * from the second line if first line start with `var`
48 49 *
49 50 * cell_magic_highlight['javascript'] = {'reg':[/^var/]}
50 51 */
51 52 cell_magic_highlight : {
52 53 'magic_javascript':{'reg':[/^%%javascript/]}
53 54 ,'magic_perl' :{'reg':[/^%%perl/]}
54 55 ,'magic_ruby' :{'reg':[/^%%ruby/]}
55 56 ,'magic_python' :{'reg':[/^%%python3?/]}
56 57 ,'magic_shell' :{'reg':[/^%%bash/]}
57 58 ,'magic_r' :{'reg':[/^%%R/]}
58 59 },
59 60
60 61 /**
61 62 * same as `cell_magic_highlight` but for raw cells
62 63 * @attribute raw_cell_highlight
63 64 */
64 65 raw_cell_highlight : {
65 66 'diff' :{'reg':[/^diff/]}
66 67 },
67 68
68 69 tooltip_on_tab : true,
69 70 };
70 71
71 72 // use the same method to merge user configuration
72 73 IPython.config = {};
73 74 $.extend(IPython.config, default_config);
74 75
75 76 return IPython;
76 77
77 78 }(IPython));
78 79
@@ -1,62 +1,63
1 1 //----------------------------------------------------------------------------
2 2 // Copyright (C) 2008-2011 The IPython Development Team
3 3 //
4 4 // Distributed under the terms of the BSD License. The full license is in
5 5 // the file COPYING, distributed as part of this software.
6 6 //----------------------------------------------------------------------------
7 7
8 8 //============================================================================
9 9 // Layout
10 10 //============================================================================
11 11
12 12 var IPython = (function (IPython) {
13 "use strict";
13 14
14 15 var LayoutManager = function () {
15 16 this.bind_events();
16 17 };
17 18
18 19
19 20 LayoutManager.prototype.bind_events = function () {
20 21 $(window).resize($.proxy(this.do_resize,this));
21 22 };
22 23
23 24 LayoutManager.prototype.app_height = function() {
24 25 var win = $(window);
25 26 var w = win.width();
26 27 var h = win.height();
27 28 var header_height;
28 29 if ($('div#header').css('display') === 'none') {
29 30 header_height = 0;
30 31 } else {
31 32 header_height = $('div#header').outerHeight(true);
32 33 }
33 34 var menubar_height = $('div#menubar').outerHeight(true);
34 35 var toolbar_height;
35 36 if ($('div#maintoolbar').css('display') === 'none') {
36 37 toolbar_height = 0;
37 38 } else {
38 39 toolbar_height = $('div#maintoolbar').outerHeight(true);
39 40 }
40 41 return h-header_height-menubar_height-toolbar_height; // content height
41 42 }
42 43
43 44 LayoutManager.prototype.do_resize = function () {
44 45 var app_height = this.app_height() // content height
45 46
46 47 $('#ipython-main-app').height(app_height); // content+padding+border height
47 48
48 49 var pager_height = IPython.pager.percentage_height*app_height;
49 50 var pager_splitter_height = $('div#pager_splitter').outerHeight(true);
50 51 $('div#pager').outerHeight(pager_height);
51 52 if (IPython.pager.expanded) {
52 53 $('div#notebook').outerHeight(app_height-pager_height-pager_splitter_height);
53 54 } else {
54 55 $('div#notebook').outerHeight(app_height-pager_splitter_height);
55 56 }
56 57 };
57 58
58 59 IPython.LayoutManager = LayoutManager;
59 60
60 61 return IPython;
61 62
62 63 }(IPython));
@@ -1,206 +1,207
1 1 //----------------------------------------------------------------------------
2 2 // Copyright (C) 2011 The IPython Development Team
3 3 //
4 4 // Distributed under the terms of the BSD License. The full license is in
5 5 // the file COPYING, distributed as part of this software.
6 6 //----------------------------------------------------------------------------
7 7
8 8 //============================================================================
9 9 // ToolBar
10 10 //============================================================================
11 11
12 12 var IPython = (function (IPython) {
13 "use strict";
13 14
14 15 var MainToolBar = function (selector) {
15 16 IPython.ToolBar.apply(this, arguments);
16 17 this.construct();
17 18 this.add_celltype_list();
18 19 this.add_celltoolbar_list();
19 20 this.bind_events();
20 21 };
21 22
22 23 MainToolBar.prototype = new IPython.ToolBar();
23 24
24 25 MainToolBar.prototype.construct = function () {
25 26 this.add_buttons_group([
26 27 {
27 28 id : 'save_b',
28 29 label : 'Save and Checkpoint',
29 30 icon : 'icon-save',
30 31 callback : function () {
31 32 IPython.notebook.save_checkpoint();
32 33 }
33 34 }
34 35 ]);
35 36 this.add_buttons_group([
36 37 {
37 38 id : 'cut_b',
38 39 label : 'Cut Cell',
39 40 icon : 'icon-cut',
40 41 callback : function () {
41 42 IPython.notebook.cut_cell();
42 43 }
43 44 },
44 45 {
45 46 id : 'copy_b',
46 47 label : 'Copy Cell',
47 48 icon : 'icon-copy',
48 49 callback : function () {
49 50 IPython.notebook.copy_cell();
50 51 }
51 52 },
52 53 {
53 54 id : 'paste_b',
54 55 label : 'Paste Cell Below',
55 56 icon : 'icon-paste',
56 57 callback : function () {
57 58 IPython.notebook.paste_cell_below();
58 59 }
59 60 }
60 61 ],'cut_copy_paste');
61 62
62 63 this.add_buttons_group([
63 64 {
64 65 id : 'move_up_b',
65 66 label : 'Move Cell Up',
66 67 icon : 'icon-arrow-up',
67 68 callback : function () {
68 69 IPython.notebook.move_cell_up();
69 70 }
70 71 },
71 72 {
72 73 id : 'move_down_b',
73 74 label : 'Move Cell Down',
74 75 icon : 'icon-arrow-down',
75 76 callback : function () {
76 77 IPython.notebook.move_cell_down();
77 78 }
78 79 }
79 80 ],'move_up_down');
80 81
81 82 this.add_buttons_group([
82 83 {
83 84 id : 'insert_above_b',
84 85 label : 'Insert Cell Above',
85 86 icon : 'icon-circle-arrow-up',
86 87 callback : function () {
87 88 IPython.notebook.insert_cell_above('code');
88 89 }
89 90 },
90 91 {
91 92 id : 'insert_below_b',
92 93 label : 'Insert Cell Below',
93 94 icon : 'icon-circle-arrow-down',
94 95 callback : function () {
95 96 IPython.notebook.insert_cell_below('code');
96 97 }
97 98 }
98 99 ],'insert_above_below');
99 100
100 101 this.add_buttons_group([
101 102 {
102 103 id : 'run_b',
103 104 label : 'Run Cell',
104 105 icon : 'icon-play',
105 106 callback : function () {
106 107 IPython.notebook.execute_selected_cell();
107 108 }
108 109 },
109 110 {
110 111 id : 'interrupt_b',
111 112 label : 'Interrupt',
112 113 icon : 'icon-stop',
113 114 callback : function () {
114 115 IPython.notebook.kernel.interrupt();
115 116 }
116 117 }
117 118 ],'run_int');
118 119 };
119 120
120 121 MainToolBar.prototype.add_celltype_list = function () {
121 122 this.element
122 123 .append($('<select/>')
123 124 .attr('id','cell_type')
124 125 // .addClass('ui-widget-content')
125 126 .append($('<option/>').attr('value','code').text('Code'))
126 127 .append($('<option/>').attr('value','markdown').text('Markdown'))
127 128 .append($('<option/>').attr('value','raw').text('Raw Text'))
128 129 .append($('<option/>').attr('value','heading1').text('Heading 1'))
129 130 .append($('<option/>').attr('value','heading2').text('Heading 2'))
130 131 .append($('<option/>').attr('value','heading3').text('Heading 3'))
131 132 .append($('<option/>').attr('value','heading4').text('Heading 4'))
132 133 .append($('<option/>').attr('value','heading5').text('Heading 5'))
133 134 .append($('<option/>').attr('value','heading6').text('Heading 6'))
134 135 );
135 136 };
136 137
137 138
138 139 MainToolBar.prototype.add_celltoolbar_list = function () {
139 140 var label = $('<span/>').addClass("navbar-text").text('Cell Toolbar:');
140 141 var select = $('<select/>')
141 142 // .addClass('ui-widget-content')
142 143 .attr('id', 'ctb_select')
143 144 .append($('<option/>').attr('value', '').text('None'));
144 145 this.element.append(label).append(select);
145 146 select.change(function() {
146 147 var val = $(this).val()
147 148 if (val =='') {
148 149 IPython.CellToolbar.global_hide();
149 150 } else {
150 151 IPython.CellToolbar.global_show();
151 152 IPython.CellToolbar.activate_preset(val);
152 153 }
153 154 });
154 155 // Setup the currently registered presets.
155 156 var presets = IPython.CellToolbar.list_presets();
156 157 for (var i=0; i<presets.length; i++) {
157 158 var name = presets[i];
158 159 select.append($('<option/>').attr('value', name).text(name));
159 160 }
160 161 // Setup future preset registrations.
161 162 $([IPython.events]).on('preset_added.CellToolbar', function (event, data) {
162 163 var name = data.name;
163 164 select.append($('<option/>').attr('value', name).text(name));
164 165 });
165 166 };
166 167
167 168
168 169 MainToolBar.prototype.bind_events = function () {
169 170 var that = this;
170 171
171 172 this.element.find('#cell_type').change(function () {
172 173 var cell_type = $(this).val();
173 174 if (cell_type === 'code') {
174 175 IPython.notebook.to_code();
175 176 } else if (cell_type === 'markdown') {
176 177 IPython.notebook.to_markdown();
177 178 } else if (cell_type === 'raw') {
178 179 IPython.notebook.to_raw();
179 180 } else if (cell_type === 'heading1') {
180 181 IPython.notebook.to_heading(undefined, 1);
181 182 } else if (cell_type === 'heading2') {
182 183 IPython.notebook.to_heading(undefined, 2);
183 184 } else if (cell_type === 'heading3') {
184 185 IPython.notebook.to_heading(undefined, 3);
185 186 } else if (cell_type === 'heading4') {
186 187 IPython.notebook.to_heading(undefined, 4);
187 188 } else if (cell_type === 'heading5') {
188 189 IPython.notebook.to_heading(undefined, 5);
189 190 } else if (cell_type === 'heading6') {
190 191 IPython.notebook.to_heading(undefined, 6);
191 192 }
192 193 });
193 194 $([IPython.events]).on('selected_cell_type_changed.Notebook', function (event, data) {
194 195 if (data.cell_type === 'heading') {
195 196 that.element.find('#cell_type').val(data.cell_type+data.level);
196 197 } else {
197 198 that.element.find('#cell_type').val(data.cell_type);
198 199 }
199 200 });
200 201 };
201 202
202 203 IPython.MainToolBar = MainToolBar;
203 204
204 205 return IPython;
205 206
206 207 }(IPython));
@@ -1,270 +1,271
1 1 //----------------------------------------------------------------------------
2 2 // Copyright (C) 2008-2011 The IPython Development Team
3 3 //
4 4 // Distributed under the terms of the BSD License. The full license is in
5 5 // the file COPYING, distributed as part of this software.
6 6 //----------------------------------------------------------------------------
7 7
8 8 //============================================================================
9 9 // MenuBar
10 10 //============================================================================
11 11
12 12 /**
13 13 * @module IPython
14 14 * @namespace IPython
15 15 * @submodule MenuBar
16 16 */
17 17
18 18
19 19 var IPython = (function (IPython) {
20 "use strict";
20 21
21 22 /**
22 23 * A MenuBar Class to generate the menubar of IPython noteboko
23 24 * @Class MenuBar
24 25 *
25 26 * @constructor
26 27 *
27 28 *
28 29 * @param selector {string} selector for the menubar element in DOM
29 30 * @param {object} [options]
30 31 * @param [options.baseProjectUrl] {String} String to use for the
31 32 * Base Project url, default would be to inspect
32 33 * $('body').data('baseProjectUrl');
33 34 * does not support change for now is set through this option
34 35 */
35 36 var MenuBar = function (selector, options) {
36 37 var options = options || {};
37 38 if(options.baseProjectUrl!= undefined){
38 39 this._baseProjectUrl = options.baseProjectUrl;
39 40 }
40 41 this.selector = selector;
41 42 if (this.selector !== undefined) {
42 43 this.element = $(selector);
43 44 this.style();
44 45 this.bind_events();
45 46 }
46 47 };
47 48
48 49 MenuBar.prototype.baseProjectUrl = function(){
49 50 return this._baseProjectUrl || $('body').data('baseProjectUrl');
50 51 };
51 52
52 53
53 54 MenuBar.prototype.style = function () {
54 55 this.element.addClass('border-box-sizing');
55 56 this.element.find("li").click(function (event, ui) {
56 57 // The selected cell loses focus when the menu is entered, so we
57 58 // re-select it upon selection.
58 59 var i = IPython.notebook.get_selected_index();
59 60 IPython.notebook.select(i);
60 61 }
61 62 );
62 63 };
63 64
64 65
65 66 MenuBar.prototype.bind_events = function () {
66 67 // File
67 68 var that = this;
68 69 this.element.find('#new_notebook').click(function () {
69 70 window.open(that.baseProjectUrl()+'new');
70 71 });
71 72 this.element.find('#open_notebook').click(function () {
72 73 window.open(that.baseProjectUrl());
73 74 });
74 75 this.element.find('#rename_notebook').click(function () {
75 76 IPython.save_widget.rename_notebook();
76 77 });
77 78 this.element.find('#copy_notebook').click(function () {
78 79 var notebook_id = IPython.notebook.get_notebook_id();
79 80 var url = that.baseProjectUrl() + notebook_id + '/copy';
80 81 window.open(url,'_blank');
81 82 return false;
82 83 });
83 84 this.element.find('#save_checkpoint').click(function () {
84 85 IPython.notebook.save_checkpoint();
85 86 });
86 87 this.element.find('#restore_checkpoint').click(function () {
87 88 });
88 89 this.element.find('#download_ipynb').click(function () {
89 90 var notebook_id = IPython.notebook.get_notebook_id();
90 91 var url = that.baseProjectUrl() + 'notebooks/' +
91 92 notebook_id + '?format=json';
92 93 window.location.assign(url);
93 94 });
94 95 this.element.find('#download_py').click(function () {
95 96 var notebook_id = IPython.notebook.get_notebook_id();
96 97 var url = that.baseProjectUrl() + 'notebooks/' +
97 98 notebook_id + '?format=py';
98 99 window.location.assign(url);
99 100 });
100 101 this.element.find('#kill_and_exit').click(function () {
101 102 IPython.notebook.kernel.kill();
102 103 setTimeout(function(){window.close();}, 200);
103 104 });
104 105 // Edit
105 106 this.element.find('#cut_cell').click(function () {
106 107 IPython.notebook.cut_cell();
107 108 });
108 109 this.element.find('#copy_cell').click(function () {
109 110 IPython.notebook.copy_cell();
110 111 });
111 112 this.element.find('#delete_cell').click(function () {
112 113 IPython.notebook.delete_cell();
113 114 });
114 115 this.element.find('#undelete_cell').click(function () {
115 116 IPython.notebook.undelete();
116 117 });
117 118 this.element.find('#split_cell').click(function () {
118 119 IPython.notebook.split_cell();
119 120 });
120 121 this.element.find('#merge_cell_above').click(function () {
121 122 IPython.notebook.merge_cell_above();
122 123 });
123 124 this.element.find('#merge_cell_below').click(function () {
124 125 IPython.notebook.merge_cell_below();
125 126 });
126 127 this.element.find('#move_cell_up').click(function () {
127 128 IPython.notebook.move_cell_up();
128 129 });
129 130 this.element.find('#move_cell_down').click(function () {
130 131 IPython.notebook.move_cell_down();
131 132 });
132 133 this.element.find('#select_previous').click(function () {
133 134 IPython.notebook.select_prev();
134 135 });
135 136 this.element.find('#select_next').click(function () {
136 137 IPython.notebook.select_next();
137 138 });
138 139 // View
139 140 this.element.find('#toggle_header').click(function () {
140 141 $('div#header').toggle();
141 142 IPython.layout_manager.do_resize();
142 143 });
143 144 this.element.find('#toggle_toolbar').click(function () {
144 145 $('div#maintoolbar').toggle();
145 146 IPython.layout_manager.do_resize();
146 147 });
147 148 // Insert
148 149 this.element.find('#insert_cell_above').click(function () {
149 150 IPython.notebook.insert_cell_above('code');
150 151 });
151 152 this.element.find('#insert_cell_below').click(function () {
152 153 IPython.notebook.insert_cell_below('code');
153 154 });
154 155 // Cell
155 156 this.element.find('#run_cell').click(function () {
156 157 IPython.notebook.execute_selected_cell();
157 158 });
158 159 this.element.find('#run_cell_in_place').click(function () {
159 160 IPython.notebook.execute_selected_cell({terminal:true});
160 161 });
161 162 this.element.find('#run_all_cells').click(function () {
162 163 IPython.notebook.execute_all_cells();
163 164 }).attr('title', 'Run all cells in the notebook');
164 165 this.element.find('#run_all_cells_above').click(function () {
165 166 IPython.notebook.execute_cells_above();
166 167 }).attr('title', 'Run all cells above (but not including) this cell');
167 168 this.element.find('#run_all_cells_below').click(function () {
168 169 IPython.notebook.execute_cells_below();
169 170 }).attr('title', 'Run this cell and all cells below it');
170 171 this.element.find('#to_code').click(function () {
171 172 IPython.notebook.to_code();
172 173 });
173 174 this.element.find('#to_markdown').click(function () {
174 175 IPython.notebook.to_markdown();
175 176 });
176 177 this.element.find('#to_raw').click(function () {
177 178 IPython.notebook.to_raw();
178 179 });
179 180 this.element.find('#to_heading1').click(function () {
180 181 IPython.notebook.to_heading(undefined, 1);
181 182 });
182 183 this.element.find('#to_heading2').click(function () {
183 184 IPython.notebook.to_heading(undefined, 2);
184 185 });
185 186 this.element.find('#to_heading3').click(function () {
186 187 IPython.notebook.to_heading(undefined, 3);
187 188 });
188 189 this.element.find('#to_heading4').click(function () {
189 190 IPython.notebook.to_heading(undefined, 4);
190 191 });
191 192 this.element.find('#to_heading5').click(function () {
192 193 IPython.notebook.to_heading(undefined, 5);
193 194 });
194 195 this.element.find('#to_heading6').click(function () {
195 196 IPython.notebook.to_heading(undefined, 6);
196 197 });
197 198 this.element.find('#toggle_output').click(function () {
198 199 IPython.notebook.toggle_output();
199 200 });
200 201 this.element.find('#collapse_all_output').click(function () {
201 202 IPython.notebook.collapse_all_output();
202 203 });
203 204 this.element.find('#scroll_all_output').click(function () {
204 205 IPython.notebook.scroll_all_output();
205 206 });
206 207 this.element.find('#expand_all_output').click(function () {
207 208 IPython.notebook.expand_all_output();
208 209 });
209 210 this.element.find('#clear_all_output').click(function () {
210 211 IPython.notebook.clear_all_output();
211 212 });
212 213 // Kernel
213 214 this.element.find('#int_kernel').click(function () {
214 215 IPython.notebook.kernel.interrupt();
215 216 });
216 217 this.element.find('#restart_kernel').click(function () {
217 218 IPython.notebook.restart_kernel();
218 219 });
219 220 // Help
220 221 this.element.find('#keyboard_shortcuts').click(function () {
221 222 IPython.quick_help.show_keyboard_shortcuts();
222 223 });
223 224
224 225 this.update_restore_checkpoint(null);
225 226
226 227 $([IPython.events]).on('checkpoints_listed.Notebook', function (event, data) {
227 228 that.update_restore_checkpoint(data);
228 229 });
229 230
230 231 $([IPython.events]).on('checkpoint_created.Notebook', function (event, data) {
231 232 that.update_restore_checkpoint([data]);
232 233 });
233 234 };
234 235
235 236 MenuBar.prototype.update_restore_checkpoint = function(checkpoints) {
236 237 var ul = this.element.find("#restore_checkpoint").find("ul");
237 238 ul.empty();
238 239 if (! checkpoints || checkpoints.length == 0) {
239 240 ul.append(
240 241 $("<li/>")
241 242 .addClass("disabled")
242 243 .append(
243 244 $("<a/>")
244 245 .text("No checkpoints")
245 246 )
246 247 );
247 248 return;
248 249 };
249 250
250 251 for (var i = 0; i < checkpoints.length; i++) {
251 252 var checkpoint = checkpoints[i];
252 253 var d = new Date(checkpoint.last_modified);
253 254 ul.append(
254 255 $("<li/>").append(
255 256 $("<a/>")
256 257 .attr("href", "#")
257 258 .text(d.format("mmm dd HH:MM:ss"))
258 259 .click(function () {
259 260 IPython.notebook.restore_checkpoint_dialog(checkpoint);
260 261 })
261 262 )
262 263 );
263 264 };
264 265 };
265 266
266 267 IPython.MenuBar = MenuBar;
267 268
268 269 return IPython;
269 270
270 271 }(IPython));
@@ -1,2040 +1,2041
1 1 //----------------------------------------------------------------------------
2 2 // Copyright (C) 2008-2011 The IPython Development Team
3 3 //
4 4 // Distributed under the terms of the BSD License. The full license is in
5 5 // the file COPYING, distributed as part of this software.
6 6 //----------------------------------------------------------------------------
7 7
8 8 //============================================================================
9 9 // Notebook
10 10 //============================================================================
11 11
12 12 var IPython = (function (IPython) {
13 "use strict";
13 14
14 15 var utils = IPython.utils;
15 16 var key = IPython.utils.keycodes;
16 17
17 18 /**
18 19 * A notebook contains and manages cells.
19 20 *
20 21 * @class Notebook
21 22 * @constructor
22 23 * @param {String} selector A jQuery selector for the notebook's DOM element
23 24 * @param {Object} [options] A config object
24 25 */
25 26 var Notebook = function (selector, options) {
26 27 var options = options || {};
27 28 this._baseProjectUrl = options.baseProjectUrl;
28 29
29 30 this.element = $(selector);
30 31 this.element.scroll();
31 32 this.element.data("notebook", this);
32 33 this.next_prompt_number = 1;
33 34 this.kernel = null;
34 35 this.clipboard = null;
35 36 this.undelete_backup = null;
36 37 this.undelete_index = null;
37 38 this.undelete_below = false;
38 39 this.paste_enabled = false;
39 40 this.set_dirty(false);
40 41 this.metadata = {};
41 42 this._checkpoint_after_save = false;
42 43 this.last_checkpoint = null;
43 44 this.autosave_interval = 0;
44 45 this.autosave_timer = null;
45 46 // autosave *at most* every two minutes
46 47 this.minimum_autosave_interval = 120000;
47 48 // single worksheet for now
48 49 this.worksheet_metadata = {};
49 50 this.control_key_active = false;
50 51 this.notebook_id = null;
51 52 this.notebook_name = null;
52 53 this.notebook_name_blacklist_re = /[\/\\:]/;
53 54 this.nbformat = 3 // Increment this when changing the nbformat
54 55 this.nbformat_minor = 0 // Increment this when changing the nbformat
55 56 this.style();
56 57 this.create_elements();
57 58 this.bind_events();
58 59 };
59 60
60 61 /**
61 62 * Tweak the notebook's CSS style.
62 63 *
63 64 * @method style
64 65 */
65 66 Notebook.prototype.style = function () {
66 67 $('div#notebook').addClass('border-box-sizing');
67 68 };
68 69
69 70 /**
70 71 * Get the root URL of the notebook server.
71 72 *
72 73 * @method baseProjectUrl
73 74 * @return {String} The base project URL
74 75 */
75 76 Notebook.prototype.baseProjectUrl = function(){
76 77 return this._baseProjectUrl || $('body').data('baseProjectUrl');
77 78 };
78 79
79 80 /**
80 81 * Create an HTML and CSS representation of the notebook.
81 82 *
82 83 * @method create_elements
83 84 */
84 85 Notebook.prototype.create_elements = function () {
85 86 // We add this end_space div to the end of the notebook div to:
86 87 // i) provide a margin between the last cell and the end of the notebook
87 88 // ii) to prevent the div from scrolling up when the last cell is being
88 89 // edited, but is too low on the page, which browsers will do automatically.
89 90 var that = this;
90 91 this.container = $("<div/>").addClass("container").attr("id", "notebook-container");
91 92 var end_space = $('<div/>').addClass('end_space');
92 93 end_space.dblclick(function (e) {
93 94 var ncells = that.ncells();
94 95 that.insert_cell_below('code',ncells-1);
95 96 });
96 97 this.element.append(this.container);
97 98 this.container.append(end_space);
98 99 $('div#notebook').addClass('border-box-sizing');
99 100 };
100 101
101 102 /**
102 103 * Bind JavaScript events: key presses and custom IPython events.
103 104 *
104 105 * @method bind_events
105 106 */
106 107 Notebook.prototype.bind_events = function () {
107 108 var that = this;
108 109
109 110 $([IPython.events]).on('set_next_input.Notebook', function (event, data) {
110 111 var index = that.find_cell_index(data.cell);
111 112 var new_cell = that.insert_cell_below('code',index);
112 113 new_cell.set_text(data.text);
113 114 that.dirty = true;
114 115 });
115 116
116 117 $([IPython.events]).on('set_dirty.Notebook', function (event, data) {
117 118 that.dirty = data.value;
118 119 });
119 120
120 121 $([IPython.events]).on('select.Cell', function (event, data) {
121 122 var index = that.find_cell_index(data.cell);
122 123 that.select(index);
123 124 });
124 125
125 126 $([IPython.events]).on('status_autorestarting.Kernel', function () {
126 127 IPython.dialog.modal({
127 128 title: "Kernel Restarting",
128 129 body: "The kernel appears to have died. It will restart automatically.",
129 130 buttons: {
130 131 OK : {
131 132 class : "btn-primary"
132 133 }
133 134 }
134 135 });
135 136 });
136 137
137 138
138 139 $(document).keydown(function (event) {
139 140
140 141 // Save (CTRL+S) or (AppleKey+S)
141 142 //metaKey = applekey on mac
142 143 if ((event.ctrlKey || event.metaKey) && event.keyCode==83) {
143 144 that.save_checkpoint();
144 145 event.preventDefault();
145 146 return false;
146 147 } else if (event.which === key.ESC) {
147 148 // Intercept escape at highest level to avoid closing
148 149 // websocket connection with firefox
149 150 IPython.pager.collapse();
150 151 event.preventDefault();
151 152 } else if (event.which === key.SHIFT) {
152 153 // ignore shift keydown
153 154 return true;
154 155 }
155 156 if (event.which === key.UPARROW && !event.shiftKey) {
156 157 var cell = that.get_selected_cell();
157 158 if (cell && cell.at_top()) {
158 159 event.preventDefault();
159 160 that.select_prev();
160 161 };
161 162 } else if (event.which === key.DOWNARROW && !event.shiftKey) {
162 163 var cell = that.get_selected_cell();
163 164 if (cell && cell.at_bottom()) {
164 165 event.preventDefault();
165 166 that.select_next();
166 167 };
167 168 } else if (event.which === key.ENTER && event.shiftKey) {
168 169 that.execute_selected_cell();
169 170 return false;
170 171 } else if (event.which === key.ENTER && event.altKey) {
171 172 // Execute code cell, and insert new in place
172 173 that.execute_selected_cell();
173 174 // Only insert a new cell, if we ended up in an already populated cell
174 175 if (/\S/.test(that.get_selected_cell().get_text()) == true) {
175 176 that.insert_cell_above('code');
176 177 }
177 178 return false;
178 179 } else if (event.which === key.ENTER && event.ctrlKey) {
179 180 that.execute_selected_cell({terminal:true});
180 181 return false;
181 182 } else if (event.which === 77 && event.ctrlKey && that.control_key_active == false) {
182 183 that.control_key_active = true;
183 184 return false;
184 185 } else if (event.which === 88 && that.control_key_active) {
185 186 // Cut selected cell = x
186 187 that.cut_cell();
187 188 that.control_key_active = false;
188 189 return false;
189 190 } else if (event.which === 67 && that.control_key_active) {
190 191 // Copy selected cell = c
191 192 that.copy_cell();
192 193 that.control_key_active = false;
193 194 return false;
194 195 } else if (event.which === 86 && that.control_key_active) {
195 196 // Paste below selected cell = v
196 197 that.paste_cell_below();
197 198 that.control_key_active = false;
198 199 return false;
199 200 } else if (event.which === 68 && that.control_key_active) {
200 201 // Delete selected cell = d
201 202 that.delete_cell();
202 203 that.control_key_active = false;
203 204 return false;
204 205 } else if (event.which === 65 && that.control_key_active) {
205 206 // Insert code cell above selected = a
206 207 that.insert_cell_above('code');
207 208 that.control_key_active = false;
208 209 return false;
209 210 } else if (event.which === 66 && that.control_key_active) {
210 211 // Insert code cell below selected = b
211 212 that.insert_cell_below('code');
212 213 that.control_key_active = false;
213 214 return false;
214 215 } else if (event.which === 89 && that.control_key_active) {
215 216 // To code = y
216 217 that.to_code();
217 218 that.control_key_active = false;
218 219 return false;
219 220 } else if (event.which === 77 && that.control_key_active) {
220 221 // To markdown = m
221 222 that.to_markdown();
222 223 that.control_key_active = false;
223 224 return false;
224 225 } else if (event.which === 84 && that.control_key_active) {
225 226 // To Raw = t
226 227 that.to_raw();
227 228 that.control_key_active = false;
228 229 return false;
229 230 } else if (event.which === 49 && that.control_key_active) {
230 231 // To Heading 1 = 1
231 232 that.to_heading(undefined, 1);
232 233 that.control_key_active = false;
233 234 return false;
234 235 } else if (event.which === 50 && that.control_key_active) {
235 236 // To Heading 2 = 2
236 237 that.to_heading(undefined, 2);
237 238 that.control_key_active = false;
238 239 return false;
239 240 } else if (event.which === 51 && that.control_key_active) {
240 241 // To Heading 3 = 3
241 242 that.to_heading(undefined, 3);
242 243 that.control_key_active = false;
243 244 return false;
244 245 } else if (event.which === 52 && that.control_key_active) {
245 246 // To Heading 4 = 4
246 247 that.to_heading(undefined, 4);
247 248 that.control_key_active = false;
248 249 return false;
249 250 } else if (event.which === 53 && that.control_key_active) {
250 251 // To Heading 5 = 5
251 252 that.to_heading(undefined, 5);
252 253 that.control_key_active = false;
253 254 return false;
254 255 } else if (event.which === 54 && that.control_key_active) {
255 256 // To Heading 6 = 6
256 257 that.to_heading(undefined, 6);
257 258 that.control_key_active = false;
258 259 return false;
259 260 } else if (event.which === 79 && that.control_key_active) {
260 261 // Toggle output = o
261 262 if (event.shiftKey){
262 263 that.toggle_output_scroll();
263 264 } else {
264 265 that.toggle_output();
265 266 }
266 267 that.control_key_active = false;
267 268 return false;
268 269 } else if (event.which === 83 && that.control_key_active) {
269 270 // Save notebook = s
270 271 that.save_checkpoint();
271 272 that.control_key_active = false;
272 273 return false;
273 274 } else if (event.which === 74 && that.control_key_active) {
274 275 // Move cell down = j
275 276 that.move_cell_down();
276 277 that.control_key_active = false;
277 278 return false;
278 279 } else if (event.which === 75 && that.control_key_active) {
279 280 // Move cell up = k
280 281 that.move_cell_up();
281 282 that.control_key_active = false;
282 283 return false;
283 284 } else if (event.which === 80 && that.control_key_active) {
284 285 // Select previous = p
285 286 that.select_prev();
286 287 that.control_key_active = false;
287 288 return false;
288 289 } else if (event.which === 78 && that.control_key_active) {
289 290 // Select next = n
290 291 that.select_next();
291 292 that.control_key_active = false;
292 293 return false;
293 294 } else if (event.which === 76 && that.control_key_active) {
294 295 // Toggle line numbers = l
295 296 that.cell_toggle_line_numbers();
296 297 that.control_key_active = false;
297 298 return false;
298 299 } else if (event.which === 73 && that.control_key_active) {
299 300 // Interrupt kernel = i
300 301 that.kernel.interrupt();
301 302 that.control_key_active = false;
302 303 return false;
303 304 } else if (event.which === 190 && that.control_key_active) {
304 305 // Restart kernel = . # matches qt console
305 306 that.restart_kernel();
306 307 that.control_key_active = false;
307 308 return false;
308 309 } else if (event.which === 72 && that.control_key_active) {
309 310 // Show keyboard shortcuts = h
310 311 IPython.quick_help.show_keyboard_shortcuts();
311 312 that.control_key_active = false;
312 313 return false;
313 314 } else if (event.which === 90 && that.control_key_active) {
314 315 // Undo last cell delete = z
315 316 that.undelete();
316 317 that.control_key_active = false;
317 318 return false;
318 319 } else if (event.which === 189 && that.control_key_active) {
319 320 // Split cell = -
320 321 that.split_cell();
321 322 that.control_key_active = false;
322 323 return false;
323 324 } else if (that.control_key_active) {
324 325 that.control_key_active = false;
325 326 return true;
326 327 }
327 328 return true;
328 329 });
329 330
330 331 var collapse_time = function(time){
331 332 var app_height = $('#ipython-main-app').height(); // content height
332 333 var splitter_height = $('div#pager_splitter').outerHeight(true);
333 334 var new_height = app_height - splitter_height;
334 335 that.element.animate({height : new_height + 'px'}, time);
335 336 }
336 337
337 338 this.element.bind('collapse_pager', function (event,extrap) {
338 339 var time = (extrap != undefined) ? ((extrap.duration != undefined ) ? extrap.duration : 'fast') : 'fast';
339 340 collapse_time(time);
340 341 });
341 342
342 343 var expand_time = function(time) {
343 344 var app_height = $('#ipython-main-app').height(); // content height
344 345 var splitter_height = $('div#pager_splitter').outerHeight(true);
345 346 var pager_height = $('div#pager').outerHeight(true);
346 347 var new_height = app_height - pager_height - splitter_height;
347 348 that.element.animate({height : new_height + 'px'}, time);
348 349 }
349 350
350 351 this.element.bind('expand_pager', function (event, extrap) {
351 352 var time = (extrap != undefined) ? ((extrap.duration != undefined ) ? extrap.duration : 'fast') : 'fast';
352 353 expand_time(time);
353 354 });
354 355
355 356 // Firefox 22 broke $(window).on("beforeunload")
356 357 // I'm not sure why or how.
357 358 window.onbeforeunload = function (e) {
358 359 // TODO: Make killing the kernel configurable.
359 360 var kill_kernel = false;
360 361 if (kill_kernel) {
361 362 that.kernel.kill();
362 363 }
363 364 // if we are autosaving, trigger an autosave on nav-away.
364 365 // still warn, because if we don't the autosave may fail.
365 366 if (that.dirty) {
366 367 if ( that.autosave_interval ) {
367 368 // schedule autosave in a timeout
368 369 // this gives you a chance to forcefully discard changes
369 370 // by reloading the page if you *really* want to.
370 371 // the timer doesn't start until you *dismiss* the dialog.
371 372 setTimeout(function () {
372 373 if (that.dirty) {
373 374 that.save_notebook();
374 375 }
375 376 }, 1000);
376 377 return "Autosave in progress, latest changes may be lost.";
377 378 } else {
378 379 return "Unsaved changes will be lost.";
379 380 }
380 381 };
381 382 // Null is the *only* return value that will make the browser not
382 383 // pop up the "don't leave" dialog.
383 384 return null;
384 385 };
385 386 };
386 387
387 388 /**
388 389 * Set the dirty flag, and trigger the set_dirty.Notebook event
389 390 *
390 391 * @method set_dirty
391 392 */
392 393 Notebook.prototype.set_dirty = function (value) {
393 394 if (value === undefined) {
394 395 value = true;
395 396 }
396 397 if (this.dirty == value) {
397 398 return;
398 399 }
399 400 $([IPython.events]).trigger('set_dirty.Notebook', {value: value});
400 401 };
401 402
402 403 /**
403 404 * Scroll the top of the page to a given cell.
404 405 *
405 406 * @method scroll_to_cell
406 407 * @param {Number} cell_number An index of the cell to view
407 408 * @param {Number} time Animation time in milliseconds
408 409 * @return {Number} Pixel offset from the top of the container
409 410 */
410 411 Notebook.prototype.scroll_to_cell = function (cell_number, time) {
411 412 var cells = this.get_cells();
412 413 var time = time || 0;
413 414 cell_number = Math.min(cells.length-1,cell_number);
414 415 cell_number = Math.max(0 ,cell_number);
415 416 var scroll_value = cells[cell_number].element.position().top-cells[0].element.position().top ;
416 417 this.element.animate({scrollTop:scroll_value}, time);
417 418 return scroll_value;
418 419 };
419 420
420 421 /**
421 422 * Scroll to the bottom of the page.
422 423 *
423 424 * @method scroll_to_bottom
424 425 */
425 426 Notebook.prototype.scroll_to_bottom = function () {
426 427 this.element.animate({scrollTop:this.element.get(0).scrollHeight}, 0);
427 428 };
428 429
429 430 /**
430 431 * Scroll to the top of the page.
431 432 *
432 433 * @method scroll_to_top
433 434 */
434 435 Notebook.prototype.scroll_to_top = function () {
435 436 this.element.animate({scrollTop:0}, 0);
436 437 };
437 438
438 439
439 440 // Cell indexing, retrieval, etc.
440 441
441 442 /**
442 443 * Get all cell elements in the notebook.
443 444 *
444 445 * @method get_cell_elements
445 446 * @return {jQuery} A selector of all cell elements
446 447 */
447 448 Notebook.prototype.get_cell_elements = function () {
448 449 return this.container.children("div.cell");
449 450 };
450 451
451 452 /**
452 453 * Get a particular cell element.
453 454 *
454 455 * @method get_cell_element
455 456 * @param {Number} index An index of a cell to select
456 457 * @return {jQuery} A selector of the given cell.
457 458 */
458 459 Notebook.prototype.get_cell_element = function (index) {
459 460 var result = null;
460 461 var e = this.get_cell_elements().eq(index);
461 462 if (e.length !== 0) {
462 463 result = e;
463 464 }
464 465 return result;
465 466 };
466 467
467 468 /**
468 469 * Count the cells in this notebook.
469 470 *
470 471 * @method ncells
471 472 * @return {Number} The number of cells in this notebook
472 473 */
473 474 Notebook.prototype.ncells = function () {
474 475 return this.get_cell_elements().length;
475 476 };
476 477
477 478 /**
478 479 * Get all Cell objects in this notebook.
479 480 *
480 481 * @method get_cells
481 482 * @return {Array} This notebook's Cell objects
482 483 */
483 484 // TODO: we are often calling cells as cells()[i], which we should optimize
484 485 // to cells(i) or a new method.
485 486 Notebook.prototype.get_cells = function () {
486 487 return this.get_cell_elements().toArray().map(function (e) {
487 488 return $(e).data("cell");
488 489 });
489 490 };
490 491
491 492 /**
492 493 * Get a Cell object from this notebook.
493 494 *
494 495 * @method get_cell
495 496 * @param {Number} index An index of a cell to retrieve
496 497 * @return {Cell} A particular cell
497 498 */
498 499 Notebook.prototype.get_cell = function (index) {
499 500 var result = null;
500 501 var ce = this.get_cell_element(index);
501 502 if (ce !== null) {
502 503 result = ce.data('cell');
503 504 }
504 505 return result;
505 506 }
506 507
507 508 /**
508 509 * Get the cell below a given cell.
509 510 *
510 511 * @method get_next_cell
511 512 * @param {Cell} cell The provided cell
512 513 * @return {Cell} The next cell
513 514 */
514 515 Notebook.prototype.get_next_cell = function (cell) {
515 516 var result = null;
516 517 var index = this.find_cell_index(cell);
517 518 if (this.is_valid_cell_index(index+1)) {
518 519 result = this.get_cell(index+1);
519 520 }
520 521 return result;
521 522 }
522 523
523 524 /**
524 525 * Get the cell above a given cell.
525 526 *
526 527 * @method get_prev_cell
527 528 * @param {Cell} cell The provided cell
528 529 * @return {Cell} The previous cell
529 530 */
530 531 Notebook.prototype.get_prev_cell = function (cell) {
531 532 // TODO: off-by-one
532 533 // nb.get_prev_cell(nb.get_cell(1)) is null
533 534 var result = null;
534 535 var index = this.find_cell_index(cell);
535 536 if (index !== null && index > 1) {
536 537 result = this.get_cell(index-1);
537 538 }
538 539 return result;
539 540 }
540 541
541 542 /**
542 543 * Get the numeric index of a given cell.
543 544 *
544 545 * @method find_cell_index
545 546 * @param {Cell} cell The provided cell
546 547 * @return {Number} The cell's numeric index
547 548 */
548 549 Notebook.prototype.find_cell_index = function (cell) {
549 550 var result = null;
550 551 this.get_cell_elements().filter(function (index) {
551 552 if ($(this).data("cell") === cell) {
552 553 result = index;
553 554 };
554 555 });
555 556 return result;
556 557 };
557 558
558 559 /**
559 560 * Get a given index , or the selected index if none is provided.
560 561 *
561 562 * @method index_or_selected
562 563 * @param {Number} index A cell's index
563 564 * @return {Number} The given index, or selected index if none is provided.
564 565 */
565 566 Notebook.prototype.index_or_selected = function (index) {
566 567 var i;
567 568 if (index === undefined || index === null) {
568 569 i = this.get_selected_index();
569 570 if (i === null) {
570 571 i = 0;
571 572 }
572 573 } else {
573 574 i = index;
574 575 }
575 576 return i;
576 577 };
577 578
578 579 /**
579 580 * Get the currently selected cell.
580 581 * @method get_selected_cell
581 582 * @return {Cell} The selected cell
582 583 */
583 584 Notebook.prototype.get_selected_cell = function () {
584 585 var index = this.get_selected_index();
585 586 return this.get_cell(index);
586 587 };
587 588
588 589 /**
589 590 * Check whether a cell index is valid.
590 591 *
591 592 * @method is_valid_cell_index
592 593 * @param {Number} index A cell index
593 594 * @return True if the index is valid, false otherwise
594 595 */
595 596 Notebook.prototype.is_valid_cell_index = function (index) {
596 597 if (index !== null && index >= 0 && index < this.ncells()) {
597 598 return true;
598 599 } else {
599 600 return false;
600 601 };
601 602 }
602 603
603 604 /**
604 605 * Get the index of the currently selected cell.
605 606
606 607 * @method get_selected_index
607 608 * @return {Number} The selected cell's numeric index
608 609 */
609 610 Notebook.prototype.get_selected_index = function () {
610 611 var result = null;
611 612 this.get_cell_elements().filter(function (index) {
612 613 if ($(this).data("cell").selected === true) {
613 614 result = index;
614 615 };
615 616 });
616 617 return result;
617 618 };
618 619
619 620
620 621 // Cell selection.
621 622
622 623 /**
623 624 * Programmatically select a cell.
624 625 *
625 626 * @method select
626 627 * @param {Number} index A cell's index
627 628 * @return {Notebook} This notebook
628 629 */
629 630 Notebook.prototype.select = function (index) {
630 631 if (this.is_valid_cell_index(index)) {
631 632 var sindex = this.get_selected_index()
632 633 if (sindex !== null && index !== sindex) {
633 634 this.get_cell(sindex).unselect();
634 635 };
635 636 var cell = this.get_cell(index);
636 637 cell.select();
637 638 if (cell.cell_type === 'heading') {
638 639 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
639 640 {'cell_type':cell.cell_type,level:cell.level}
640 641 );
641 642 } else {
642 643 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
643 644 {'cell_type':cell.cell_type}
644 645 );
645 646 };
646 647 };
647 648 return this;
648 649 };
649 650
650 651 /**
651 652 * Programmatically select the next cell.
652 653 *
653 654 * @method select_next
654 655 * @return {Notebook} This notebook
655 656 */
656 657 Notebook.prototype.select_next = function () {
657 658 var index = this.get_selected_index();
658 659 this.select(index+1);
659 660 return this;
660 661 };
661 662
662 663 /**
663 664 * Programmatically select the previous cell.
664 665 *
665 666 * @method select_prev
666 667 * @return {Notebook} This notebook
667 668 */
668 669 Notebook.prototype.select_prev = function () {
669 670 var index = this.get_selected_index();
670 671 this.select(index-1);
671 672 return this;
672 673 };
673 674
674 675
675 676 // Cell movement
676 677
677 678 /**
678 679 * Move given (or selected) cell up and select it.
679 680 *
680 681 * @method move_cell_up
681 682 * @param [index] {integer} cell index
682 683 * @return {Notebook} This notebook
683 684 **/
684 685 Notebook.prototype.move_cell_up = function (index) {
685 686 var i = this.index_or_selected(index);
686 687 if (this.is_valid_cell_index(i) && i > 0) {
687 688 var pivot = this.get_cell_element(i-1);
688 689 var tomove = this.get_cell_element(i);
689 690 if (pivot !== null && tomove !== null) {
690 691 tomove.detach();
691 692 pivot.before(tomove);
692 693 this.select(i-1);
693 694 };
694 695 this.set_dirty(true);
695 696 };
696 697 return this;
697 698 };
698 699
699 700
700 701 /**
701 702 * Move given (or selected) cell down and select it
702 703 *
703 704 * @method move_cell_down
704 705 * @param [index] {integer} cell index
705 706 * @return {Notebook} This notebook
706 707 **/
707 708 Notebook.prototype.move_cell_down = function (index) {
708 709 var i = this.index_or_selected(index);
709 710 if ( this.is_valid_cell_index(i) && this.is_valid_cell_index(i+1)) {
710 711 var pivot = this.get_cell_element(i+1);
711 712 var tomove = this.get_cell_element(i);
712 713 if (pivot !== null && tomove !== null) {
713 714 tomove.detach();
714 715 pivot.after(tomove);
715 716 this.select(i+1);
716 717 };
717 718 };
718 719 this.set_dirty();
719 720 return this;
720 721 };
721 722
722 723
723 724 // Insertion, deletion.
724 725
725 726 /**
726 727 * Delete a cell from the notebook.
727 728 *
728 729 * @method delete_cell
729 730 * @param [index] A cell's numeric index
730 731 * @return {Notebook} This notebook
731 732 */
732 733 Notebook.prototype.delete_cell = function (index) {
733 734 var i = this.index_or_selected(index);
734 735 var cell = this.get_selected_cell();
735 736 this.undelete_backup = cell.toJSON();
736 737 $('#undelete_cell').removeClass('disabled');
737 738 if (this.is_valid_cell_index(i)) {
738 739 var ce = this.get_cell_element(i);
739 740 ce.remove();
740 741 if (i === (this.ncells())) {
741 742 this.select(i-1);
742 743 this.undelete_index = i - 1;
743 744 this.undelete_below = true;
744 745 } else {
745 746 this.select(i);
746 747 this.undelete_index = i;
747 748 this.undelete_below = false;
748 749 };
749 750 $([IPython.events]).trigger('delete.Cell', {'cell': cell, 'index': i});
750 751 this.set_dirty(true);
751 752 };
752 753 return this;
753 754 };
754 755
755 756 /**
756 757 * Insert a cell so that after insertion the cell is at given index.
757 758 *
758 759 * Similar to insert_above, but index parameter is mandatory
759 760 *
760 761 * Index will be brought back into the accissible range [0,n]
761 762 *
762 763 * @method insert_cell_at_index
763 764 * @param type {string} in ['code','markdown','heading']
764 765 * @param [index] {int} a valid index where to inser cell
765 766 *
766 767 * @return cell {cell|null} created cell or null
767 768 **/
768 769 Notebook.prototype.insert_cell_at_index = function(type, index){
769 770
770 771 var ncells = this.ncells();
771 772 var index = Math.min(index,ncells);
772 773 index = Math.max(index,0);
773 774 var cell = null;
774 775
775 776 if (ncells === 0 || this.is_valid_cell_index(index) || index === ncells) {
776 777 if (type === 'code') {
777 778 cell = new IPython.CodeCell(this.kernel);
778 779 cell.set_input_prompt();
779 780 } else if (type === 'markdown') {
780 781 cell = new IPython.MarkdownCell();
781 782 } else if (type === 'raw') {
782 783 cell = new IPython.RawCell();
783 784 } else if (type === 'heading') {
784 785 cell = new IPython.HeadingCell();
785 786 }
786 787
787 788 if(this._insert_element_at_index(cell.element,index)){
788 789 cell.render();
789 790 this.select(this.find_cell_index(cell));
790 791 $([IPython.events]).trigger('create.Cell', {'cell': cell, 'index': index});
791 792 this.set_dirty(true);
792 793 }
793 794 }
794 795 return cell;
795 796
796 797 };
797 798
798 799 /**
799 800 * Insert an element at given cell index.
800 801 *
801 802 * @method _insert_element_at_index
802 803 * @param element {dom element} a cell element
803 804 * @param [index] {int} a valid index where to inser cell
804 805 * @private
805 806 *
806 807 * return true if everything whent fine.
807 808 **/
808 809 Notebook.prototype._insert_element_at_index = function(element, index){
809 810 if (element === undefined){
810 811 return false;
811 812 }
812 813
813 814 var ncells = this.ncells();
814 815
815 816 if (ncells === 0) {
816 817 // special case append if empty
817 818 this.element.find('div.end_space').before(element);
818 819 } else if ( ncells === index ) {
819 820 // special case append it the end, but not empty
820 821 this.get_cell_element(index-1).after(element);
821 822 } else if (this.is_valid_cell_index(index)) {
822 823 // otherwise always somewhere to append to
823 824 this.get_cell_element(index).before(element);
824 825 } else {
825 826 return false;
826 827 }
827 828
828 829 if (this.undelete_index !== null && index <= this.undelete_index) {
829 830 this.undelete_index = this.undelete_index + 1;
830 831 this.set_dirty(true);
831 832 }
832 833 return true;
833 834 };
834 835
835 836 /**
836 837 * Insert a cell of given type above given index, or at top
837 838 * of notebook if index smaller than 0.
838 839 *
839 840 * default index value is the one of currently selected cell
840 841 *
841 842 * @method insert_cell_above
842 843 * @param type {string} cell type
843 844 * @param [index] {integer}
844 845 *
845 846 * @return handle to created cell or null
846 847 **/
847 848 Notebook.prototype.insert_cell_above = function (type, index) {
848 849 index = this.index_or_selected(index);
849 850 return this.insert_cell_at_index(type, index);
850 851 };
851 852
852 853 /**
853 854 * Insert a cell of given type below given index, or at bottom
854 855 * of notebook if index greater thatn number of cell
855 856 *
856 857 * default index value is the one of currently selected cell
857 858 *
858 859 * @method insert_cell_below
859 860 * @param type {string} cell type
860 861 * @param [index] {integer}
861 862 *
862 863 * @return handle to created cell or null
863 864 *
864 865 **/
865 866 Notebook.prototype.insert_cell_below = function (type, index) {
866 867 index = this.index_or_selected(index);
867 868 return this.insert_cell_at_index(type, index+1);
868 869 };
869 870
870 871
871 872 /**
872 873 * Insert cell at end of notebook
873 874 *
874 875 * @method insert_cell_at_bottom
875 876 * @param {String} type cell type
876 877 *
877 878 * @return the added cell; or null
878 879 **/
879 880 Notebook.prototype.insert_cell_at_bottom = function (type){
880 881 var len = this.ncells();
881 882 return this.insert_cell_below(type,len-1);
882 883 };
883 884
884 885 /**
885 886 * Turn a cell into a code cell.
886 887 *
887 888 * @method to_code
888 889 * @param {Number} [index] A cell's index
889 890 */
890 891 Notebook.prototype.to_code = function (index) {
891 892 var i = this.index_or_selected(index);
892 893 if (this.is_valid_cell_index(i)) {
893 894 var source_element = this.get_cell_element(i);
894 895 var source_cell = source_element.data("cell");
895 896 if (!(source_cell instanceof IPython.CodeCell)) {
896 897 var target_cell = this.insert_cell_below('code',i);
897 898 var text = source_cell.get_text();
898 899 if (text === source_cell.placeholder) {
899 900 text = '';
900 901 }
901 902 target_cell.set_text(text);
902 903 // make this value the starting point, so that we can only undo
903 904 // to this state, instead of a blank cell
904 905 target_cell.code_mirror.clearHistory();
905 906 source_element.remove();
906 907 this.set_dirty(true);
907 908 };
908 909 };
909 910 };
910 911
911 912 /**
912 913 * Turn a cell into a Markdown cell.
913 914 *
914 915 * @method to_markdown
915 916 * @param {Number} [index] A cell's index
916 917 */
917 918 Notebook.prototype.to_markdown = function (index) {
918 919 var i = this.index_or_selected(index);
919 920 if (this.is_valid_cell_index(i)) {
920 921 var source_element = this.get_cell_element(i);
921 922 var source_cell = source_element.data("cell");
922 923 if (!(source_cell instanceof IPython.MarkdownCell)) {
923 924 var target_cell = this.insert_cell_below('markdown',i);
924 925 var text = source_cell.get_text();
925 926 if (text === source_cell.placeholder) {
926 927 text = '';
927 928 };
928 929 // The edit must come before the set_text.
929 930 target_cell.edit();
930 931 target_cell.set_text(text);
931 932 // make this value the starting point, so that we can only undo
932 933 // to this state, instead of a blank cell
933 934 target_cell.code_mirror.clearHistory();
934 935 source_element.remove();
935 936 this.set_dirty(true);
936 937 };
937 938 };
938 939 };
939 940
940 941 /**
941 942 * Turn a cell into a raw text cell.
942 943 *
943 944 * @method to_raw
944 945 * @param {Number} [index] A cell's index
945 946 */
946 947 Notebook.prototype.to_raw = function (index) {
947 948 var i = this.index_or_selected(index);
948 949 if (this.is_valid_cell_index(i)) {
949 950 var source_element = this.get_cell_element(i);
950 951 var source_cell = source_element.data("cell");
951 952 var target_cell = null;
952 953 if (!(source_cell instanceof IPython.RawCell)) {
953 954 target_cell = this.insert_cell_below('raw',i);
954 955 var text = source_cell.get_text();
955 956 if (text === source_cell.placeholder) {
956 957 text = '';
957 958 };
958 959 // The edit must come before the set_text.
959 960 target_cell.edit();
960 961 target_cell.set_text(text);
961 962 // make this value the starting point, so that we can only undo
962 963 // to this state, instead of a blank cell
963 964 target_cell.code_mirror.clearHistory();
964 965 source_element.remove();
965 966 this.set_dirty(true);
966 967 };
967 968 };
968 969 };
969 970
970 971 /**
971 972 * Turn a cell into a heading cell.
972 973 *
973 974 * @method to_heading
974 975 * @param {Number} [index] A cell's index
975 976 * @param {Number} [level] A heading level (e.g., 1 becomes &lt;h1&gt;)
976 977 */
977 978 Notebook.prototype.to_heading = function (index, level) {
978 979 level = level || 1;
979 980 var i = this.index_or_selected(index);
980 981 if (this.is_valid_cell_index(i)) {
981 982 var source_element = this.get_cell_element(i);
982 983 var source_cell = source_element.data("cell");
983 984 var target_cell = null;
984 985 if (source_cell instanceof IPython.HeadingCell) {
985 986 source_cell.set_level(level);
986 987 } else {
987 988 target_cell = this.insert_cell_below('heading',i);
988 989 var text = source_cell.get_text();
989 990 if (text === source_cell.placeholder) {
990 991 text = '';
991 992 };
992 993 // The edit must come before the set_text.
993 994 target_cell.set_level(level);
994 995 target_cell.edit();
995 996 target_cell.set_text(text);
996 997 // make this value the starting point, so that we can only undo
997 998 // to this state, instead of a blank cell
998 999 target_cell.code_mirror.clearHistory();
999 1000 source_element.remove();
1000 1001 this.set_dirty(true);
1001 1002 };
1002 1003 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
1003 1004 {'cell_type':'heading',level:level}
1004 1005 );
1005 1006 };
1006 1007 };
1007 1008
1008 1009
1009 1010 // Cut/Copy/Paste
1010 1011
1011 1012 /**
1012 1013 * Enable UI elements for pasting cells.
1013 1014 *
1014 1015 * @method enable_paste
1015 1016 */
1016 1017 Notebook.prototype.enable_paste = function () {
1017 1018 var that = this;
1018 1019 if (!this.paste_enabled) {
1019 1020 $('#paste_cell_replace').removeClass('disabled')
1020 1021 .on('click', function () {that.paste_cell_replace();});
1021 1022 $('#paste_cell_above').removeClass('disabled')
1022 1023 .on('click', function () {that.paste_cell_above();});
1023 1024 $('#paste_cell_below').removeClass('disabled')
1024 1025 .on('click', function () {that.paste_cell_below();});
1025 1026 this.paste_enabled = true;
1026 1027 };
1027 1028 };
1028 1029
1029 1030 /**
1030 1031 * Disable UI elements for pasting cells.
1031 1032 *
1032 1033 * @method disable_paste
1033 1034 */
1034 1035 Notebook.prototype.disable_paste = function () {
1035 1036 if (this.paste_enabled) {
1036 1037 $('#paste_cell_replace').addClass('disabled').off('click');
1037 1038 $('#paste_cell_above').addClass('disabled').off('click');
1038 1039 $('#paste_cell_below').addClass('disabled').off('click');
1039 1040 this.paste_enabled = false;
1040 1041 };
1041 1042 };
1042 1043
1043 1044 /**
1044 1045 * Cut a cell.
1045 1046 *
1046 1047 * @method cut_cell
1047 1048 */
1048 1049 Notebook.prototype.cut_cell = function () {
1049 1050 this.copy_cell();
1050 1051 this.delete_cell();
1051 1052 }
1052 1053
1053 1054 /**
1054 1055 * Copy a cell.
1055 1056 *
1056 1057 * @method copy_cell
1057 1058 */
1058 1059 Notebook.prototype.copy_cell = function () {
1059 1060 var cell = this.get_selected_cell();
1060 1061 this.clipboard = cell.toJSON();
1061 1062 this.enable_paste();
1062 1063 };
1063 1064
1064 1065 /**
1065 1066 * Replace the selected cell with a cell in the clipboard.
1066 1067 *
1067 1068 * @method paste_cell_replace
1068 1069 */
1069 1070 Notebook.prototype.paste_cell_replace = function () {
1070 1071 if (this.clipboard !== null && this.paste_enabled) {
1071 1072 var cell_data = this.clipboard;
1072 1073 var new_cell = this.insert_cell_above(cell_data.cell_type);
1073 1074 new_cell.fromJSON(cell_data);
1074 1075 var old_cell = this.get_next_cell(new_cell);
1075 1076 this.delete_cell(this.find_cell_index(old_cell));
1076 1077 this.select(this.find_cell_index(new_cell));
1077 1078 };
1078 1079 };
1079 1080
1080 1081 /**
1081 1082 * Paste a cell from the clipboard above the selected cell.
1082 1083 *
1083 1084 * @method paste_cell_above
1084 1085 */
1085 1086 Notebook.prototype.paste_cell_above = function () {
1086 1087 if (this.clipboard !== null && this.paste_enabled) {
1087 1088 var cell_data = this.clipboard;
1088 1089 var new_cell = this.insert_cell_above(cell_data.cell_type);
1089 1090 new_cell.fromJSON(cell_data);
1090 1091 };
1091 1092 };
1092 1093
1093 1094 /**
1094 1095 * Paste a cell from the clipboard below the selected cell.
1095 1096 *
1096 1097 * @method paste_cell_below
1097 1098 */
1098 1099 Notebook.prototype.paste_cell_below = function () {
1099 1100 if (this.clipboard !== null && this.paste_enabled) {
1100 1101 var cell_data = this.clipboard;
1101 1102 var new_cell = this.insert_cell_below(cell_data.cell_type);
1102 1103 new_cell.fromJSON(cell_data);
1103 1104 };
1104 1105 };
1105 1106
1106 1107 // Cell undelete
1107 1108
1108 1109 /**
1109 1110 * Restore the most recently deleted cell.
1110 1111 *
1111 1112 * @method undelete
1112 1113 */
1113 1114 Notebook.prototype.undelete = function() {
1114 1115 if (this.undelete_backup !== null && this.undelete_index !== null) {
1115 1116 var current_index = this.get_selected_index();
1116 1117 if (this.undelete_index < current_index) {
1117 1118 current_index = current_index + 1;
1118 1119 }
1119 1120 if (this.undelete_index >= this.ncells()) {
1120 1121 this.select(this.ncells() - 1);
1121 1122 }
1122 1123 else {
1123 1124 this.select(this.undelete_index);
1124 1125 }
1125 1126 var cell_data = this.undelete_backup;
1126 1127 var new_cell = null;
1127 1128 if (this.undelete_below) {
1128 1129 new_cell = this.insert_cell_below(cell_data.cell_type);
1129 1130 } else {
1130 1131 new_cell = this.insert_cell_above(cell_data.cell_type);
1131 1132 }
1132 1133 new_cell.fromJSON(cell_data);
1133 1134 this.select(current_index);
1134 1135 this.undelete_backup = null;
1135 1136 this.undelete_index = null;
1136 1137 }
1137 1138 $('#undelete_cell').addClass('disabled');
1138 1139 }
1139 1140
1140 1141 // Split/merge
1141 1142
1142 1143 /**
1143 1144 * Split the selected cell into two, at the cursor.
1144 1145 *
1145 1146 * @method split_cell
1146 1147 */
1147 1148 Notebook.prototype.split_cell = function () {
1148 1149 // Todo: implement spliting for other cell types.
1149 1150 var cell = this.get_selected_cell();
1150 1151 if (cell.is_splittable()) {
1151 1152 var texta = cell.get_pre_cursor();
1152 1153 var textb = cell.get_post_cursor();
1153 1154 if (cell instanceof IPython.CodeCell) {
1154 1155 cell.set_text(texta);
1155 1156 var new_cell = this.insert_cell_below('code');
1156 1157 new_cell.set_text(textb);
1157 1158 } else if (cell instanceof IPython.MarkdownCell) {
1158 1159 cell.set_text(texta);
1159 1160 cell.render();
1160 1161 var new_cell = this.insert_cell_below('markdown');
1161 1162 new_cell.edit(); // editor must be visible to call set_text
1162 1163 new_cell.set_text(textb);
1163 1164 new_cell.render();
1164 1165 }
1165 1166 };
1166 1167 };
1167 1168
1168 1169 /**
1169 1170 * Combine the selected cell into the cell above it.
1170 1171 *
1171 1172 * @method merge_cell_above
1172 1173 */
1173 1174 Notebook.prototype.merge_cell_above = function () {
1174 1175 var index = this.get_selected_index();
1175 1176 var cell = this.get_cell(index);
1176 1177 if (index > 0) {
1177 1178 var upper_cell = this.get_cell(index-1);
1178 1179 var upper_text = upper_cell.get_text();
1179 1180 var text = cell.get_text();
1180 1181 if (cell instanceof IPython.CodeCell) {
1181 1182 cell.set_text(upper_text+'\n'+text);
1182 1183 } else if (cell instanceof IPython.MarkdownCell) {
1183 1184 cell.edit();
1184 1185 cell.set_text(upper_text+'\n'+text);
1185 1186 cell.render();
1186 1187 };
1187 1188 this.delete_cell(index-1);
1188 1189 this.select(this.find_cell_index(cell));
1189 1190 };
1190 1191 };
1191 1192
1192 1193 /**
1193 1194 * Combine the selected cell into the cell below it.
1194 1195 *
1195 1196 * @method merge_cell_below
1196 1197 */
1197 1198 Notebook.prototype.merge_cell_below = function () {
1198 1199 var index = this.get_selected_index();
1199 1200 var cell = this.get_cell(index);
1200 1201 if (index < this.ncells()-1) {
1201 1202 var lower_cell = this.get_cell(index+1);
1202 1203 var lower_text = lower_cell.get_text();
1203 1204 var text = cell.get_text();
1204 1205 if (cell instanceof IPython.CodeCell) {
1205 1206 cell.set_text(text+'\n'+lower_text);
1206 1207 } else if (cell instanceof IPython.MarkdownCell) {
1207 1208 cell.edit();
1208 1209 cell.set_text(text+'\n'+lower_text);
1209 1210 cell.render();
1210 1211 };
1211 1212 this.delete_cell(index+1);
1212 1213 this.select(this.find_cell_index(cell));
1213 1214 };
1214 1215 };
1215 1216
1216 1217
1217 1218 // Cell collapsing and output clearing
1218 1219
1219 1220 /**
1220 1221 * Hide a cell's output.
1221 1222 *
1222 1223 * @method collapse
1223 1224 * @param {Number} index A cell's numeric index
1224 1225 */
1225 1226 Notebook.prototype.collapse = function (index) {
1226 1227 var i = this.index_or_selected(index);
1227 1228 this.get_cell(i).collapse();
1228 1229 this.set_dirty(true);
1229 1230 };
1230 1231
1231 1232 /**
1232 1233 * Show a cell's output.
1233 1234 *
1234 1235 * @method expand
1235 1236 * @param {Number} index A cell's numeric index
1236 1237 */
1237 1238 Notebook.prototype.expand = function (index) {
1238 1239 var i = this.index_or_selected(index);
1239 1240 this.get_cell(i).expand();
1240 1241 this.set_dirty(true);
1241 1242 };
1242 1243
1243 1244 /** Toggle whether a cell's output is collapsed or expanded.
1244 1245 *
1245 1246 * @method toggle_output
1246 1247 * @param {Number} index A cell's numeric index
1247 1248 */
1248 1249 Notebook.prototype.toggle_output = function (index) {
1249 1250 var i = this.index_or_selected(index);
1250 1251 this.get_cell(i).toggle_output();
1251 1252 this.set_dirty(true);
1252 1253 };
1253 1254
1254 1255 /**
1255 1256 * Toggle a scrollbar for long cell outputs.
1256 1257 *
1257 1258 * @method toggle_output_scroll
1258 1259 * @param {Number} index A cell's numeric index
1259 1260 */
1260 1261 Notebook.prototype.toggle_output_scroll = function (index) {
1261 1262 var i = this.index_or_selected(index);
1262 1263 this.get_cell(i).toggle_output_scroll();
1263 1264 };
1264 1265
1265 1266 /**
1266 1267 * Hide each code cell's output area.
1267 1268 *
1268 1269 * @method collapse_all_output
1269 1270 */
1270 1271 Notebook.prototype.collapse_all_output = function () {
1271 1272 var ncells = this.ncells();
1272 1273 var cells = this.get_cells();
1273 1274 for (var i=0; i<ncells; i++) {
1274 1275 if (cells[i] instanceof IPython.CodeCell) {
1275 1276 cells[i].output_area.collapse();
1276 1277 }
1277 1278 };
1278 1279 // this should not be set if the `collapse` key is removed from nbformat
1279 1280 this.set_dirty(true);
1280 1281 };
1281 1282
1282 1283 /**
1283 1284 * Expand each code cell's output area, and add a scrollbar for long output.
1284 1285 *
1285 1286 * @method scroll_all_output
1286 1287 */
1287 1288 Notebook.prototype.scroll_all_output = function () {
1288 1289 var ncells = this.ncells();
1289 1290 var cells = this.get_cells();
1290 1291 for (var i=0; i<ncells; i++) {
1291 1292 if (cells[i] instanceof IPython.CodeCell) {
1292 1293 cells[i].output_area.expand();
1293 1294 cells[i].output_area.scroll_if_long();
1294 1295 }
1295 1296 };
1296 1297 // this should not be set if the `collapse` key is removed from nbformat
1297 1298 this.set_dirty(true);
1298 1299 };
1299 1300
1300 1301 /**
1301 1302 * Expand each code cell's output area, and remove scrollbars.
1302 1303 *
1303 1304 * @method expand_all_output
1304 1305 */
1305 1306 Notebook.prototype.expand_all_output = function () {
1306 1307 var ncells = this.ncells();
1307 1308 var cells = this.get_cells();
1308 1309 for (var i=0; i<ncells; i++) {
1309 1310 if (cells[i] instanceof IPython.CodeCell) {
1310 1311 cells[i].output_area.expand();
1311 1312 cells[i].output_area.unscroll_area();
1312 1313 }
1313 1314 };
1314 1315 // this should not be set if the `collapse` key is removed from nbformat
1315 1316 this.set_dirty(true);
1316 1317 };
1317 1318
1318 1319 /**
1319 1320 * Clear each code cell's output area.
1320 1321 *
1321 1322 * @method clear_all_output
1322 1323 */
1323 1324 Notebook.prototype.clear_all_output = function () {
1324 1325 var ncells = this.ncells();
1325 1326 var cells = this.get_cells();
1326 1327 for (var i=0; i<ncells; i++) {
1327 1328 if (cells[i] instanceof IPython.CodeCell) {
1328 1329 cells[i].clear_output(true,true,true);
1329 1330 // Make all In[] prompts blank, as well
1330 1331 // TODO: make this configurable (via checkbox?)
1331 1332 cells[i].set_input_prompt();
1332 1333 }
1333 1334 };
1334 1335 this.set_dirty(true);
1335 1336 };
1336 1337
1337 1338
1338 1339 // Other cell functions: line numbers, ...
1339 1340
1340 1341 /**
1341 1342 * Toggle line numbers in the selected cell's input area.
1342 1343 *
1343 1344 * @method cell_toggle_line_numbers
1344 1345 */
1345 1346 Notebook.prototype.cell_toggle_line_numbers = function() {
1346 1347 this.get_selected_cell().toggle_line_numbers();
1347 1348 };
1348 1349
1349 1350 // Kernel related things
1350 1351
1351 1352 /**
1352 1353 * Start a new kernel and set it on each code cell.
1353 1354 *
1354 1355 * @method start_kernel
1355 1356 */
1356 1357 Notebook.prototype.start_kernel = function () {
1357 1358 var base_url = $('body').data('baseKernelUrl') + "kernels";
1358 1359 this.kernel = new IPython.Kernel(base_url);
1359 1360 this.kernel.start(this.notebook_id);
1360 1361 // Now that the kernel has been created, tell the CodeCells about it.
1361 1362 var ncells = this.ncells();
1362 1363 for (var i=0; i<ncells; i++) {
1363 1364 var cell = this.get_cell(i);
1364 1365 if (cell instanceof IPython.CodeCell) {
1365 1366 cell.set_kernel(this.kernel)
1366 1367 };
1367 1368 };
1368 1369 };
1369 1370
1370 1371 /**
1371 1372 * Prompt the user to restart the IPython kernel.
1372 1373 *
1373 1374 * @method restart_kernel
1374 1375 */
1375 1376 Notebook.prototype.restart_kernel = function () {
1376 1377 var that = this;
1377 1378 IPython.dialog.modal({
1378 1379 title : "Restart kernel or continue running?",
1379 1380 body : $("<p/>").html(
1380 1381 'Do you want to restart the current kernel? You will lose all variables defined in it.'
1381 1382 ),
1382 1383 buttons : {
1383 1384 "Continue running" : {},
1384 1385 "Restart" : {
1385 1386 "class" : "btn-danger",
1386 1387 "click" : function() {
1387 1388 that.kernel.restart();
1388 1389 }
1389 1390 }
1390 1391 }
1391 1392 });
1392 1393 };
1393 1394
1394 1395 /**
1395 1396 * Run the selected cell.
1396 1397 *
1397 1398 * Execute or render cell outputs.
1398 1399 *
1399 1400 * @method execute_selected_cell
1400 1401 * @param {Object} options Customize post-execution behavior
1401 1402 */
1402 1403 Notebook.prototype.execute_selected_cell = function (options) {
1403 1404 // add_new: should a new cell be added if we are at the end of the nb
1404 1405 // terminal: execute in terminal mode, which stays in the current cell
1405 1406 var default_options = {terminal: false, add_new: true};
1406 1407 $.extend(default_options, options);
1407 1408 var that = this;
1408 1409 var cell = that.get_selected_cell();
1409 1410 var cell_index = that.find_cell_index(cell);
1410 1411 if (cell instanceof IPython.CodeCell) {
1411 1412 cell.execute();
1412 1413 }
1413 1414 if (default_options.terminal) {
1414 1415 cell.select_all();
1415 1416 } else {
1416 1417 if ((cell_index === (that.ncells()-1)) && default_options.add_new) {
1417 1418 that.insert_cell_below('code');
1418 1419 // If we are adding a new cell at the end, scroll down to show it.
1419 1420 that.scroll_to_bottom();
1420 1421 } else {
1421 1422 that.select(cell_index+1);
1422 1423 };
1423 1424 };
1424 1425 this.set_dirty(true);
1425 1426 };
1426 1427
1427 1428 /**
1428 1429 * Execute all cells below the selected cell.
1429 1430 *
1430 1431 * @method execute_cells_below
1431 1432 */
1432 1433 Notebook.prototype.execute_cells_below = function () {
1433 1434 this.execute_cell_range(this.get_selected_index(), this.ncells());
1434 1435 this.scroll_to_bottom();
1435 1436 };
1436 1437
1437 1438 /**
1438 1439 * Execute all cells above the selected cell.
1439 1440 *
1440 1441 * @method execute_cells_above
1441 1442 */
1442 1443 Notebook.prototype.execute_cells_above = function () {
1443 1444 this.execute_cell_range(0, this.get_selected_index());
1444 1445 };
1445 1446
1446 1447 /**
1447 1448 * Execute all cells.
1448 1449 *
1449 1450 * @method execute_all_cells
1450 1451 */
1451 1452 Notebook.prototype.execute_all_cells = function () {
1452 1453 this.execute_cell_range(0, this.ncells());
1453 1454 this.scroll_to_bottom();
1454 1455 };
1455 1456
1456 1457 /**
1457 1458 * Execute a contiguous range of cells.
1458 1459 *
1459 1460 * @method execute_cell_range
1460 1461 * @param {Number} start Index of the first cell to execute (inclusive)
1461 1462 * @param {Number} end Index of the last cell to execute (exclusive)
1462 1463 */
1463 1464 Notebook.prototype.execute_cell_range = function (start, end) {
1464 1465 for (var i=start; i<end; i++) {
1465 1466 this.select(i);
1466 1467 this.execute_selected_cell({add_new:false});
1467 1468 };
1468 1469 };
1469 1470
1470 1471 // Persistance and loading
1471 1472
1472 1473 /**
1473 1474 * Getter method for this notebook's ID.
1474 1475 *
1475 1476 * @method get_notebook_id
1476 1477 * @return {String} This notebook's ID
1477 1478 */
1478 1479 Notebook.prototype.get_notebook_id = function () {
1479 1480 return this.notebook_id;
1480 1481 };
1481 1482
1482 1483 /**
1483 1484 * Getter method for this notebook's name.
1484 1485 *
1485 1486 * @method get_notebook_name
1486 1487 * @return {String} This notebook's name
1487 1488 */
1488 1489 Notebook.prototype.get_notebook_name = function () {
1489 1490 return this.notebook_name;
1490 1491 };
1491 1492
1492 1493 /**
1493 1494 * Setter method for this notebook's name.
1494 1495 *
1495 1496 * @method set_notebook_name
1496 1497 * @param {String} name A new name for this notebook
1497 1498 */
1498 1499 Notebook.prototype.set_notebook_name = function (name) {
1499 1500 this.notebook_name = name;
1500 1501 };
1501 1502
1502 1503 /**
1503 1504 * Check that a notebook's name is valid.
1504 1505 *
1505 1506 * @method test_notebook_name
1506 1507 * @param {String} nbname A name for this notebook
1507 1508 * @return {Boolean} True if the name is valid, false if invalid
1508 1509 */
1509 1510 Notebook.prototype.test_notebook_name = function (nbname) {
1510 1511 nbname = nbname || '';
1511 1512 if (this.notebook_name_blacklist_re.test(nbname) == false && nbname.length>0) {
1512 1513 return true;
1513 1514 } else {
1514 1515 return false;
1515 1516 };
1516 1517 };
1517 1518
1518 1519 /**
1519 1520 * Load a notebook from JSON (.ipynb).
1520 1521 *
1521 1522 * This currently handles one worksheet: others are deleted.
1522 1523 *
1523 1524 * @method fromJSON
1524 1525 * @param {Object} data JSON representation of a notebook
1525 1526 */
1526 1527 Notebook.prototype.fromJSON = function (data) {
1527 1528 var ncells = this.ncells();
1528 1529 var i;
1529 1530 for (i=0; i<ncells; i++) {
1530 1531 // Always delete cell 0 as they get renumbered as they are deleted.
1531 1532 this.delete_cell(0);
1532 1533 };
1533 1534 // Save the metadata and name.
1534 1535 this.metadata = data.metadata;
1535 1536 this.notebook_name = data.metadata.name;
1536 1537 // Only handle 1 worksheet for now.
1537 1538 var worksheet = data.worksheets[0];
1538 1539 if (worksheet !== undefined) {
1539 1540 if (worksheet.metadata) {
1540 1541 this.worksheet_metadata = worksheet.metadata;
1541 1542 }
1542 1543 var new_cells = worksheet.cells;
1543 1544 ncells = new_cells.length;
1544 1545 var cell_data = null;
1545 1546 var new_cell = null;
1546 1547 for (i=0; i<ncells; i++) {
1547 1548 cell_data = new_cells[i];
1548 1549 // VERSIONHACK: plaintext -> raw
1549 1550 // handle never-released plaintext name for raw cells
1550 1551 if (cell_data.cell_type === 'plaintext'){
1551 1552 cell_data.cell_type = 'raw';
1552 1553 }
1553 1554
1554 1555 new_cell = this.insert_cell_below(cell_data.cell_type);
1555 1556 new_cell.fromJSON(cell_data);
1556 1557 };
1557 1558 };
1558 1559 if (data.worksheets.length > 1) {
1559 1560 IPython.dialog.modal({
1560 1561 title : "Multiple worksheets",
1561 1562 body : "This notebook has " + data.worksheets.length + " worksheets, " +
1562 1563 "but this version of IPython can only handle the first. " +
1563 1564 "If you save this notebook, worksheets after the first will be lost.",
1564 1565 buttons : {
1565 1566 OK : {
1566 1567 class : "btn-danger"
1567 1568 }
1568 1569 }
1569 1570 });
1570 1571 }
1571 1572 };
1572 1573
1573 1574 /**
1574 1575 * Dump this notebook into a JSON-friendly object.
1575 1576 *
1576 1577 * @method toJSON
1577 1578 * @return {Object} A JSON-friendly representation of this notebook.
1578 1579 */
1579 1580 Notebook.prototype.toJSON = function () {
1580 1581 var cells = this.get_cells();
1581 1582 var ncells = cells.length;
1582 1583 var cell_array = new Array(ncells);
1583 1584 for (var i=0; i<ncells; i++) {
1584 1585 cell_array[i] = cells[i].toJSON();
1585 1586 };
1586 1587 var data = {
1587 1588 // Only handle 1 worksheet for now.
1588 1589 worksheets : [{
1589 1590 cells: cell_array,
1590 1591 metadata: this.worksheet_metadata
1591 1592 }],
1592 1593 metadata : this.metadata
1593 1594 };
1594 1595 return data;
1595 1596 };
1596 1597
1597 1598 /**
1598 1599 * Start an autosave timer, for periodically saving the notebook.
1599 1600 *
1600 1601 * @method set_autosave_interval
1601 1602 * @param {Integer} interval the autosave interval in milliseconds
1602 1603 */
1603 1604 Notebook.prototype.set_autosave_interval = function (interval) {
1604 1605 var that = this;
1605 1606 // clear previous interval, so we don't get simultaneous timers
1606 1607 if (this.autosave_timer) {
1607 1608 clearInterval(this.autosave_timer);
1608 1609 }
1609 1610
1610 1611 this.autosave_interval = this.minimum_autosave_interval = interval;
1611 1612 if (interval) {
1612 1613 this.autosave_timer = setInterval(function() {
1613 1614 if (that.dirty) {
1614 1615 that.save_notebook();
1615 1616 }
1616 1617 }, interval);
1617 1618 $([IPython.events]).trigger("autosave_enabled.Notebook", interval);
1618 1619 } else {
1619 1620 this.autosave_timer = null;
1620 1621 $([IPython.events]).trigger("autosave_disabled.Notebook");
1621 1622 };
1622 1623 };
1623 1624
1624 1625 /**
1625 1626 * Save this notebook on the server.
1626 1627 *
1627 1628 * @method save_notebook
1628 1629 */
1629 1630 Notebook.prototype.save_notebook = function () {
1630 1631 // We may want to move the name/id/nbformat logic inside toJSON?
1631 1632 var data = this.toJSON();
1632 1633 data.metadata.name = this.notebook_name;
1633 1634 data.nbformat = this.nbformat;
1634 1635 data.nbformat_minor = this.nbformat_minor;
1635 1636
1636 1637 // time the ajax call for autosave tuning purposes.
1637 1638 var start = new Date().getTime();
1638 1639
1639 1640 // We do the call with settings so we can set cache to false.
1640 1641 var settings = {
1641 1642 processData : false,
1642 1643 cache : false,
1643 1644 type : "PUT",
1644 1645 data : JSON.stringify(data),
1645 1646 headers : {'Content-Type': 'application/json'},
1646 1647 success : $.proxy(this.save_notebook_success, this, start),
1647 1648 error : $.proxy(this.save_notebook_error, this)
1648 1649 };
1649 1650 $([IPython.events]).trigger('notebook_saving.Notebook');
1650 1651 var url = this.baseProjectUrl() + 'notebooks/' + this.notebook_id;
1651 1652 $.ajax(url, settings);
1652 1653 };
1653 1654
1654 1655 /**
1655 1656 * Success callback for saving a notebook.
1656 1657 *
1657 1658 * @method save_notebook_success
1658 1659 * @param {Integer} start the time when the save request started
1659 1660 * @param {Object} data JSON representation of a notebook
1660 1661 * @param {String} status Description of response status
1661 1662 * @param {jqXHR} xhr jQuery Ajax object
1662 1663 */
1663 1664 Notebook.prototype.save_notebook_success = function (start, data, status, xhr) {
1664 1665 this.set_dirty(false);
1665 1666 $([IPython.events]).trigger('notebook_saved.Notebook');
1666 1667 this._update_autosave_interval(start);
1667 1668 if (this._checkpoint_after_save) {
1668 1669 this.create_checkpoint();
1669 1670 this._checkpoint_after_save = false;
1670 1671 };
1671 1672 };
1672 1673
1673 1674 /**
1674 1675 * update the autosave interval based on how long the last save took
1675 1676 *
1676 1677 * @method _update_autosave_interval
1677 1678 * @param {Integer} timestamp when the save request started
1678 1679 */
1679 1680 Notebook.prototype._update_autosave_interval = function (start) {
1680 1681 var duration = (new Date().getTime() - start);
1681 1682 if (this.autosave_interval) {
1682 1683 // new save interval: higher of 10x save duration or parameter (default 30 seconds)
1683 1684 var interval = Math.max(10 * duration, this.minimum_autosave_interval);
1684 1685 // round to 10 seconds, otherwise we will be setting a new interval too often
1685 1686 interval = 10000 * Math.round(interval / 10000);
1686 1687 // set new interval, if it's changed
1687 1688 if (interval != this.autosave_interval) {
1688 1689 this.set_autosave_interval(interval);
1689 1690 }
1690 1691 }
1691 1692 };
1692 1693
1693 1694 /**
1694 1695 * Failure callback for saving a notebook.
1695 1696 *
1696 1697 * @method save_notebook_error
1697 1698 * @param {jqXHR} xhr jQuery Ajax object
1698 1699 * @param {String} status Description of response status
1699 1700 * @param {String} error_msg HTTP error message
1700 1701 */
1701 1702 Notebook.prototype.save_notebook_error = function (xhr, status, error_msg) {
1702 1703 $([IPython.events]).trigger('notebook_save_failed.Notebook');
1703 1704 };
1704 1705
1705 1706 /**
1706 1707 * Request a notebook's data from the server.
1707 1708 *
1708 1709 * @method load_notebook
1709 1710 * @param {String} notebook_id A notebook to load
1710 1711 */
1711 1712 Notebook.prototype.load_notebook = function (notebook_id) {
1712 1713 var that = this;
1713 1714 this.notebook_id = notebook_id;
1714 1715 // We do the call with settings so we can set cache to false.
1715 1716 var settings = {
1716 1717 processData : false,
1717 1718 cache : false,
1718 1719 type : "GET",
1719 1720 dataType : "json",
1720 1721 success : $.proxy(this.load_notebook_success,this),
1721 1722 error : $.proxy(this.load_notebook_error,this),
1722 1723 };
1723 1724 $([IPython.events]).trigger('notebook_loading.Notebook');
1724 1725 var url = this.baseProjectUrl() + 'notebooks/' + this.notebook_id;
1725 1726 $.ajax(url, settings);
1726 1727 };
1727 1728
1728 1729 /**
1729 1730 * Success callback for loading a notebook from the server.
1730 1731 *
1731 1732 * Load notebook data from the JSON response.
1732 1733 *
1733 1734 * @method load_notebook_success
1734 1735 * @param {Object} data JSON representation of a notebook
1735 1736 * @param {String} status Description of response status
1736 1737 * @param {jqXHR} xhr jQuery Ajax object
1737 1738 */
1738 1739 Notebook.prototype.load_notebook_success = function (data, status, xhr) {
1739 1740 this.fromJSON(data);
1740 1741 if (this.ncells() === 0) {
1741 1742 this.insert_cell_below('code');
1742 1743 };
1743 1744 this.set_dirty(false);
1744 1745 this.select(0);
1745 1746 this.scroll_to_top();
1746 1747 if (data.orig_nbformat !== undefined && data.nbformat !== data.orig_nbformat) {
1747 1748 var msg = "This notebook has been converted from an older " +
1748 1749 "notebook format (v"+data.orig_nbformat+") to the current notebook " +
1749 1750 "format (v"+data.nbformat+"). The next time you save this notebook, the " +
1750 1751 "newer notebook format will be used and older versions of IPython " +
1751 1752 "may not be able to read it. To keep the older version, close the " +
1752 1753 "notebook without saving it.";
1753 1754 IPython.dialog.modal({
1754 1755 title : "Notebook converted",
1755 1756 body : msg,
1756 1757 buttons : {
1757 1758 OK : {
1758 1759 class : "btn-primary"
1759 1760 }
1760 1761 }
1761 1762 });
1762 1763 } else if (data.orig_nbformat_minor !== undefined && data.nbformat_minor !== data.orig_nbformat_minor) {
1763 1764 var that = this;
1764 1765 var orig_vs = 'v' + data.nbformat + '.' + data.orig_nbformat_minor;
1765 1766 var this_vs = 'v' + data.nbformat + '.' + this.nbformat_minor;
1766 1767 var msg = "This notebook is version " + orig_vs + ", but we only fully support up to " +
1767 1768 this_vs + ". You can still work with this notebook, but some features " +
1768 1769 "introduced in later notebook versions may not be available."
1769 1770
1770 1771 IPython.dialog.modal({
1771 1772 title : "Newer Notebook",
1772 1773 body : msg,
1773 1774 buttons : {
1774 1775 OK : {
1775 1776 class : "btn-danger"
1776 1777 }
1777 1778 }
1778 1779 });
1779 1780
1780 1781 }
1781 1782
1782 1783 // Create the kernel after the notebook is completely loaded to prevent
1783 1784 // code execution upon loading, which is a security risk.
1784 1785 this.start_kernel();
1785 1786 // load our checkpoint list
1786 1787 IPython.notebook.list_checkpoints();
1787 1788
1788 1789 $([IPython.events]).trigger('notebook_loaded.Notebook');
1789 1790 };
1790 1791
1791 1792 /**
1792 1793 * Failure callback for loading a notebook from the server.
1793 1794 *
1794 1795 * @method load_notebook_error
1795 1796 * @param {jqXHR} xhr jQuery Ajax object
1796 1797 * @param {String} textStatus Description of response status
1797 1798 * @param {String} errorThrow HTTP error message
1798 1799 */
1799 1800 Notebook.prototype.load_notebook_error = function (xhr, textStatus, errorThrow) {
1800 1801 if (xhr.status === 400) {
1801 1802 var msg = errorThrow;
1802 1803 } else if (xhr.status === 500) {
1803 1804 var msg = "An unknown error occurred while loading this notebook. " +
1804 1805 "This version can load notebook formats " +
1805 1806 "v" + this.nbformat + " or earlier.";
1806 1807 }
1807 1808 IPython.dialog.modal({
1808 1809 title: "Error loading notebook",
1809 1810 body : msg,
1810 1811 buttons : {
1811 1812 "OK": {}
1812 1813 }
1813 1814 });
1814 1815 }
1815 1816
1816 1817 /********************* checkpoint-related *********************/
1817 1818
1818 1819 /**
1819 1820 * Save the notebook then immediately create a checkpoint.
1820 1821 *
1821 1822 * @method save_checkpoint
1822 1823 */
1823 1824 Notebook.prototype.save_checkpoint = function () {
1824 1825 this._checkpoint_after_save = true;
1825 1826 this.save_notebook();
1826 1827 };
1827 1828
1828 1829 /**
1829 1830 * List checkpoints for this notebook.
1830 1831 *
1831 1832 * @method list_checkpoint
1832 1833 */
1833 1834 Notebook.prototype.list_checkpoints = function () {
1834 1835 var url = this.baseProjectUrl() + 'notebooks/' + this.notebook_id + '/checkpoints';
1835 1836 $.get(url).done(
1836 1837 $.proxy(this.list_checkpoints_success, this)
1837 1838 ).fail(
1838 1839 $.proxy(this.list_checkpoints_error, this)
1839 1840 );
1840 1841 };
1841 1842
1842 1843 /**
1843 1844 * Success callback for listing checkpoints.
1844 1845 *
1845 1846 * @method list_checkpoint_success
1846 1847 * @param {Object} data JSON representation of a checkpoint
1847 1848 * @param {String} status Description of response status
1848 1849 * @param {jqXHR} xhr jQuery Ajax object
1849 1850 */
1850 1851 Notebook.prototype.list_checkpoints_success = function (data, status, xhr) {
1851 1852 var data = $.parseJSON(data);
1852 1853 if (data.length) {
1853 1854 this.last_checkpoint = data[0];
1854 1855 } else {
1855 1856 this.last_checkpoint = null;
1856 1857 }
1857 1858 $([IPython.events]).trigger('checkpoints_listed.Notebook', [data]);
1858 1859 };
1859 1860
1860 1861 /**
1861 1862 * Failure callback for listing a checkpoint.
1862 1863 *
1863 1864 * @method list_checkpoint_error
1864 1865 * @param {jqXHR} xhr jQuery Ajax object
1865 1866 * @param {String} status Description of response status
1866 1867 * @param {String} error_msg HTTP error message
1867 1868 */
1868 1869 Notebook.prototype.list_checkpoints_error = function (xhr, status, error_msg) {
1869 1870 $([IPython.events]).trigger('list_checkpoints_failed.Notebook');
1870 1871 };
1871 1872
1872 1873 /**
1873 1874 * Create a checkpoint of this notebook on the server from the most recent save.
1874 1875 *
1875 1876 * @method create_checkpoint
1876 1877 */
1877 1878 Notebook.prototype.create_checkpoint = function () {
1878 1879 var url = this.baseProjectUrl() + 'notebooks/' + this.notebook_id + '/checkpoints';
1879 1880 $.post(url).done(
1880 1881 $.proxy(this.create_checkpoint_success, this)
1881 1882 ).fail(
1882 1883 $.proxy(this.create_checkpoint_error, this)
1883 1884 );
1884 1885 };
1885 1886
1886 1887 /**
1887 1888 * Success callback for creating a checkpoint.
1888 1889 *
1889 1890 * @method create_checkpoint_success
1890 1891 * @param {Object} data JSON representation of a checkpoint
1891 1892 * @param {String} status Description of response status
1892 1893 * @param {jqXHR} xhr jQuery Ajax object
1893 1894 */
1894 1895 Notebook.prototype.create_checkpoint_success = function (data, status, xhr) {
1895 1896 var data = $.parseJSON(data);
1896 1897 this.last_checkpoint = data;
1897 1898 $([IPython.events]).trigger('checkpoint_created.Notebook', data);
1898 1899 };
1899 1900
1900 1901 /**
1901 1902 * Failure callback for creating a checkpoint.
1902 1903 *
1903 1904 * @method create_checkpoint_error
1904 1905 * @param {jqXHR} xhr jQuery Ajax object
1905 1906 * @param {String} status Description of response status
1906 1907 * @param {String} error_msg HTTP error message
1907 1908 */
1908 1909 Notebook.prototype.create_checkpoint_error = function (xhr, status, error_msg) {
1909 1910 $([IPython.events]).trigger('checkpoint_failed.Notebook');
1910 1911 };
1911 1912
1912 1913 Notebook.prototype.restore_checkpoint_dialog = function (checkpoint) {
1913 1914 var that = this;
1914 1915 var checkpoint = checkpoint || this.last_checkpoint;
1915 1916 if ( ! checkpoint ) {
1916 1917 console.log("restore dialog, but no checkpoint to restore to!");
1917 1918 return;
1918 1919 }
1919 1920 var body = $('<div/>').append(
1920 1921 $('<p/>').addClass("p-space").text(
1921 1922 "Are you sure you want to revert the notebook to " +
1922 1923 "the latest checkpoint?"
1923 1924 ).append(
1924 1925 $("<strong/>").text(
1925 1926 " This cannot be undone."
1926 1927 )
1927 1928 )
1928 1929 ).append(
1929 1930 $('<p/>').addClass("p-space").text("The checkpoint was last updated at:")
1930 1931 ).append(
1931 1932 $('<p/>').addClass("p-space").text(
1932 1933 Date(checkpoint.last_modified)
1933 1934 ).css("text-align", "center")
1934 1935 );
1935 1936
1936 1937 IPython.dialog.modal({
1937 1938 title : "Revert notebook to checkpoint",
1938 1939 body : body,
1939 1940 buttons : {
1940 1941 Revert : {
1941 1942 class : "btn-danger",
1942 1943 click : function () {
1943 1944 that.restore_checkpoint(checkpoint.checkpoint_id);
1944 1945 }
1945 1946 },
1946 1947 Cancel : {}
1947 1948 }
1948 1949 });
1949 1950 }
1950 1951
1951 1952 /**
1952 1953 * Restore the notebook to a checkpoint state.
1953 1954 *
1954 1955 * @method restore_checkpoint
1955 1956 * @param {String} checkpoint ID
1956 1957 */
1957 1958 Notebook.prototype.restore_checkpoint = function (checkpoint) {
1958 1959 $([IPython.events]).trigger('checkpoint_restoring.Notebook', checkpoint);
1959 1960 var url = this.baseProjectUrl() + 'notebooks/' + this.notebook_id + '/checkpoints/' + checkpoint;
1960 1961 $.post(url).done(
1961 1962 $.proxy(this.restore_checkpoint_success, this)
1962 1963 ).fail(
1963 1964 $.proxy(this.restore_checkpoint_error, this)
1964 1965 );
1965 1966 };
1966 1967
1967 1968 /**
1968 1969 * Success callback for restoring a notebook to a checkpoint.
1969 1970 *
1970 1971 * @method restore_checkpoint_success
1971 1972 * @param {Object} data (ignored, should be empty)
1972 1973 * @param {String} status Description of response status
1973 1974 * @param {jqXHR} xhr jQuery Ajax object
1974 1975 */
1975 1976 Notebook.prototype.restore_checkpoint_success = function (data, status, xhr) {
1976 1977 $([IPython.events]).trigger('checkpoint_restored.Notebook');
1977 1978 this.load_notebook(this.notebook_id);
1978 1979 };
1979 1980
1980 1981 /**
1981 1982 * Failure callback for restoring a notebook to a checkpoint.
1982 1983 *
1983 1984 * @method restore_checkpoint_error
1984 1985 * @param {jqXHR} xhr jQuery Ajax object
1985 1986 * @param {String} status Description of response status
1986 1987 * @param {String} error_msg HTTP error message
1987 1988 */
1988 1989 Notebook.prototype.restore_checkpoint_error = function (xhr, status, error_msg) {
1989 1990 $([IPython.events]).trigger('checkpoint_restore_failed.Notebook');
1990 1991 };
1991 1992
1992 1993 /**
1993 1994 * Delete a notebook checkpoint.
1994 1995 *
1995 1996 * @method delete_checkpoint
1996 1997 * @param {String} checkpoint ID
1997 1998 */
1998 1999 Notebook.prototype.delete_checkpoint = function (checkpoint) {
1999 2000 $([IPython.events]).trigger('checkpoint_deleting.Notebook', checkpoint);
2000 2001 var url = this.baseProjectUrl() + 'notebooks/' + this.notebook_id + '/checkpoints/' + checkpoint;
2001 2002 $.ajax(url, {
2002 2003 type: 'DELETE',
2003 2004 success: $.proxy(this.delete_checkpoint_success, this),
2004 2005 error: $.proxy(this.delete_notebook_error,this)
2005 2006 });
2006 2007 };
2007 2008
2008 2009 /**
2009 2010 * Success callback for deleting a notebook checkpoint
2010 2011 *
2011 2012 * @method delete_checkpoint_success
2012 2013 * @param {Object} data (ignored, should be empty)
2013 2014 * @param {String} status Description of response status
2014 2015 * @param {jqXHR} xhr jQuery Ajax object
2015 2016 */
2016 2017 Notebook.prototype.delete_checkpoint_success = function (data, status, xhr) {
2017 2018 $([IPython.events]).trigger('checkpoint_deleted.Notebook', data);
2018 2019 this.load_notebook(this.notebook_id);
2019 2020 };
2020 2021
2021 2022 /**
2022 2023 * Failure callback for deleting a notebook checkpoint.
2023 2024 *
2024 2025 * @method delete_checkpoint_error
2025 2026 * @param {jqXHR} xhr jQuery Ajax object
2026 2027 * @param {String} status Description of response status
2027 2028 * @param {String} error_msg HTTP error message
2028 2029 */
2029 2030 Notebook.prototype.delete_checkpoint_error = function (xhr, status, error_msg) {
2030 2031 $([IPython.events]).trigger('checkpoint_delete_failed.Notebook');
2031 2032 };
2032 2033
2033 2034
2034 2035 IPython.Notebook = Notebook;
2035 2036
2036 2037
2037 2038 return IPython;
2038 2039
2039 2040 }(IPython));
2040 2041
@@ -1,175 +1,176
1 1 //----------------------------------------------------------------------------
2 2 // Copyright (C) 2008-2011 The IPython Development Team
3 3 //
4 4 // Distributed under the terms of the BSD License. The full license is in
5 5 // the file COPYING, distributed as part of this software.
6 6 //----------------------------------------------------------------------------
7 7
8 8 //============================================================================
9 9 // Pager
10 10 //============================================================================
11 11
12 12 var IPython = (function (IPython) {
13 "use strict";
13 14
14 15 var utils = IPython.utils;
15 16
16 17 var Pager = function (pager_selector, pager_splitter_selector) {
17 18 this.pager_element = $(pager_selector);
18 19 this.pager_button_area = $('#pager_button_area');
19 20 var that = this;
20 21 this.percentage_height = 0.40;
21 22 this.pager_splitter_element = $(pager_splitter_selector)
22 23 .draggable({
23 24 containment: 'window',
24 25 axis:'y',
25 26 helper: null ,
26 27 drag: function(event, ui) {
27 28 // recalculate the amount of space the pager should take
28 29 var pheight = ($(document.body).height()-event.clientY-4);
29 30 var downprct = pheight/IPython.layout_manager.app_height();
30 31 downprct = Math.min(0.9, downprct);
31 32 if (downprct < 0.1) {
32 33 that.percentage_height = 0.1;
33 34 that.collapse({'duration':0});
34 35 } else if (downprct > 0.2) {
35 36 that.percentage_height = downprct;
36 37 that.expand({'duration':0});
37 38 }
38 39 IPython.layout_manager.do_resize();
39 40 }
40 41 });
41 42 this.expanded = false;
42 43 this.style();
43 44 this.create_button_area();
44 45 this.bind_events();
45 46 };
46 47
47 48 Pager.prototype.create_button_area = function(){
48 49 var that = this;
49 50 this.pager_button_area.append(
50 51 $('<a>').attr('role', "button")
51 52 .attr('title',"Open the pager in an external window")
52 53 .addClass('ui-button')
53 54 .click(function(){that.detach()})
54 55 .attr('style','position: absolute; right: 20px;')
55 56 .append(
56 57 $('<span>').addClass("ui-icon ui-icon-extlink")
57 58 )
58 59 )
59 60 this.pager_button_area.append(
60 61 $('<a>').attr('role', "button")
61 62 .attr('title',"Close the pager")
62 63 .addClass('ui-button')
63 64 .click(function(){that.collapse()})
64 65 .attr('style','position: absolute; right: 5px;')
65 66 .append(
66 67 $('<span>').addClass("ui-icon ui-icon-close")
67 68 )
68 69 )
69 70 };
70 71
71 72 Pager.prototype.style = function () {
72 73 this.pager_splitter_element.addClass('border-box-sizing ui-widget ui-state-default');
73 74 this.pager_element.addClass('border-box-sizing');
74 75 this.pager_element.find(".container").addClass('border-box-sizing');
75 76 this.pager_splitter_element.attr('title', 'Click to Show/Hide pager area, drag to Resize');
76 77 };
77 78
78 79
79 80 Pager.prototype.bind_events = function () {
80 81 var that = this;
81 82
82 83 this.pager_element.bind('collapse_pager', function (event, extrap) {
83 84 var time = (extrap != undefined) ? ((extrap.duration != undefined ) ? extrap.duration : 'fast') : 'fast';
84 85 that.pager_element.hide(time);
85 86 });
86 87
87 88 this.pager_element.bind('expand_pager', function (event, extrap) {
88 89 var time = (extrap != undefined) ? ((extrap.duration != undefined ) ? extrap.duration : 'fast') : 'fast';
89 90 that.pager_element.show(time);
90 91 });
91 92
92 93 this.pager_splitter_element.hover(
93 94 function () {
94 95 that.pager_splitter_element.addClass('ui-state-hover');
95 96 },
96 97 function () {
97 98 that.pager_splitter_element.removeClass('ui-state-hover');
98 99 }
99 100 );
100 101
101 102 this.pager_splitter_element.click(function () {
102 103 that.toggle();
103 104 });
104 105
105 106 $([IPython.events]).on('open_with_text.Pager', function (event, data) {
106 107 if (data.text.trim() !== '') {
107 108 that.clear();
108 109 that.expand();
109 110 that.append_text(data.text);
110 111 };
111 112 });
112 113 };
113 114
114 115
115 116 Pager.prototype.collapse = function (extrap) {
116 117 if (this.expanded === true) {
117 118 this.expanded = false;
118 119 this.pager_element.add($('div#notebook')).trigger('collapse_pager', extrap);
119 120 };
120 121 };
121 122
122 123
123 124 Pager.prototype.expand = function (extrap) {
124 125 if (this.expanded !== true) {
125 126 this.expanded = true;
126 127 this.pager_element.add($('div#notebook')).trigger('expand_pager', extrap);
127 128 };
128 129 };
129 130
130 131
131 132 Pager.prototype.toggle = function () {
132 133 if (this.expanded === true) {
133 134 this.collapse();
134 135 } else {
135 136 this.expand();
136 137 };
137 138 };
138 139
139 140
140 141 Pager.prototype.clear = function (text) {
141 142 this.pager_element.find(".container").empty();
142 143 };
143 144
144 145 Pager.prototype.detach = function(){
145 146 var w = window.open("","_blank");
146 147 $(w.document.head)
147 148 .append(
148 149 $('<link>')
149 150 .attr('rel',"stylesheet")
150 151 .attr('href',"/static/css/notebook.css")
151 152 .attr('type',"text/css")
152 153 )
153 154 .append(
154 155 $('<title>').text("IPython Pager")
155 156 );
156 157 var pager_body = $(w.document.body);
157 158 pager_body.css('overflow','scroll');
158 159
159 160 pager_body.append(this.pager_element.clone().children());
160 161 w.document.close();
161 162 this.collapse();
162 163
163 164 }
164 165
165 166 Pager.prototype.append_text = function (text) {
166 167 this.pager_element.find(".container").append($('<pre/>').html(utils.fixCarriageReturn(utils.fixConsole(text))));
167 168 };
168 169
169 170
170 171 IPython.Pager = Pager;
171 172
172 173 return IPython;
173 174
174 175 }(IPython));
175 176
@@ -1,75 +1,76
1 1 //----------------------------------------------------------------------------
2 2 // Copyright (C) 2008-2011 The IPython Development Team
3 3 //
4 4 // Distributed under the terms of the BSD License. The full license is in
5 5 // the file COPYING, distributed as part of this software.
6 6 //----------------------------------------------------------------------------
7 7
8 8 //============================================================================
9 9 // QuickHelp button
10 10 //============================================================================
11 11
12 12 var IPython = (function (IPython) {
13 "use strict";
13 14
14 15 var QuickHelp = function (selector) {
15 16 };
16 17
17 18 QuickHelp.prototype.show_keyboard_shortcuts = function () {
18 19 // toggles display of keyboard shortcut dialog
19 20 var that = this;
20 21 if ( this.shortcut_dialog ){
21 22 // if dialog is already shown, close it
22 23 $(this.shortcut_dialog).modal("toggle");
23 24 return;
24 25 }
25 26 var body = $('<div/>');
26 27 var shortcuts = [
27 28 {key: 'Shift-Enter', help: 'run cell'},
28 29 {key: 'Ctrl-Enter', help: 'run cell in-place'},
29 30 {key: 'Alt-Enter', help: 'run cell, insert below'},
30 31 {key: 'Ctrl-m x', help: 'cut cell'},
31 32 {key: 'Ctrl-m c', help: 'copy cell'},
32 33 {key: 'Ctrl-m v', help: 'paste cell'},
33 34 {key: 'Ctrl-m d', help: 'delete cell'},
34 35 {key: 'Ctrl-m z', help: 'undo last cell deletion'},
35 36 {key: 'Ctrl-m -', help: 'split cell'},
36 37 {key: 'Ctrl-m a', help: 'insert cell above'},
37 38 {key: 'Ctrl-m b', help: 'insert cell below'},
38 39 {key: 'Ctrl-m o', help: 'toggle output'},
39 40 {key: 'Ctrl-m O', help: 'toggle output scroll'},
40 41 {key: 'Ctrl-m l', help: 'toggle line numbers'},
41 42 {key: 'Ctrl-m s', help: 'save notebook'},
42 43 {key: 'Ctrl-m j', help: 'move cell down'},
43 44 {key: 'Ctrl-m k', help: 'move cell up'},
44 45 {key: 'Ctrl-m y', help: 'code cell'},
45 46 {key: 'Ctrl-m m', help: 'markdown cell'},
46 47 {key: 'Ctrl-m t', help: 'raw cell'},
47 48 {key: 'Ctrl-m 1-6', help: 'heading 1-6 cell'},
48 49 {key: 'Ctrl-m p', help: 'select previous'},
49 50 {key: 'Ctrl-m n', help: 'select next'},
50 51 {key: 'Ctrl-m i', help: 'interrupt kernel'},
51 52 {key: 'Ctrl-m .', help: 'restart kernel'},
52 53 {key: 'Ctrl-m h', help: 'show keyboard shortcuts'}
53 54 ];
54 55 for (var i=0; i<shortcuts.length; i++) {
55 56 body.append($('<div>').addClass('quickhelp').
56 57 append($('<span/>').addClass('shortcut_key').html(shortcuts[i].key)).
57 58 append($('<span/>').addClass('shortcut_descr').html(' : ' + shortcuts[i].help))
58 59 );
59 60 };
60 61 this.shortcut_dialog = IPython.dialog.modal({
61 62 title : "Keyboard shortcuts",
62 63 body : body,
63 64 destroy : false,
64 65 buttons : {
65 66 Close : {}
66 67 }
67 68 });
68 69 };
69 70
70 71 // Set module variables
71 72 IPython.QuickHelp = QuickHelp;
72 73
73 74 return IPython;
74 75
75 76 }(IPython));
@@ -1,157 +1,158
1 1 //----------------------------------------------------------------------------
2 2 // Copyright (C) 2008-2011 The IPython Development Team
3 3 //
4 4 // Distributed under the terms of the BSD License. The full license is in
5 5 // the file COPYING, distributed as part of this software.
6 6 //----------------------------------------------------------------------------
7 7
8 8 //============================================================================
9 9 // SaveWidget
10 10 //============================================================================
11 11
12 12 var IPython = (function (IPython) {
13 "use strict";
13 14
14 15 var utils = IPython.utils;
15 16
16 17 var SaveWidget = function (selector) {
17 18 this.selector = selector;
18 19 if (this.selector !== undefined) {
19 20 this.element = $(selector);
20 21 this.style();
21 22 this.bind_events();
22 23 }
23 24 };
24 25
25 26
26 27 SaveWidget.prototype.style = function () {
27 28 };
28 29
29 30
30 31 SaveWidget.prototype.bind_events = function () {
31 32 var that = this;
32 33 this.element.find('span#notebook_name').click(function () {
33 34 that.rename_notebook();
34 35 });
35 36 this.element.find('span#notebook_name').hover(function () {
36 37 $(this).addClass("ui-state-hover");
37 38 }, function () {
38 39 $(this).removeClass("ui-state-hover");
39 40 });
40 41 $([IPython.events]).on('notebook_loaded.Notebook', function () {
41 42 that.update_notebook_name();
42 43 that.update_document_title();
43 44 });
44 45 $([IPython.events]).on('notebook_saved.Notebook', function () {
45 46 that.update_notebook_name();
46 47 that.update_document_title();
47 48 });
48 49 $([IPython.events]).on('notebook_save_failed.Notebook', function () {
49 50 that.set_save_status('Autosave Failed!');
50 51 });
51 52 $([IPython.events]).on('checkpoints_listed.Notebook', function (event, data) {
52 53 that.set_last_checkpoint(data[0]);
53 54 });
54 55
55 56 $([IPython.events]).on('checkpoint_created.Notebook', function (event, data) {
56 57 that.set_last_checkpoint(data);
57 58 });
58 59 $([IPython.events]).on('set_dirty.Notebook', function (event, data) {
59 60 that.set_autosaved(data.value);
60 61 });
61 62 };
62 63
63 64
64 65 SaveWidget.prototype.rename_notebook = function () {
65 66 var that = this;
66 67 var dialog = $('<div/>').append(
67 68 $("<p/>").addClass("rename-message")
68 69 .html('Enter a new notebook name:')
69 70 ).append(
70 71 $("<br/>")
71 72 ).append(
72 73 $('<input/>').attr('type','text').attr('size','25')
73 74 .val(IPython.notebook.get_notebook_name())
74 75 );
75 76 IPython.dialog.modal({
76 77 title: "Rename Notebook",
77 78 body: dialog,
78 79 buttons : {
79 80 "Cancel": {},
80 81 "OK": {
81 82 class: "btn-primary",
82 83 click: function () {
83 84 var new_name = $(this).find('input').val();
84 85 if (!IPython.notebook.test_notebook_name(new_name)) {
85 86 $(this).find('.rename-message').html(
86 87 "Invalid notebook name. Notebook names must "+
87 88 "have 1 or more characters and can contain any characters " +
88 89 "except :/\\. Please enter a new notebook name:"
89 90 );
90 91 return false;
91 92 } else {
92 93 IPython.notebook.set_notebook_name(new_name);
93 94 IPython.notebook.save_notebook();
94 95 }
95 96 }}
96 97 },
97 98 open : function (event, ui) {
98 99 var that = $(this);
99 100 // Upon ENTER, click the OK button.
100 101 that.find('input[type="text"]').keydown(function (event, ui) {
101 102 if (event.which === utils.keycodes.ENTER) {
102 103 that.find('.btn-primary').first().click();
103 104 return false;
104 105 }
105 106 });
106 107 that.find('input[type="text"]').focus();
107 108 }
108 109 });
109 110 }
110 111
111 112
112 113 SaveWidget.prototype.update_notebook_name = function () {
113 114 var nbname = IPython.notebook.get_notebook_name();
114 115 this.element.find('span#notebook_name').html(nbname);
115 116 };
116 117
117 118
118 119 SaveWidget.prototype.update_document_title = function () {
119 120 var nbname = IPython.notebook.get_notebook_name();
120 121 document.title = nbname;
121 122 };
122 123
123 124
124 125 SaveWidget.prototype.set_save_status = function (msg) {
125 126 this.element.find('span#autosave_status').html(msg);
126 127 }
127 128
128 129 SaveWidget.prototype.set_checkpoint_status = function (msg) {
129 130 this.element.find('span#checkpoint_status').html(msg);
130 131 }
131 132
132 133 SaveWidget.prototype.set_last_checkpoint = function (checkpoint) {
133 134 if (!checkpoint) {
134 135 this.set_checkpoint_status("");
135 136 return;
136 137 }
137 138 var d = new Date(checkpoint.last_modified);
138 139 this.set_checkpoint_status(
139 140 "Last Checkpoint: " + d.format('mmm dd HH:MM')
140 141 );
141 142 }
142 143
143 144 SaveWidget.prototype.set_autosaved = function (dirty) {
144 145 if (dirty) {
145 146 this.set_save_status("(unsaved changes)");
146 147 } else {
147 148 this.set_save_status("(autosaved)");
148 149 }
149 150 };
150 151
151 152
152 153 IPython.SaveWidget = SaveWidget;
153 154
154 155 return IPython;
155 156
156 157 }(IPython));
157 158
@@ -1,108 +1,109
1 1 //----------------------------------------------------------------------------
2 2 // Copyright (C) 2008 The IPython Development Team
3 3 //
4 4 // Distributed under the terms of the BSD License. The full license is in
5 5 // the file COPYING, distributed as part of this software.
6 6 //----------------------------------------------------------------------------
7 7
8 8 //============================================================================
9 9 // ToolBar
10 10 //============================================================================
11 11 /**
12 12 * @module IPython
13 13 * @namespace IPython
14 14 * @submodule ToolBar
15 15 */
16 16
17 17 var IPython = (function (IPython) {
18 "use strict";
18 19
19 20 /**
20 21 * A generic toolbar on which one can add button
21 22 * @class ToolBar
22 23 * @constructor
23 24 * @param {Dom object} selector
24 25 */
25 26 var ToolBar = function (selector) {
26 27 this.selector = selector;
27 28 if (this.selector !== undefined) {
28 29 this.element = $(selector);
29 30 this.style();
30 31 }
31 32 };
32 33
33 34 /**
34 35 * add a group of button into the current toolbar.
35 36 *
36 37 *
37 38 * @example
38 39 *
39 40 * IPython.toolbar.add_buttons_group([
40 41 * {
41 42 * label:'my button',
42 43 * icon:'icon-hdd',
43 44 * callback:function(){alert('hoho')},
44 45 * id : 'my_button_id', // this is optional
45 46 * },
46 47 * {
47 48 * label:'my second button',
48 49 * icon:'icon-play',
49 50 * callback:function(){alert('be carefull I cut')}
50 51 * }
51 52 * ],
52 53 * "my_button_group_id"
53 54 * )
54 55 *
55 56 * @method add_buttons_group
56 57 * @param list {List}
57 58 * List of button of the group, with the following paramter for each :
58 59 * @param list.label {string} text to show on button hover
59 60 * @param list.icon {string} icon to choose from [Font Awesome](http://fortawesome.github.io/Font-Awesome)
60 61 * @param list.callback {function} function to be called on button click
61 62 * @param [list.id] {String} id to give to the button
62 63 * @param [group_id] {String} optionnal id to give to the group
63 64 *
64 65 */
65 66 ToolBar.prototype.add_buttons_group = function (list, group_id) {
66 67 var btn_group = $('<div/>').addClass("btn-group");
67 68 if( group_id != undefined ) {
68 69 btn_group.attr('id',group_id);
69 70 }
70 71 for(var el in list) {
71 72 var button = $('<button/>')
72 73 .addClass('btn')
73 74 .attr("title", list[el].label)
74 75 .append(
75 76 $("<i/>").addClass(list[el].icon)
76 77 );
77 78 var id = list[el].id;
78 79 if( id != undefined )
79 80 button.attr('id',id);
80 81 var fun = list[el].callback;
81 82 button.click(fun);
82 83 btn_group.append(button);
83 84 }
84 85 $(this.selector).append(btn_group);
85 86 };
86 87
87 88 ToolBar.prototype.style = function () {
88 89 this.element.addClass('border-box-sizing')
89 90 .addClass('toolbar');
90 91 };
91 92
92 93 /**
93 94 * Show and hide toolbar
94 95 * @method toggle
95 96 */
96 97 ToolBar.prototype.toggle = function () {
97 98 this.element.toggle();
98 99 if (IPython.layout_manager != undefined) {
99 100 IPython.layout_manager.do_resize();
100 101 }
101 102 };
102 103
103 104
104 105 IPython.ToolBar = ToolBar;
105 106
106 107 return IPython;
107 108
108 109 }(IPython));
General Comments 0
You need to be logged in to leave comments. Login now