##// END OF EJS Templates
More review changes
Jonathan Frederic -
Show More
@@ -1,161 +1,163 b''
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 4 define([
5 5 'base/js/namespace',
6 6 'jquery',
7 7 ], function(IPython, $) {
8 8 "use strict";
9 9
10 10 var modal = function (options) {
11 11
12 12 var modal = $("<div/>")
13 13 .addClass("modal")
14 14 .addClass("fade")
15 15 .attr("role", "dialog");
16 16 var dialog = $("<div/>")
17 17 .addClass("modal-dialog")
18 18 .appendTo(modal);
19 19 var dialog_content = $("<div/>")
20 20 .addClass("modal-content")
21 21 .appendTo(dialog);
22 22 dialog_content.append(
23 23 $("<div/>")
24 24 .addClass("modal-header")
25 25 .append($("<button>")
26 26 .attr("type", "button")
27 27 .addClass("close")
28 28 .attr("data-dismiss", "modal")
29 29 .attr("aria-hidden", "true")
30 30 .html("&times;")
31 31 ).append(
32 32 $("<h4/>")
33 33 .addClass('modal-title')
34 34 .text(options.title || "")
35 35 )
36 36 ).append(
37 37 $("<div/>").addClass("modal-body").append(
38 38 options.body || $("<p/>")
39 39 )
40 40 );
41 41
42 42 var footer = $("<div/>").addClass("modal-footer");
43 43
44 44 for (var label in options.buttons) {
45 45 var btn_opts = options.buttons[label];
46 46 var button = $("<button/>")
47 47 .addClass("btn btn-default btn-sm")
48 48 .attr("data-dismiss", "modal")
49 49 .text(label);
50 50 if (btn_opts.click) {
51 51 button.click($.proxy(btn_opts.click, dialog_content));
52 52 }
53 53 if (btn_opts.class) {
54 54 button.addClass(btn_opts.class);
55 55 }
56 56 footer.append(button);
57 57 }
58 58 dialog_content.append(footer);
59 59 // hook up on-open event
60 60 modal.on("shown.bs.modal", function() {
61 61 setTimeout(function() {
62 62 footer.find("button").last().focus();
63 63 if (options.open) {
64 64 $.proxy(options.open, modal)();
65 65 }
66 66 }, 0);
67 67 });
68 68
69 69 // destroy modal on hide, unless explicitly asked not to
70 70 if (options.destroy === undefined || options.destroy) {
71 71 modal.on("hidden.bs.modal", function () {
72 72 modal.remove();
73 73 });
74 74 }
75 75 modal.on("hidden.bs.modal", function () {
76 76 if (options.notebook) {
77 77 var cell = options.notebook.get_selected_cell();
78 78 if (cell) cell.select();
79 79 }
80 80 if (options.keyboard_manager) {
81 81 options.keyboard_manager.enable();
82 82 options.keyboard_manager.command_mode();
83 83 }
84 84 });
85 85
86 86 if (options.keyboard_manager) {
87 87 options.keyboard_manager.disable();
88 88 }
89 89
90 90 return modal.modal(options);
91 91 };
92 92
93 var edit_metadata = function (md, callback, name, keyboard_manager, notebook) {
94 name = name || "Cell";
93 var edit_metadata = function (options) {
94 options.name = options.name || "Cell";
95 95 var error_div = $('<div/>').css('color', 'red');
96 96 var message =
97 "Manually edit the JSON below to manipulate the metadata for this " + name + "." +
97 "Manually edit the JSON below to manipulate the metadata for this " + options.name + "." +
98 98 " We recommend putting custom metadata attributes in an appropriately named sub-structure," +
99 99 " so they don't conflict with those of others.";
100 100
101 101 var textarea = $('<textarea/>')
102 102 .attr('rows', '13')
103 103 .attr('cols', '80')
104 104 .attr('name', 'metadata')
105 .text(JSON.stringify(md || {}, null, 2));
105 .text(JSON.stringify(options.md || {}, null, 2));
106 106
107 107 var dialogform = $('<div/>').attr('title', 'Edit the metadata')
108 108 .append(
109 109 $('<form/>').append(
110 110 $('<fieldset/>').append(
111 111 $('<label/>')
112 112 .attr('for','metadata')
113 113 .text(message)
114 114 )
115 115 .append(error_div)
116 116 .append($('<br/>'))
117 117 .append(textarea)
118 118 )
119 119 );
120 120 var editor = CodeMirror.fromTextArea(textarea[0], {
121 121 lineNumbers: true,
122 122 matchBrackets: true,
123 123 indentUnit: 2,
124 124 autoIndent: true,
125 125 mode: 'application/json',
126 126 });
127 127 var modal = modal({
128 title: "Edit " + name + " Metadata",
128 title: "Edit " + options.name + " Metadata",
129 129 body: dialogform,
130 130 buttons: {
131 131 OK: { class : "btn-primary",
132 132 click: function() {
133 133 // validate json and set it
134 134 var new_md;
135 135 try {
136 136 new_md = JSON.parse(editor.getValue());
137 137 } catch(e) {
138 138 console.log(e);
139 139 error_div.text('WARNING: Could not save invalid JSON.');
140 140 return false;
141 141 }
142 callback(new_md);
142 options.callback(new_md);
143 143 }
144 144 },
145 145 Cancel: {}
146 }
147 }, keyboard_manager, notebook);
146 },
147 notebook: options.notebook,
148 keyboard_manager: options.keyboard_manager,
149 });
148 150
149 151 modal.on('shown.bs.modal', function(){ editor.refresh(); });
150 152 };
151 153
152 154 var dialog = {
153 155 modal : modal,
154 156 edit_metadata : edit_metadata,
155 157 };
156 158
157 159 // Backwards compatability.
158 IPython.Dialog = dialog;
160 IPython.dialog = dialog;
159 161
160 162 return dialog;
161 163 });
@@ -1,254 +1,259 b''
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 4 define([
5 5 'base/js/namespace',
6 6 'jquery',
7 7 'base/js/utils',
8 8 ], function(IPython, $, utils) {
9 9 "use strict";
10 10
11 11
12 12 // Setup global keycodes and inverse keycodes.
13 13
14 14 // See http://unixpapa.com/js/key.html for a complete description. The short of
15 15 // it is that there are different keycode sets. Firefox uses the "Mozilla keycodes"
16 16 // and Webkit/IE use the "IE keycodes". These keycode sets are mostly the same
17 17 // but have minor differences.
18 18
19 19 // These apply to Firefox, (Webkit and IE)
20 20 var _keycodes = {
21 21 'a': 65, 'b': 66, 'c': 67, 'd': 68, 'e': 69, 'f': 70, 'g': 71, 'h': 72, 'i': 73,
22 22 'j': 74, 'k': 75, 'l': 76, 'm': 77, 'n': 78, 'o': 79, 'p': 80, 'q': 81, 'r': 82,
23 23 's': 83, 't': 84, 'u': 85, 'v': 86, 'w': 87, 'x': 88, 'y': 89, 'z': 90,
24 24 '1 !': 49, '2 @': 50, '3 #': 51, '4 $': 52, '5 %': 53, '6 ^': 54,
25 25 '7 &': 55, '8 *': 56, '9 (': 57, '0 )': 48,
26 26 '[ {': 219, '] }': 221, '` ~': 192, ', <': 188, '. >': 190, '/ ?': 191,
27 27 '\\ |': 220, '\' "': 222,
28 28 'numpad0': 96, 'numpad1': 97, 'numpad2': 98, 'numpad3': 99, 'numpad4': 100,
29 29 'numpad5': 101, 'numpad6': 102, 'numpad7': 103, 'numpad8': 104, 'numpad9': 105,
30 30 'multiply': 106, 'add': 107, 'subtract': 109, 'decimal': 110, 'divide': 111,
31 31 'f1': 112, 'f2': 113, 'f3': 114, 'f4': 115, 'f5': 116, 'f6': 117, 'f7': 118,
32 32 'f8': 119, 'f9': 120, 'f11': 122, 'f12': 123, 'f13': 124, 'f14': 125, 'f15': 126,
33 33 'backspace': 8, 'tab': 9, 'enter': 13, 'shift': 16, 'ctrl': 17, 'alt': 18,
34 34 'meta': 91, 'capslock': 20, 'esc': 27, 'space': 32, 'pageup': 33, 'pagedown': 34,
35 35 'end': 35, 'home': 36, 'left': 37, 'up': 38, 'right': 39, 'down': 40,
36 36 'insert': 45, 'delete': 46, 'numlock': 144,
37 37 };
38 38
39 39 // These apply to Firefox and Opera
40 40 var _mozilla_keycodes = {
41 41 '; :': 59, '= +': 61, '- _': 173, 'meta': 224
42 42 };
43 43
44 44 // This apply to Webkit and IE
45 45 var _ie_keycodes = {
46 46 '; :': 186, '= +': 187, '- _': 189
47 47 };
48 48
49 49 var browser = utils.browser[0];
50 50 var platform = utils.platform;
51 51
52 52 if (browser === 'Firefox' || browser === 'Opera' || browser === 'Netscape') {
53 53 $.extend(_keycodes, _mozilla_keycodes);
54 54 } else if (browser === 'Safari' || browser === 'Chrome' || browser === 'MSIE') {
55 55 $.extend(_keycodes, _ie_keycodes);
56 56 }
57 57
58 58 var keycodes = {};
59 59 var inv_keycodes = {};
60 60 for (var name in _keycodes) {
61 61 var names = name.split(' ');
62 62 if (names.length === 1) {
63 63 var n = names[0];
64 64 keycodes[n] = _keycodes[n];
65 65 inv_keycodes[_keycodes[n]] = n;
66 66 } else {
67 67 var primary = names[0];
68 68 var secondary = names[1];
69 69 keycodes[primary] = _keycodes[name];
70 70 keycodes[secondary] = _keycodes[name];
71 71 inv_keycodes[_keycodes[name]] = primary;
72 72 }
73 73 }
74 74
75 75 var normalize_key = function (key) {
76 76 return inv_keycodes[keycodes[key]];
77 77 };
78 78
79 79 var normalize_shortcut = function (shortcut) {
80 80 // Put a shortcut into normalized form:
81 81 // 1. Make lowercase
82 82 // 2. Replace cmd by meta
83 83 // 3. Sort '-' separated modifiers into the order alt-ctrl-meta-shift
84 84 // 4. Normalize keys
85 85 shortcut = shortcut.toLowerCase().replace('cmd', 'meta');
86 86 shortcut = shortcut.replace(/-$/, '_'); // catch shortcuts using '-' key
87 87 var values = shortcut.split("-");
88 88 if (values.length === 1) {
89 89 return normalize_key(values[0]);
90 90 } else {
91 91 var modifiers = values.slice(0,-1);
92 92 var key = normalize_key(values[values.length-1]);
93 93 modifiers.sort();
94 94 return modifiers.join('-') + '-' + key;
95 95 }
96 96 };
97 97
98 98 var shortcut_to_event = function (shortcut, type) {
99 99 // Convert a shortcut (shift-r) to a jQuery Event object
100 100 type = type || 'keydown';
101 101 shortcut = normalize_shortcut(shortcut);
102 102 shortcut = shortcut.replace(/-$/, '_'); // catch shortcuts using '-' key
103 103 var values = shortcut.split("-");
104 104 var modifiers = values.slice(0,-1);
105 105 var key = values[values.length-1];
106 106 var opts = {which: keycodes[key]};
107 107 if (modifiers.indexOf('alt') !== -1) {opts.altKey = true;}
108 108 if (modifiers.indexOf('ctrl') !== -1) {opts.ctrlKey = true;}
109 109 if (modifiers.indexOf('meta') !== -1) {opts.metaKey = true;}
110 110 if (modifiers.indexOf('shift') !== -1) {opts.shiftKey = true;}
111 111 return $.Event(type, opts);
112 112 };
113 113
114 114 var event_to_shortcut = function (event) {
115 115 // Convert a jQuery Event object to a shortcut (shift-r)
116 116 var shortcut = '';
117 117 var key = inv_keycodes[event.which];
118 118 if (event.altKey && key !== 'alt') {shortcut += 'alt-';}
119 119 if (event.ctrlKey && key !== 'ctrl') {shortcut += 'ctrl-';}
120 120 if (event.metaKey && key !== 'meta') {shortcut += 'meta-';}
121 121 if (event.shiftKey && key !== 'shift') {shortcut += 'shift-';}
122 122 shortcut += key;
123 123 return shortcut;
124 124 };
125 125
126 126 // Shortcut manager class
127 127
128 128 var ShortcutManager = function (delay, events) {
129 129 this._shortcuts = {};
130 130 this._counts = {};
131 131 this._timers = {};
132 132 this.delay = delay || 800; // delay in milliseconds
133 133 this.events = events;
134 134 };
135 135
136 136 ShortcutManager.prototype.help = function () {
137 137 var help = [];
138 138 for (var shortcut in this._shortcuts) {
139 139 var help_string = this._shortcuts[shortcut].help;
140 140 var help_index = this._shortcuts[shortcut].help_index;
141 141 if (help_string) {
142 142 if (platform === 'MacOS') {
143 143 shortcut = shortcut.replace('meta', 'cmd');
144 144 }
145 145 help.push({
146 146 shortcut: shortcut,
147 147 help: help_string,
148 148 help_index: help_index}
149 149 );
150 150 }
151 151 }
152 152 help.sort(function (a, b) {
153 153 if (a.help_index > b.help_index)
154 154 return 1;
155 155 if (a.help_index < b.help_index)
156 156 return -1;
157 157 return 0;
158 158 });
159 159 return help;
160 160 };
161 161
162 162 ShortcutManager.prototype.clear_shortcuts = function () {
163 163 this._shortcuts = {};
164 164 };
165 165
166 166 ShortcutManager.prototype.add_shortcut = function (shortcut, data, suppress_help_update) {
167 167 if (typeof(data) === 'function') {
168 168 data = {help: '', help_index: '', handler: data};
169 169 }
170 170 data.help_index = data.help_index || '';
171 171 data.help = data.help || '';
172 172 data.count = data.count || 1;
173 173 if (data.help_index === '') {
174 174 data.help_index = 'zz';
175 175 }
176 176 shortcut = normalize_shortcut(shortcut);
177 177 this._counts[shortcut] = 0;
178 178 this._shortcuts[shortcut] = data;
179 179 if (!suppress_help_update) {
180 180 // update the keyboard shortcuts notebook help
181 181 this.events.trigger('rebuild.QuickHelp');
182 182 }
183 183 };
184 184
185 185 ShortcutManager.prototype.add_shortcuts = function (data) {
186 186 for (var shortcut in data) {
187 187 this.add_shortcut(shortcut, data[shortcut], true);
188 188 }
189 189 // update the keyboard shortcuts notebook help
190 190 this.events.trigger('rebuild.QuickHelp');
191 191 };
192 192
193 193 ShortcutManager.prototype.remove_shortcut = function (shortcut, suppress_help_update) {
194 194 shortcut = normalize_shortcut(shortcut);
195 195 delete this._counts[shortcut];
196 196 delete this._shortcuts[shortcut];
197 197 if (!suppress_help_update) {
198 198 // update the keyboard shortcuts notebook help
199 199 this.events.trigger('rebuild.QuickHelp');
200 200 }
201 201 };
202 202
203 203 ShortcutManager.prototype.count_handler = function (shortcut, event, data) {
204 204 var that = this;
205 205 var c = this._counts;
206 206 var t = this._timers;
207 207 var timer = null;
208 208 if (c[shortcut] === data.count-1) {
209 209 c[shortcut] = 0;
210 210 timer = t[shortcut];
211 211 if (timer) {clearTimeout(timer); delete t[shortcut];}
212 212 return data.handler(event);
213 213 } else {
214 214 c[shortcut] = c[shortcut] + 1;
215 215 timer = setTimeout(function () {
216 216 c[shortcut] = 0;
217 217 }, that.delay);
218 218 t[shortcut] = timer;
219 219 }
220 220 return false;
221 221 };
222 222
223 223 ShortcutManager.prototype.call_handler = function (event) {
224 224 var shortcut = event_to_shortcut(event);
225 225 var data = this._shortcuts[shortcut];
226 226 if (data) {
227 227 var handler = data.handler;
228 228 if (handler) {
229 229 if (data.count === 1) {
230 230 return handler(event);
231 231 } else if (data.count > 1) {
232 232 return this.count_handler(shortcut, event, data);
233 233 }
234 234 }
235 235 }
236 236 return true;
237 237 };
238 238
239 239 ShortcutManager.prototype.handles = function (event) {
240 240 var shortcut = event_to_shortcut(event);
241 241 var data = this._shortcuts[shortcut];
242 242 return !( data === undefined || data.handler === undefined );
243 243 };
244 244
245 return {
245 var keyboard = {
246 246 keycodes : keycodes,
247 247 inv_keycodes : inv_keycodes,
248 248 ShortcutManager : ShortcutManager,
249 249 normalize_key : normalize_key,
250 250 normalize_shortcut : normalize_shortcut,
251 251 shortcut_to_event : shortcut_to_event,
252 252 event_to_shortcut : event_to_shortcut
253 253 };
254
255 // For backwards compatability.
256 IPython.keyboard = keyboard;
257
258 return keyboard;
254 259 });
@@ -1,48 +1,45 b''
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 4 define([
5 5 'base/js/namespace',
6 6 'jquery',
7 'components/jquery-ui/ui/minified/jquery-ui.min',
8 'components/bootstrap/js/bootstrap.min',
9 'auth/js/loginwidget'
10 7 ], function(IPython, $){
11 8 "use strict";
12 9
13 10 var Page = function () {
14 11 this.style();
15 12 this.bind_events();
16 13 };
17 14
18 15 Page.prototype.style = function () {
19 16 $('div#header').addClass('border-box-sizing');
20 17 $('div#site').addClass('border-box-sizing');
21 18 };
22 19
23 20 Page.prototype.bind_events = function () {
24 21 };
25 22
26 23 Page.prototype.show = function () {
27 24 // The header and site divs start out hidden to prevent FLOUC.
28 25 // Main scripts should call this method after styling everything.
29 26 this.show_header();
30 27 this.show_site();
31 28 };
32 29
33 30 Page.prototype.show_header = function () {
34 31 // The header and site divs start out hidden to prevent FLOUC.
35 32 // Main scripts should call this method after styling everything.
36 33 $('div#header').css('display','block');
37 34 };
38 35
39 36 Page.prototype.show_site = function () {
40 37 // The header and site divs start out hidden to prevent FLOUC.
41 38 // Main scripts should call this method after styling everything.
42 39 $('div#site').css('display','block');
43 40 };
44 41
45 42 // Register self in the global namespace for convenience.
46 43 IPython.Page = Page;
47 44 return {'Page': Page};
48 45 });
@@ -1,10 +1,7 b''
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 var ipython = ipython || {};
5 4 require(['base/js/page'], function(page) {
6 5 var page_instance = new page.Page();
7 6 page_instance.show();
8
9 ipython.page = page_instance;
10 7 });
@@ -1,557 +1,557 b''
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 4 define([
5 5 'base/js/namespace',
6 6 'jquery',
7 7 ], function(IPython, $){
8 8 "use strict";
9 9
10 10 IPython.load_extensions = function () {
11 11 // load one or more IPython notebook extensions with requirejs
12 12
13 13 var extensions = [];
14 14 var extension_names = arguments;
15 15 for (var i = 0; i < extension_names.length; i++) {
16 16 extensions.push("nbextensions/" + arguments[i]);
17 17 }
18 18
19 19 require(extensions,
20 20 function () {
21 21 for (var i = 0; i < arguments.length; i++) {
22 22 var ext = arguments[i];
23 23 var ext_name = extension_names[i];
24 24 // success callback
25 25 console.log("Loaded extension: " + ext_name);
26 26 if (ext && ext.load_ipython_extension !== undefined) {
27 27 ext.load_ipython_extension();
28 28 }
29 29 }
30 30 },
31 31 function (err) {
32 32 // failure callback
33 33 console.log("Failed to load extension(s):", err.requireModules, err);
34 34 }
35 35 );
36 36 };
37 37
38 38 //============================================================================
39 39 // Cross-browser RegEx Split
40 40 //============================================================================
41 41
42 42 // This code has been MODIFIED from the code licensed below to not replace the
43 43 // default browser split. The license is reproduced here.
44 44
45 45 // see http://blog.stevenlevithan.com/archives/cross-browser-split for more info:
46 46 /*!
47 47 * Cross-Browser Split 1.1.1
48 48 * Copyright 2007-2012 Steven Levithan <stevenlevithan.com>
49 49 * Available under the MIT License
50 50 * ECMAScript compliant, uniform cross-browser split method
51 51 */
52 52
53 53 /**
54 54 * Splits a string into an array of strings using a regex or string
55 55 * separator. Matches of the separator are not included in the result array.
56 56 * However, if `separator` is a regex that contains capturing groups,
57 57 * backreferences are spliced into the result each time `separator` is
58 58 * matched. Fixes browser bugs compared to the native
59 59 * `String.prototype.split` and can be used reliably cross-browser.
60 60 * @param {String} str String to split.
61 61 * @param {RegExp|String} separator Regex or string to use for separating
62 62 * the string.
63 63 * @param {Number} [limit] Maximum number of items to include in the result
64 64 * array.
65 65 * @returns {Array} Array of substrings.
66 66 * @example
67 67 *
68 68 * // Basic use
69 69 * regex_split('a b c d', ' ');
70 70 * // -> ['a', 'b', 'c', 'd']
71 71 *
72 72 * // With limit
73 73 * regex_split('a b c d', ' ', 2);
74 74 * // -> ['a', 'b']
75 75 *
76 76 * // Backreferences in result array
77 77 * regex_split('..word1 word2..', /([a-z]+)(\d+)/i);
78 78 * // -> ['..', 'word', '1', ' ', 'word', '2', '..']
79 79 */
80 80 var regex_split = function (str, separator, limit) {
81 81 // If `separator` is not a regex, use `split`
82 82 if (Object.prototype.toString.call(separator) !== "[object RegExp]") {
83 83 return split.call(str, separator, limit);
84 84 }
85 85 var output = [],
86 86 flags = (separator.ignoreCase ? "i" : "") +
87 87 (separator.multiline ? "m" : "") +
88 88 (separator.extended ? "x" : "") + // Proposed for ES6
89 89 (separator.sticky ? "y" : ""), // Firefox 3+
90 90 lastLastIndex = 0,
91 91 // Make `global` and avoid `lastIndex` issues by working with a copy
92 92 separator = new RegExp(separator.source, flags + "g"),
93 93 separator2, match, lastIndex, lastLength;
94 94 str += ""; // Type-convert
95 95
96 96 var compliantExecNpcg = typeof(/()??/.exec("")[1]) === "undefined";
97 97 if (!compliantExecNpcg) {
98 98 // Doesn't need flags gy, but they don't hurt
99 99 separator2 = new RegExp("^" + separator.source + "$(?!\\s)", flags);
100 100 }
101 101 /* Values for `limit`, per the spec:
102 102 * If undefined: 4294967295 // Math.pow(2, 32) - 1
103 103 * If 0, Infinity, or NaN: 0
104 104 * If positive number: limit = Math.floor(limit); if (limit > 4294967295) limit -= 4294967296;
105 105 * If negative number: 4294967296 - Math.floor(Math.abs(limit))
106 106 * If other: Type-convert, then use the above rules
107 107 */
108 108 limit = typeof(limit) === "undefined" ?
109 109 -1 >>> 0 : // Math.pow(2, 32) - 1
110 110 limit >>> 0; // ToUint32(limit)
111 111 while (match = separator.exec(str)) {
112 112 // `separator.lastIndex` is not reliable cross-browser
113 113 lastIndex = match.index + match[0].length;
114 114 if (lastIndex > lastLastIndex) {
115 115 output.push(str.slice(lastLastIndex, match.index));
116 116 // Fix browsers whose `exec` methods don't consistently return `undefined` for
117 117 // nonparticipating capturing groups
118 118 if (!compliantExecNpcg && match.length > 1) {
119 119 match[0].replace(separator2, function () {
120 120 for (var i = 1; i < arguments.length - 2; i++) {
121 121 if (typeof(arguments[i]) === "undefined") {
122 122 match[i] = undefined;
123 123 }
124 124 }
125 125 });
126 126 }
127 127 if (match.length > 1 && match.index < str.length) {
128 128 Array.prototype.push.apply(output, match.slice(1));
129 129 }
130 130 lastLength = match[0].length;
131 131 lastLastIndex = lastIndex;
132 132 if (output.length >= limit) {
133 133 break;
134 134 }
135 135 }
136 136 if (separator.lastIndex === match.index) {
137 137 separator.lastIndex++; // Avoid an infinite loop
138 138 }
139 139 }
140 140 if (lastLastIndex === str.length) {
141 141 if (lastLength || !separator.test("")) {
142 142 output.push("");
143 143 }
144 144 } else {
145 145 output.push(str.slice(lastLastIndex));
146 146 }
147 147 return output.length > limit ? output.slice(0, limit) : output;
148 148 };
149 149
150 150 //============================================================================
151 151 // End contributed Cross-browser RegEx Split
152 152 //============================================================================
153 153
154 154
155 155 var uuid = function () {
156 156 // http://www.ietf.org/rfc/rfc4122.txt
157 157 var s = [];
158 158 var hexDigits = "0123456789ABCDEF";
159 159 for (var i = 0; i < 32; i++) {
160 160 s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
161 161 }
162 162 s[12] = "4"; // bits 12-15 of the time_hi_and_version field to 0010
163 163 s[16] = hexDigits.substr((s[16] & 0x3) | 0x8, 1); // bits 6-7 of the clock_seq_hi_and_reserved to 01
164 164
165 165 var uuid = s.join("");
166 166 return uuid;
167 167 };
168 168
169 169
170 170 //Fix raw text to parse correctly in crazy XML
171 171 function xmlencode(string) {
172 172 return string.replace(/\&/g,'&'+'amp;')
173 173 .replace(/</g,'&'+'lt;')
174 174 .replace(/>/g,'&'+'gt;')
175 175 .replace(/\'/g,'&'+'apos;')
176 176 .replace(/\"/g,'&'+'quot;')
177 177 .replace(/`/g,'&'+'#96;');
178 178 }
179 179
180 180
181 181 //Map from terminal commands to CSS classes
182 182 var ansi_colormap = {
183 183 "01":"ansibold",
184 184
185 185 "30":"ansiblack",
186 186 "31":"ansired",
187 187 "32":"ansigreen",
188 188 "33":"ansiyellow",
189 189 "34":"ansiblue",
190 190 "35":"ansipurple",
191 191 "36":"ansicyan",
192 192 "37":"ansigray",
193 193
194 194 "40":"ansibgblack",
195 195 "41":"ansibgred",
196 196 "42":"ansibggreen",
197 197 "43":"ansibgyellow",
198 198 "44":"ansibgblue",
199 199 "45":"ansibgpurple",
200 200 "46":"ansibgcyan",
201 201 "47":"ansibggray"
202 202 };
203 203
204 204 function _process_numbers(attrs, numbers) {
205 205 // process ansi escapes
206 206 var n = numbers.shift();
207 207 if (ansi_colormap[n]) {
208 208 if ( ! attrs["class"] ) {
209 209 attrs["class"] = ansi_colormap[n];
210 210 } else {
211 211 attrs["class"] += " " + ansi_colormap[n];
212 212 }
213 213 } else if (n == "38" || n == "48") {
214 214 // VT100 256 color or 24 bit RGB
215 215 if (numbers.length < 2) {
216 216 console.log("Not enough fields for VT100 color", numbers);
217 217 return;
218 218 }
219 219
220 220 var index_or_rgb = numbers.shift();
221 221 var r,g,b;
222 222 if (index_or_rgb == "5") {
223 223 // 256 color
224 224 var idx = parseInt(numbers.shift());
225 225 if (idx < 16) {
226 226 // indexed ANSI
227 227 // ignore bright / non-bright distinction
228 228 idx = idx % 8;
229 229 var ansiclass = ansi_colormap[n[0] + (idx % 8).toString()];
230 230 if ( ! attrs["class"] ) {
231 231 attrs["class"] = ansiclass;
232 232 } else {
233 233 attrs["class"] += " " + ansiclass;
234 234 }
235 235 return;
236 236 } else if (idx < 232) {
237 237 // 216 color 6x6x6 RGB
238 238 idx = idx - 16;
239 239 b = idx % 6;
240 240 g = Math.floor(idx / 6) % 6;
241 241 r = Math.floor(idx / 36) % 6;
242 242 // convert to rgb
243 243 r = (r * 51);
244 244 g = (g * 51);
245 245 b = (b * 51);
246 246 } else {
247 247 // grayscale
248 248 idx = idx - 231;
249 249 // it's 1-24 and should *not* include black or white,
250 250 // so a 26 point scale
251 251 r = g = b = Math.floor(idx * 256 / 26);
252 252 }
253 253 } else if (index_or_rgb == "2") {
254 254 // Simple 24 bit RGB
255 255 if (numbers.length > 3) {
256 256 console.log("Not enough fields for RGB", numbers);
257 257 return;
258 258 }
259 259 r = numbers.shift();
260 260 g = numbers.shift();
261 261 b = numbers.shift();
262 262 } else {
263 263 console.log("unrecognized control", numbers);
264 264 return;
265 265 }
266 266 if (r !== undefined) {
267 267 // apply the rgb color
268 268 var line;
269 269 if (n == "38") {
270 270 line = "color: ";
271 271 } else {
272 272 line = "background-color: ";
273 273 }
274 274 line = line + "rgb(" + r + "," + g + "," + b + ");"
275 275 if ( !attrs["style"] ) {
276 276 attrs["style"] = line;
277 277 } else {
278 278 attrs["style"] += " " + line;
279 279 }
280 280 }
281 281 }
282 282 }
283 283
284 284 function ansispan(str) {
285 285 // ansispan function adapted from github.com/mmalecki/ansispan (MIT License)
286 286 // regular ansi escapes (using the table above)
287 287 return str.replace(/\033\[(0?[01]|22|39)?([;\d]+)?m/g, function(match, prefix, pattern) {
288 288 if (!pattern) {
289 289 // [(01|22|39|)m close spans
290 290 return "</span>";
291 291 }
292 292 // consume sequence of color escapes
293 293 var numbers = pattern.match(/\d+/g);
294 294 var attrs = {};
295 295 while (numbers.length > 0) {
296 296 _process_numbers(attrs, numbers);
297 297 }
298 298
299 299 var span = "<span ";
300 300 for (var attr in attrs) {
301 301 var value = attrs[attr];
302 302 span = span + " " + attr + '="' + attrs[attr] + '"';
303 303 }
304 304 return span + ">";
305 305 });
306 306 };
307 307
308 308 // Transform ANSI color escape codes into HTML <span> tags with css
309 309 // classes listed in the above ansi_colormap object. The actual color used
310 310 // are set in the css file.
311 311 function fixConsole(txt) {
312 312 txt = xmlencode(txt);
313 313 var re = /\033\[([\dA-Fa-f;]*?)m/;
314 314 var opened = false;
315 315 var cmds = [];
316 316 var opener = "";
317 317 var closer = "";
318 318
319 319 // Strip all ANSI codes that are not color related. Matches
320 320 // all ANSI codes that do not end with "m".
321 321 var ignored_re = /(?=(\033\[[\d;=]*[a-ln-zA-Z]{1}))\1(?!m)/g;
322 322 txt = txt.replace(ignored_re, "");
323 323
324 324 // color ansi codes
325 325 txt = ansispan(txt);
326 326 return txt;
327 327 }
328 328
329 329 // Remove chunks that should be overridden by the effect of
330 330 // carriage return characters
331 331 function fixCarriageReturn(txt) {
332 332 var tmp = txt;
333 333 do {
334 334 txt = tmp;
335 335 tmp = txt.replace(/\r+\n/gm, '\n'); // \r followed by \n --> newline
336 336 tmp = tmp.replace(/^.*\r+/gm, ''); // Other \r --> clear line
337 337 } while (tmp.length < txt.length);
338 338 return txt;
339 339 }
340 340
341 341 // Locate any URLs and convert them to a anchor tag
342 342 function autoLinkUrls(txt) {
343 343 return txt.replace(/(^|\s)(https?|ftp)(:[^'">\s]+)/gi,
344 344 "$1<a target=\"_blank\" href=\"$2$3\">$2$3</a>");
345 345 }
346 346
347 347 var points_to_pixels = function (points) {
348 348 // A reasonably good way of converting between points and pixels.
349 349 var test = $('<div style="display: none; width: 10000pt; padding:0; border:0;"></div>');
350 350 $(body).append(test);
351 351 var pixel_per_point = test.width()/10000;
352 352 test.remove();
353 353 return Math.floor(points*pixel_per_point);
354 354 };
355 355
356 356 var always_new = function (constructor) {
357 357 // wrapper around contructor to avoid requiring `var a = new constructor()`
358 358 // useful for passing constructors as callbacks,
359 359 // not for programmer laziness.
360 360 // from http://programmers.stackexchange.com/questions/118798
361 361 return function () {
362 362 var obj = Object.create(constructor.prototype);
363 363 constructor.apply(obj, arguments);
364 364 return obj;
365 365 };
366 366 };
367 367
368 368 var url_path_join = function () {
369 369 // join a sequence of url components with '/'
370 370 var url = '';
371 371 for (var i = 0; i < arguments.length; i++) {
372 372 if (arguments[i] === '') {
373 373 continue;
374 374 }
375 375 if (url.length > 0 && url[url.length-1] != '/') {
376 376 url = url + '/' + arguments[i];
377 377 } else {
378 378 url = url + arguments[i];
379 379 }
380 380 }
381 381 url = url.replace(/\/\/+/, '/');
382 382 return url;
383 383 };
384 384
385 385 var parse_url = function (url) {
386 386 // an `a` element with an href allows attr-access to the parsed segments of a URL
387 387 // a = parse_url("http://localhost:8888/path/name#hash")
388 388 // a.protocol = "http:"
389 389 // a.host = "localhost:8888"
390 390 // a.hostname = "localhost"
391 391 // a.port = 8888
392 392 // a.pathname = "/path/name"
393 393 // a.hash = "#hash"
394 394 var a = document.createElement("a");
395 395 a.href = url;
396 396 return a;
397 397 };
398 398
399 399 var encode_uri_components = function (uri) {
400 400 // encode just the components of a multi-segment uri,
401 401 // leaving '/' separators
402 402 return uri.split('/').map(encodeURIComponent).join('/');
403 403 };
404 404
405 405 var url_join_encode = function () {
406 406 // join a sequence of url components with '/',
407 407 // encoding each component with encodeURIComponent
408 408 return encode_uri_components(url_path_join.apply(null, arguments));
409 409 };
410 410
411 411
412 412 var splitext = function (filename) {
413 413 // mimic Python os.path.splitext
414 414 // Returns ['base', '.ext']
415 415 var idx = filename.lastIndexOf('.');
416 416 if (idx > 0) {
417 417 return [filename.slice(0, idx), filename.slice(idx)];
418 418 } else {
419 419 return [filename, ''];
420 420 }
421 421 };
422 422
423 423
424 424 var escape_html = function (text) {
425 425 // escape text to HTML
426 426 return $("<div/>").text(text).html();
427 427 };
428 428
429 429
430 430 var get_body_data = function(key) {
431 431 // get a url-encoded item from body.data and decode it
432 432 // we should never have any encoded URLs anywhere else in code
433 433 // until we are building an actual request
434 434 return decodeURIComponent($('body').data(key));
435 435 };
436 436
437 437 var to_absolute_cursor_pos = function (cm, cursor) {
438 438 // get the absolute cursor position from CodeMirror's col, ch
439 439 if (!cursor) {
440 440 cursor = cm.getCursor();
441 441 }
442 442 var cursor_pos = cursor.ch;
443 443 for (var i = 0; i < cursor.line; i++) {
444 444 cursor_pos += cm.getLine(i).length + 1;
445 445 }
446 446 return cursor_pos;
447 447 };
448 448
449 449 var from_absolute_cursor_pos = function (cm, cursor_pos) {
450 450 // turn absolute cursor postion into CodeMirror col, ch cursor
451 451 var i, line;
452 452 var offset = 0;
453 453 for (i = 0, line=cm.getLine(i); line !== undefined; i++, line=cm.getLine(i)) {
454 454 if (offset + line.length < cursor_pos) {
455 455 offset += line.length + 1;
456 456 } else {
457 457 return {
458 458 line : i,
459 459 ch : cursor_pos - offset,
460 460 };
461 461 }
462 462 }
463 463 // reached end, return endpoint
464 464 return {
465 465 ch : line.length - 1,
466 466 line : i - 1,
467 467 };
468 468 };
469 469
470 470 // http://stackoverflow.com/questions/2400935/browser-detection-in-javascript
471 471 var browser = (function() {
472 472 if (typeof navigator === 'undefined') {
473 473 // navigator undefined in node
474 474 return 'None';
475 475 }
476 476 var N= navigator.appName, ua= navigator.userAgent, tem;
477 477 var M= ua.match(/(opera|chrome|safari|firefox|msie)\/?\s*(\.?\d+(\.\d+)*)/i);
478 478 if (M && (tem= ua.match(/version\/([\.\d]+)/i)) !== null) M[2]= tem[1];
479 479 M= M? [M[1], M[2]]: [N, navigator.appVersion,'-?'];
480 480 return M;
481 481 })();
482 482
483 483 // http://stackoverflow.com/questions/11219582/how-to-detect-my-browser-version-and-operating-system-using-javascript
484 484 var platform = (function () {
485 485 if (typeof navigator === 'undefined') {
486 486 // navigator undefined in node
487 487 return 'None';
488 488 }
489 489 var OSName="None";
490 490 if (navigator.appVersion.indexOf("Win")!=-1) OSName="Windows";
491 491 if (navigator.appVersion.indexOf("Mac")!=-1) OSName="MacOS";
492 492 if (navigator.appVersion.indexOf("X11")!=-1) OSName="UNIX";
493 493 if (navigator.appVersion.indexOf("Linux")!=-1) OSName="Linux";
494 494 return OSName;
495 495 })();
496 496
497 497 var is_or_has = function (a, b) {
498 498 // Is b a child of a or a itself?
499 499 return a.has(b).length !==0 || a.is(b);
500 500 };
501 501
502 502 var is_focused = function (e) {
503 503 // Is element e, or one of its children focused?
504 504 e = $(e);
505 505 var target = $(document.activeElement);
506 506 if (target.length > 0) {
507 507 if (is_or_has(e, target)) {
508 508 return true;
509 509 } else {
510 510 return false;
511 511 }
512 512 } else {
513 513 return false;
514 514 }
515 515 };
516 516
517 517 var log_ajax_error = function (jqXHR, status, error) {
518 518 // log ajax failures with informative messages
519 519 var msg = "API request failed (" + jqXHR.status + "): ";
520 520 console.log(jqXHR);
521 521 if (jqXHR.responseJSON && jqXHR.responseJSON.message) {
522 522 msg += jqXHR.responseJSON.message;
523 523 } else {
524 524 msg += jqXHR.statusText;
525 525 }
526 526 console.log(msg);
527 527 };
528 528
529 529 var utils = {
530 530 regex_split : regex_split,
531 531 uuid : uuid,
532 532 fixConsole : fixConsole,
533 533 fixCarriageReturn : fixCarriageReturn,
534 534 autoLinkUrls : autoLinkUrls,
535 535 points_to_pixels : points_to_pixels,
536 536 get_body_data : get_body_data,
537 537 parse_url : parse_url,
538 538 url_path_join : url_path_join,
539 539 url_join_encode : url_join_encode,
540 540 encode_uri_components : encode_uri_components,
541 541 splitext : splitext,
542 542 escape_html : escape_html,
543 543 always_new : always_new,
544 544 to_absolute_cursor_pos : to_absolute_cursor_pos,
545 545 from_absolute_cursor_pos : from_absolute_cursor_pos,
546 546 browser : browser,
547 547 platform: platform,
548 548 is_or_has : is_or_has,
549 549 is_focused : is_focused,
550 550 log_ajax_error : log_ajax_error,
551 551 };
552 552
553 553 // Backwards compatability.
554 IPython.Utils = utils;
554 IPython.utils = utils;
555 555
556 556 return utils;
557 557 });
@@ -1,132 +1,125 b''
1 1 /*
2 2 * Date Format 1.2.3
3 3 * (c) 2007-2009 Steven Levithan <stevenlevithan.com>
4 4 * MIT license
5 5 *
6 6 * Includes enhancements by Scott Trenda <scott.trenda.net>
7 7 * and Kris Kowal <cixar.com/~kris.kowal/>
8 8 *
9 9 * Accepts a date, a mask, or a date and a mask.
10 10 * Returns a formatted version of the given date.
11 11 * The date defaults to the current date/time.
12 12 * The mask defaults to dateFormat.masks.default.
13 13 */
14 // Copyright (c) IPython Development Team.
15 // Distributed under the terms of the Modified BSD License.
16 14
17 // Require.js define call added by IPython team.
18 define([], function() {
19 var dateFormat = function () {
20 var token = /d{1,4}|m{1,4}|yy(?:yy)?|([HhMsTt])\1?|[LloSZ]|"[^"]*"|'[^']*'/g,
21 timezone = /\b(?:[PMCEA][SDP]T|(?:Pacific|Mountain|Central|Eastern|Atlantic) (?:Standard|Daylight|Prevailing) Time|(?:GMT|UTC)(?:[-+]\d{4})?)\b/g,
22 timezoneClip = /[^-+\dA-Z]/g,
23 pad = function (val, len) {
24 val = String(val);
25 len = len || 2;
26 while (val.length < len) val = "0" + val;
27 return val;
28 };
29
30 // Regexes and supporting functions are cached through closure
31 return function (date, mask, utc) {
32 var dF = dateFormat;
15 var dateFormat = function () {
16 var token = /d{1,4}|m{1,4}|yy(?:yy)?|([HhMsTt])\1?|[LloSZ]|"[^"]*"|'[^']*'/g,
17 timezone = /\b(?:[PMCEA][SDP]T|(?:Pacific|Mountain|Central|Eastern|Atlantic) (?:Standard|Daylight|Prevailing) Time|(?:GMT|UTC)(?:[-+]\d{4})?)\b/g,
18 timezoneClip = /[^-+\dA-Z]/g,
19 pad = function (val, len) {
20 val = String(val);
21 len = len || 2;
22 while (val.length < len) val = "0" + val;
23 return val;
24 };
33 25
34 // You can't provide utc if you skip other args (use the "UTC:" mask prefix)
35 if (arguments.length == 1 && Object.prototype.toString.call(date) == "[object String]" && !/\d/.test(date)) {
36 mask = date;
37 date = undefined;
38 }
26 // Regexes and supporting functions are cached through closure
27 return function (date, mask, utc) {
28 var dF = dateFormat;
39 29
40 // Passing date through Date applies Date.parse, if necessary
41 date = date ? new Date(date) : new Date;
42 if (isNaN(date)) throw SyntaxError("invalid date");
30 // You can't provide utc if you skip other args (use the "UTC:" mask prefix)
31 if (arguments.length == 1 && Object.prototype.toString.call(date) == "[object String]" && !/\d/.test(date)) {
32 mask = date;
33 date = undefined;
34 }
43 35
44 mask = String(dF.masks[mask] || mask || dF.masks["default"]);
36 // Passing date through Date applies Date.parse, if necessary
37 date = date ? new Date(date) : new Date;
38 if (isNaN(date)) throw SyntaxError("invalid date");
45 39
46 // Allow setting the utc argument via the mask
47 if (mask.slice(0, 4) == "UTC:") {
48 mask = mask.slice(4);
49 utc = true;
50 }
40 mask = String(dF.masks[mask] || mask || dF.masks["default"]);
51 41
52 var _ = utc ? "getUTC" : "get",
53 d = date[_ + "Date"](),
54 D = date[_ + "Day"](),
55 m = date[_ + "Month"](),
56 y = date[_ + "FullYear"](),
57 H = date[_ + "Hours"](),
58 M = date[_ + "Minutes"](),
59 s = date[_ + "Seconds"](),
60 L = date[_ + "Milliseconds"](),
61 o = utc ? 0 : date.getTimezoneOffset(),
62 flags = {
63 d: d,
64 dd: pad(d),
65 ddd: dF.i18n.dayNames[D],
66 dddd: dF.i18n.dayNames[D + 7],
67 m: m + 1,
68 mm: pad(m + 1),
69 mmm: dF.i18n.monthNames[m],
70 mmmm: dF.i18n.monthNames[m + 12],
71 yy: String(y).slice(2),
72 yyyy: y,
73 h: H % 12 || 12,
74 hh: pad(H % 12 || 12),
75 H: H,
76 HH: pad(H),
77 M: M,
78 MM: pad(M),
79 s: s,
80 ss: pad(s),
81 l: pad(L, 3),
82 L: pad(L > 99 ? Math.round(L / 10) : L),
83 t: H < 12 ? "a" : "p",
84 tt: H < 12 ? "am" : "pm",
85 T: H < 12 ? "A" : "P",
86 TT: H < 12 ? "AM" : "PM",
87 Z: utc ? "UTC" : (String(date).match(timezone) || [""]).pop().replace(timezoneClip, ""),
88 o: (o > 0 ? "-" : "+") + pad(Math.floor(Math.abs(o) / 60) * 100 + Math.abs(o) % 60, 4),
89 S: ["th", "st", "nd", "rd"][d % 10 > 3 ? 0 : (d % 100 - d % 10 != 10) * d % 10]
90 };
42 // Allow setting the utc argument via the mask
43 if (mask.slice(0, 4) == "UTC:") {
44 mask = mask.slice(4);
45 utc = true;
46 }
91 47
92 return mask.replace(token, function ($0) {
93 return $0 in flags ? flags[$0] : $0.slice(1, $0.length - 1);
94 });
95 };
96 }();
48 var _ = utc ? "getUTC" : "get",
49 d = date[_ + "Date"](),
50 D = date[_ + "Day"](),
51 m = date[_ + "Month"](),
52 y = date[_ + "FullYear"](),
53 H = date[_ + "Hours"](),
54 M = date[_ + "Minutes"](),
55 s = date[_ + "Seconds"](),
56 L = date[_ + "Milliseconds"](),
57 o = utc ? 0 : date.getTimezoneOffset(),
58 flags = {
59 d: d,
60 dd: pad(d),
61 ddd: dF.i18n.dayNames[D],
62 dddd: dF.i18n.dayNames[D + 7],
63 m: m + 1,
64 mm: pad(m + 1),
65 mmm: dF.i18n.monthNames[m],
66 mmmm: dF.i18n.monthNames[m + 12],
67 yy: String(y).slice(2),
68 yyyy: y,
69 h: H % 12 || 12,
70 hh: pad(H % 12 || 12),
71 H: H,
72 HH: pad(H),
73 M: M,
74 MM: pad(M),
75 s: s,
76 ss: pad(s),
77 l: pad(L, 3),
78 L: pad(L > 99 ? Math.round(L / 10) : L),
79 t: H < 12 ? "a" : "p",
80 tt: H < 12 ? "am" : "pm",
81 T: H < 12 ? "A" : "P",
82 TT: H < 12 ? "AM" : "PM",
83 Z: utc ? "UTC" : (String(date).match(timezone) || [""]).pop().replace(timezoneClip, ""),
84 o: (o > 0 ? "-" : "+") + pad(Math.floor(Math.abs(o) / 60) * 100 + Math.abs(o) % 60, 4),
85 S: ["th", "st", "nd", "rd"][d % 10 > 3 ? 0 : (d % 100 - d % 10 != 10) * d % 10]
86 };
97 87
98 // Some common format strings
99 dateFormat.masks = {
100 "default": "ddd mmm dd yyyy HH:MM:ss",
101 shortDate: "m/d/yy",
102 mediumDate: "mmm d, yyyy",
103 longDate: "mmmm d, yyyy",
104 fullDate: "dddd, mmmm d, yyyy",
105 shortTime: "h:MM TT",
106 mediumTime: "h:MM:ss TT",
107 longTime: "h:MM:ss TT Z",
108 isoDate: "yyyy-mm-dd",
109 isoTime: "HH:MM:ss",
110 isoDateTime: "yyyy-mm-dd'T'HH:MM:ss",
111 isoUtcDateTime: "UTC:yyyy-mm-dd'T'HH:MM:ss'Z'"
88 return mask.replace(token, function ($0) {
89 return $0 in flags ? flags[$0] : $0.slice(1, $0.length - 1);
90 });
112 91 };
92 }();
113 93
114 // Internationalization strings
115 dateFormat.i18n = {
116 dayNames: [
117 "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat",
118 "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"
119 ],
120 monthNames: [
121 "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
122 "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"
123 ]
124 };
94 // Some common format strings
95 dateFormat.masks = {
96 "default": "ddd mmm dd yyyy HH:MM:ss",
97 shortDate: "m/d/yy",
98 mediumDate: "mmm d, yyyy",
99 longDate: "mmmm d, yyyy",
100 fullDate: "dddd, mmmm d, yyyy",
101 shortTime: "h:MM TT",
102 mediumTime: "h:MM:ss TT",
103 longTime: "h:MM:ss TT Z",
104 isoDate: "yyyy-mm-dd",
105 isoTime: "HH:MM:ss",
106 isoDateTime: "yyyy-mm-dd'T'HH:MM:ss",
107 isoUtcDateTime: "UTC:yyyy-mm-dd'T'HH:MM:ss'Z'"
108 };
125 109
126 // For convenience...
127 Date.prototype.format = function (mask, utc) {
128 return dateFormat(this, mask, utc);
129 };
110 // Internationalization strings
111 dateFormat.i18n = {
112 dayNames: [
113 "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat",
114 "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"
115 ],
116 monthNames: [
117 "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
118 "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"
119 ]
120 };
130 121
131 return {'dateFormat': dateFormat};
132 }); No newline at end of file
122 // For convenience...
123 Date.prototype.format = function (mask, utc) {
124 return dateFormat(this, mask, utc);
125 }; No newline at end of file
@@ -1,415 +1,418 b''
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 4 define([
5 5 'base/js/namespace',
6 6 'jquery',
7 7 'notebook/js/textcell',
8 8 ], function(IPython, $, textcell) {
9 9 "use strict";
10 10
11 /**
12 * @constructor
13 * @class CellToolbar
14 * @param {The cell to attach the metadata UI to} cell
15 */
16 var CellToolbar = function (cell, events, notebook) {
11 var CellToolbar = function (options) {
12 // Constructor
13 //
14 // Parameters:
15 // options: dictionary
16 // Dictionary of keyword arguments.
17 // events: $(Events) instance
18 // cell: Cell instance
19 // notebook: Notebook instance
17 20 CellToolbar._instances.push(this);
18 this.notebook = notebook;
19 this.events = events;
20 this.cell = cell;
21 this.notebook = options.notebook;
22 this.events = options.events;
23 this.cell = options.cell;
21 24 this.create_element();
22 25 this.rebuild();
23 26 return this;
24 27 };
25 28
26 29
27 30 CellToolbar.prototype.create_element = function () {
28 31 this.inner_element = $('<div/>').addClass('celltoolbar');
29 32 this.element = $('<div/>').addClass('ctb_hideshow')
30 33 .append(this.inner_element);
31 34 };
32 35
33 36
34 37 // The default css style for the outer celltoolbar div
35 38 // (ctb_hideshow) is display: none.
36 39 // To show the cell toolbar, *both* of the following conditions must be met:
37 40 // - A parent container has class `ctb_global_show`
38 41 // - The celltoolbar has the class `ctb_show`
39 42 // This allows global show/hide, as well as per-cell show/hide.
40 43
41 44 CellToolbar.global_hide = function () {
42 45 $('body').removeClass('ctb_global_show');
43 46 };
44 47
45 48
46 49 CellToolbar.global_show = function () {
47 50 $('body').addClass('ctb_global_show');
48 51 };
49 52
50 53
51 54 CellToolbar.prototype.hide = function () {
52 55 this.element.removeClass('ctb_show');
53 56 };
54 57
55 58
56 59 CellToolbar.prototype.show = function () {
57 60 this.element.addClass('ctb_show');
58 61 };
59 62
60 63
61 64 /**
62 65 * Class variable that should contain a dict of all available callback
63 66 * we need to think of wether or not we allow nested namespace
64 67 * @property _callback_dict
65 68 * @private
66 69 * @static
67 70 * @type Dict
68 71 */
69 72 CellToolbar._callback_dict = {};
70 73
71 74
72 75 /**
73 76 * Class variable that should contain the reverse order list of the button
74 77 * to add to the toolbar of each cell
75 78 * @property _ui_controls_list
76 79 * @private
77 80 * @static
78 81 * @type List
79 82 */
80 83 CellToolbar._ui_controls_list = [];
81 84
82 85
83 86 /**
84 87 * Class variable that should contain the CellToolbar instances for each
85 88 * cell of the notebook
86 89 *
87 90 * @private
88 91 * @property _instances
89 92 * @static
90 93 * @type List
91 94 */
92 95 CellToolbar._instances = [];
93 96
94 97
95 98 /**
96 99 * keep a list of all the available presets for the toolbar
97 100 * @private
98 101 * @property _presets
99 102 * @static
100 103 * @type Dict
101 104 */
102 105 CellToolbar._presets = {};
103 106
104 107
105 108 // this is by design not a prototype.
106 109 /**
107 110 * Register a callback to create an UI element in a cell toolbar.
108 111 * @method register_callback
109 112 * @param name {String} name to use to refer to the callback. It is advised to use a prefix with the name
110 113 * for easier sorting and avoid collision
111 114 * @param callback {function(div, cell)} callback that will be called to generate the ui element
112 115 * @param [cell_types] {List of String|undefined} optional list of cell types. If present the UI element
113 116 * will be added only to cells of types in the list.
114 117 *
115 118 *
116 119 * The callback will receive the following element :
117 120 *
118 121 * * a div in which to add element.
119 122 * * the cell it is responsible from
120 123 *
121 124 * @example
122 125 *
123 126 * Example that create callback for a button that toggle between `true` and `false` label,
124 127 * with the metadata under the key 'foo' to reflect the status of the button.
125 128 *
126 129 * // first param reference to a DOM div
127 130 * // second param reference to the cell.
128 131 * var toggle = function(div, cell) {
129 132 * var button_container = $(div)
130 133 *
131 134 * // let's create a button that show the current value of the metadata
132 135 * var button = $('<div/>').button({label:String(cell.metadata.foo)});
133 136 *
134 137 * // On click, change the metadata value and update the button label
135 138 * button.click(function(){
136 139 * var v = cell.metadata.foo;
137 140 * cell.metadata.foo = !v;
138 141 * button.button("option", "label", String(!v));
139 142 * })
140 143 *
141 144 * // add the button to the DOM div.
142 145 * button_container.append(button);
143 146 * }
144 147 *
145 148 * // now we register the callback under the name `foo` to give the
146 149 * // user the ability to use it later
147 150 * CellToolbar.register_callback('foo', toggle);
148 151 */
149 152 CellToolbar.register_callback = function(name, callback, cell_types) {
150 153 // Overwrite if it already exists.
151 154 CellToolbar._callback_dict[name] = cell_types ? {callback: callback, cell_types: cell_types} : callback;
152 155 };
153 156
154 157
155 158 /**
156 159 * Register a preset of UI element in a cell toolbar.
157 160 * Not supported Yet.
158 161 * @method register_preset
159 162 * @param name {String} name to use to refer to the preset. It is advised to use a prefix with the name
160 163 * for easier sorting and avoid collision
161 164 * @param preset_list {List of String} reverse order of the button in the toolbar. Each String of the list
162 165 * should correspond to a name of a registerd callback.
163 166 *
164 167 * @private
165 168 * @example
166 169 *
167 170 * CellToolbar.register_callback('foo.c1', function(div, cell){...});
168 171 * CellToolbar.register_callback('foo.c2', function(div, cell){...});
169 172 * CellToolbar.register_callback('foo.c3', function(div, cell){...});
170 173 * CellToolbar.register_callback('foo.c4', function(div, cell){...});
171 174 * CellToolbar.register_callback('foo.c5', function(div, cell){...});
172 175 *
173 176 * CellToolbar.register_preset('foo.foo_preset1', ['foo.c1', 'foo.c2', 'foo.c5'])
174 177 * CellToolbar.register_preset('foo.foo_preset2', ['foo.c4', 'foo.c5'])
175 178 */
176 179 CellToolbar.register_preset = function(name, preset_list) {
177 180 CellToolbar._presets[name] = preset_list;
178 181 this.events.trigger('preset_added.CellToolbar', {name: name});
179 182 // When "register_callback" is called by a custom extension, it may be executed after notebook is loaded.
180 183 // In that case, activate the preset if needed.
181 184 if (this.notebook && this.notebook.metadata && this.notebook.metadata.celltoolbar === name)
182 185 this.activate_preset(name);
183 186 };
184 187
185 188
186 189 /**
187 190 * List the names of the presets that are currently registered.
188 191 *
189 192 * @method list_presets
190 193 * @static
191 194 */
192 195 CellToolbar.list_presets = function() {
193 196 var keys = [];
194 197 for (var k in CellToolbar._presets) {
195 198 keys.push(k);
196 199 }
197 200 return keys;
198 201 };
199 202
200 203
201 204 /**
202 205 * Activate an UI preset from `register_preset`
203 206 *
204 207 * This does not update the selection UI.
205 208 *
206 209 * @method activate_preset
207 210 * @param preset_name {String} string corresponding to the preset name
208 211 *
209 212 * @static
210 213 * @private
211 214 * @example
212 215 *
213 216 * CellToolbar.activate_preset('foo.foo_preset1');
214 217 */
215 218 CellToolbar.activate_preset = function(preset_name){
216 219 var preset = CellToolbar._presets[preset_name];
217 220
218 221 if(preset !== undefined){
219 222 CellToolbar._ui_controls_list = preset;
220 223 CellToolbar.rebuild_all();
221 224 }
222 225
223 226 if (this.events) {
224 227 this.events.trigger('preset_activated.CellToolbar', {name: preset_name});
225 228 }
226 229 };
227 230
228 231
229 232 /**
230 233 * This should be called on the class and not on a instance as it will trigger
231 234 * rebuild of all the instances.
232 235 * @method rebuild_all
233 236 * @static
234 237 *
235 238 */
236 239 CellToolbar.rebuild_all = function(){
237 240 for(var i=0; i < CellToolbar._instances.length; i++){
238 241 CellToolbar._instances[i].rebuild();
239 242 }
240 243 };
241 244
242 245 /**
243 246 * Rebuild all the button on the toolbar to update its state.
244 247 * @method rebuild
245 248 */
246 249 CellToolbar.prototype.rebuild = function(){
247 250 // strip evrything from the div
248 251 // which is probably inner_element
249 252 // or this.element.
250 253 this.inner_element.empty();
251 254 this.ui_controls_list = [];
252 255
253 256 var callbacks = CellToolbar._callback_dict;
254 257 var preset = CellToolbar._ui_controls_list;
255 258 // Yes we iterate on the class variable, not the instance one.
256 259 for (var i=0; i < preset.length; i++) {
257 260 var key = preset[i];
258 261 var callback = callbacks[key];
259 262 if (!callback) continue;
260 263
261 264 if (typeof callback === 'object') {
262 265 if (callback.cell_types.indexOf(this.cell.cell_type) === -1) continue;
263 266 callback = callback.callback;
264 267 }
265 268
266 269 var local_div = $('<div/>').addClass('button_container');
267 270 try {
268 271 callback(local_div, this.cell, this);
269 272 this.ui_controls_list.push(key);
270 273 } catch (e) {
271 274 console.log("Error in cell toolbar callback " + key, e);
272 275 continue;
273 276 }
274 277 // only append if callback succeeded.
275 278 this.inner_element.append(local_div);
276 279 }
277 280
278 281 // If there are no controls or the cell is a rendered TextCell hide the toolbar.
279 282 if (!this.ui_controls_list.length || (this.cell instanceof textcell.TextCell && this.cell.rendered)) {
280 283 this.hide();
281 284 } else {
282 285 this.show();
283 286 }
284 287 };
285 288
286 289
287 290 /**
288 291 */
289 292 CellToolbar.utils = {};
290 293
291 294
292 295 /**
293 296 * A utility function to generate bindings between a checkbox and cell/metadata
294 297 * @method utils.checkbox_ui_generator
295 298 * @static
296 299 *
297 300 * @param name {string} Label in front of the checkbox
298 301 * @param setter {function( cell, newValue )}
299 302 * A setter method to set the newValue
300 303 * @param getter {function( cell )}
301 304 * A getter methods which return the current value.
302 305 *
303 306 * @return callback {function( div, cell )} Callback to be passed to `register_callback`
304 307 *
305 308 * @example
306 309 *
307 310 * An exmple that bind the subkey `slideshow.isSectionStart` to a checkbox with a `New Slide` label
308 311 *
309 312 * var newSlide = CellToolbar.utils.checkbox_ui_generator('New Slide',
310 313 * // setter
311 314 * function(cell, value){
312 315 * // we check that the slideshow namespace exist and create it if needed
313 316 * if (cell.metadata.slideshow == undefined){cell.metadata.slideshow = {}}
314 317 * // set the value
315 318 * cell.metadata.slideshow.isSectionStart = value
316 319 * },
317 320 * //geter
318 321 * function(cell){ var ns = cell.metadata.slideshow;
319 322 * // if the slideshow namespace does not exist return `undefined`
320 323 * // (will be interpreted as `false` by checkbox) otherwise
321 324 * // return the value
322 325 * return (ns == undefined)? undefined: ns.isSectionStart
323 326 * }
324 327 * );
325 328 *
326 329 * CellToolbar.register_callback('newSlide', newSlide);
327 330 *
328 331 */
329 332 CellToolbar.utils.checkbox_ui_generator = function(name, setter, getter){
330 333 return function(div, cell, celltoolbar) {
331 334 var button_container = $(div);
332 335
333 336 var chkb = $('<input/>').attr('type', 'checkbox');
334 337 var lbl = $('<label/>').append($('<span/>').text(name));
335 338 lbl.append(chkb);
336 339 chkb.attr("checked", getter(cell));
337 340
338 341 chkb.click(function(){
339 342 var v = getter(cell);
340 343 setter(cell, !v);
341 344 chkb.attr("checked", !v);
342 345 });
343 346 button_container.append($('<div/>').append(lbl));
344 347 };
345 348 };
346 349
347 350
348 351 /**
349 352 * A utility function to generate bindings between a dropdown list cell
350 353 * @method utils.select_ui_generator
351 354 * @static
352 355 *
353 356 * @param list_list {list of sublist} List of sublist of metadata value and name in the dropdown list.
354 357 * subslit shoud contain 2 element each, first a string that woul be displayed in the dropdown list,
355 358 * and second the corresponding value to be passed to setter/return by getter. the corresponding value
356 359 * should not be "undefined" or behavior can be unexpected.
357 360 * @param setter {function( cell, newValue )}
358 361 * A setter method to set the newValue
359 362 * @param getter {function( cell )}
360 363 * A getter methods which return the current value of the metadata.
361 364 * @param [label=""] {String} optionnal label for the dropdown menu
362 365 *
363 366 * @return callback {function( div, cell )} Callback to be passed to `register_callback`
364 367 *
365 368 * @example
366 369 *
367 370 * var select_type = CellToolbar.utils.select_ui_generator([
368 371 * ["<None>" , "None" ],
369 372 * ["Header Slide" , "header_slide" ],
370 373 * ["Slide" , "slide" ],
371 374 * ["Fragment" , "fragment" ],
372 375 * ["Skip" , "skip" ],
373 376 * ],
374 377 * // setter
375 378 * function(cell, value){
376 379 * // we check that the slideshow namespace exist and create it if needed
377 380 * if (cell.metadata.slideshow == undefined){cell.metadata.slideshow = {}}
378 381 * // set the value
379 382 * cell.metadata.slideshow.slide_type = value
380 383 * },
381 384 * //geter
382 385 * function(cell){ var ns = cell.metadata.slideshow;
383 386 * // if the slideshow namespace does not exist return `undefined`
384 387 * // (will be interpreted as `false` by checkbox) otherwise
385 388 * // return the value
386 389 * return (ns == undefined)? undefined: ns.slide_type
387 390 * }
388 391 * CellToolbar.register_callback('slideshow.select', select_type);
389 392 *
390 393 */
391 394 CellToolbar.utils.select_ui_generator = function(list_list, setter, getter, label) {
392 395 label = label || "";
393 396 return function(div, cell, celltoolbar) {
394 397 var button_container = $(div);
395 398 var lbl = $("<label/>").append($('<span/>').text(label));
396 399 var select = $('<select/>').addClass('ui-widget ui-widget-content');
397 400 for(var i=0; i < list_list.length; i++){
398 401 var opt = $('<option/>')
399 402 .attr('value', list_list[i][1])
400 403 .text(list_list[i][0]);
401 404 select.append(opt);
402 405 }
403 406 select.val(getter(cell));
404 407 select.change(function(){
405 408 setter(cell, select.val());
406 409 });
407 410 button_container.append($('<div/>').append(lbl).append(select));
408 411 };
409 412 };
410 413
411 414 // Backwards compatability.
412 415 IPython.CellToolbar = CellToolbar;
413 416
414 417 return {'CellToolbar': CellToolbar};
415 418 });
@@ -1,519 +1,526 b''
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 4 define([
5 5 'base/js/namespace',
6 6 'jquery',
7 7 'base/js/utils',
8 8 'notebook/js/tooltip',
9 9 'base/js/keyboard',
10 10 'notebook/js/cell',
11 11 'notebook/js/outputarea',
12 12 'notebook/js/completer',
13 13 'notebook/js/celltoolbar',
14 14 ], function(IPython, $, utils, tooltip, keyboard, cell, outputarea, completer, celltoolbar) {
15 15 "use strict";
16 16 var Cell = cell.Cell;
17 17
18 18 /* local util for codemirror */
19 19 var posEq = function(a, b) {return a.line == b.line && a.ch == b.ch;};
20 20
21 21 /**
22 22 *
23 23 * function to delete until previous non blanking space character
24 24 * or first multiple of 4 tabstop.
25 25 * @private
26 26 */
27 27 CodeMirror.commands.delSpaceToPrevTabStop = function(cm){
28 28 var from = cm.getCursor(true), to = cm.getCursor(false), sel = !posEq(from, to);
29 29 if (!posEq(from, to)) { cm.replaceRange("", from, to); return; }
30 30 var cur = cm.getCursor(), line = cm.getLine(cur.line);
31 31 var tabsize = cm.getOption('tabSize');
32 32 var chToPrevTabStop = cur.ch-(Math.ceil(cur.ch/tabsize)-1)*tabsize;
33 33 from = {ch:cur.ch-chToPrevTabStop,line:cur.line};
34 34 var select = cm.getRange(from,cur);
35 35 if( select.match(/^\ +$/) !== null){
36 36 cm.replaceRange("",from,cur);
37 37 } else {
38 38 cm.deleteH(-1,"char");
39 39 }
40 40 };
41 41
42 42 var keycodes = keyboard.keycodes;
43 43
44 44 var CodeCell = function (kernel, options) {
45 45 // Constructor
46 46 //
47 47 // A Cell conceived to write code.
48 48 //
49 49 // Parameters:
50 50 // kernel: Kernel instance
51 51 // The kernel doesn't have to be set at creation time, in that case
52 52 // it will be null and set_kernel has to be called later.
53 53 // options: dictionary
54 54 // Dictionary of keyword arguments.
55 55 // events: $(Events) instance
56 56 // config: dictionary
57 57 // keyboard_manager: KeyboardManager instance
58 58 // notebook: Notebook instance
59 59 this.kernel = kernel || null;
60 60 this.notebook = options.notebook;
61 61 this.collapsed = false;
62 62 this.events = options.events;
63 63 this.tooltip = new tooltip.Tooltip(this.events);
64 64 this.config = options.config;
65 65
66 66 // create all attributed in constructor function
67 67 // even if null for V8 VM optimisation
68 68 this.input_prompt_number = null;
69 69 this.celltoolbar = null;
70 70 this.output_area = null;
71 71 this.last_msg_id = null;
72 72 this.completer = null;
73 73
74 74
75 75 var cm_overwrite_options = {
76 76 onKeyEvent: $.proxy(this.handle_keyevent,this)
77 77 };
78 78
79 79 var config = this.mergeopt(CodeCell, this.config, {cm_config: cm_overwrite_options});
80 80 Cell.apply(this,[{
81 81 config: config,
82 82 keyboard_manager: options.keyboard_manager,
83 83 events: this.events}]);
84 84
85 85 // Attributes we want to override in this subclass.
86 86 this.cell_type = "code";
87 87
88 88 var that = this;
89 89 this.element.focusout(
90 90 function() { that.auto_highlight(); }
91 91 );
92 92 };
93 93
94 94 CodeCell.options_default = {
95 95 cm_config : {
96 96 extraKeys: {
97 97 "Tab" : "indentMore",
98 98 "Shift-Tab" : "indentLess",
99 99 "Backspace" : "delSpaceToPrevTabStop",
100 100 "Cmd-/" : "toggleComment",
101 101 "Ctrl-/" : "toggleComment"
102 102 },
103 103 mode: 'ipython',
104 104 theme: 'ipython',
105 105 matchBrackets: true,
106 106 // don't auto-close strings because of CodeMirror #2385
107 107 autoCloseBrackets: "()[]{}"
108 108 }
109 109 };
110 110
111 111 CodeCell.msg_cells = {};
112 112
113 113 CodeCell.prototype = new Cell();
114 114
115 115 /**
116 116 * @method auto_highlight
117 117 */
118 118 CodeCell.prototype.auto_highlight = function () {
119 119 this._auto_highlight(this.config.cell_magic_highlight);
120 120 };
121 121
122 122 /** @method create_element */
123 123 CodeCell.prototype.create_element = function () {
124 124 Cell.prototype.create_element.apply(this, arguments);
125 125
126 126 var cell = $('<div></div>').addClass('cell border-box-sizing code_cell');
127 127 cell.attr('tabindex','2');
128 128
129 129 var input = $('<div></div>').addClass('input');
130 130 var prompt = $('<div/>').addClass('prompt input_prompt');
131 131 var inner_cell = $('<div/>').addClass('inner_cell');
132 this.celltoolbar = new celltoolbar.CellToolbar(this, this.events, this.notebook);
132 this.celltoolbar = new celltoolbar.CellToolbar({
133 cell: this,
134 events: this.events,
135 notebook: this.notebook});
133 136 inner_cell.append(this.celltoolbar.element);
134 137 var input_area = $('<div/>').addClass('input_area');
135 138 this.code_mirror = CodeMirror(input_area.get(0), this.cm_config);
136 139 $(this.code_mirror.getInputField()).attr("spellcheck", "false");
137 140 inner_cell.append(input_area);
138 141 input.append(prompt).append(inner_cell);
139 142
140 143 var widget_area = $('<div/>')
141 144 .addClass('widget-area')
142 145 .hide();
143 146 this.widget_area = widget_area;
144 147 var widget_prompt = $('<div/>')
145 148 .addClass('prompt')
146 149 .appendTo(widget_area);
147 150 var widget_subarea = $('<div/>')
148 151 .addClass('widget-subarea')
149 152 .appendTo(widget_area);
150 153 this.widget_subarea = widget_subarea;
151 154 var widget_clear_buton = $('<button />')
152 155 .addClass('close')
153 156 .html('&times;')
154 157 .click(function() {
155 158 widget_area.slideUp('', function(){ widget_subarea.html(''); });
156 159 })
157 160 .appendTo(widget_prompt);
158 161
159 162 var output = $('<div></div>');
160 163 cell.append(input).append(widget_area).append(output);
161 164 this.element = cell;
162 this.output_area = new outputarea.OutputArea(output, true, this.events, this.keyboard_manager);
165 this.output_area = new outputarea.OutputArea({
166 selector: output,
167 prompt_area: true,
168 events: this.events,
169 keyboard_manager: this.keyboard_manager});
163 170 this.completer = new completer.Completer(this, this.events);
164 171 };
165 172
166 173 /** @method bind_events */
167 174 CodeCell.prototype.bind_events = function () {
168 175 Cell.prototype.bind_events.apply(this);
169 176 var that = this;
170 177
171 178 this.element.focusout(
172 179 function() { that.auto_highlight(); }
173 180 );
174 181 };
175 182
176 183
177 184 /**
178 185 * This method gets called in CodeMirror's onKeyDown/onKeyPress
179 186 * handlers and is used to provide custom key handling. Its return
180 187 * value is used to determine if CodeMirror should ignore the event:
181 188 * true = ignore, false = don't ignore.
182 189 * @method handle_codemirror_keyevent
183 190 */
184 191 CodeCell.prototype.handle_codemirror_keyevent = function (editor, event) {
185 192
186 193 var that = this;
187 194 // whatever key is pressed, first, cancel the tooltip request before
188 195 // they are sent, and remove tooltip if any, except for tab again
189 196 var tooltip_closed = null;
190 197 if (event.type === 'keydown' && event.which != keycodes.tab ) {
191 198 tooltip_closed = this.tooltip.remove_and_cancel_tooltip();
192 199 }
193 200
194 201 var cur = editor.getCursor();
195 202 if (event.keyCode === keycodes.enter){
196 203 this.auto_highlight();
197 204 }
198 205
199 206 if (event.which === keycodes.down && event.type === 'keypress' && this.tooltip.time_before_tooltip >= 0) {
200 207 // triger on keypress (!) otherwise inconsistent event.which depending on plateform
201 208 // browser and keyboard layout !
202 209 // Pressing '(' , request tooltip, don't forget to reappend it
203 210 // The second argument says to hide the tooltip if the docstring
204 211 // is actually empty
205 212 this.tooltip.pending(that, true);
206 213 } else if ( tooltip_closed && event.which === keycodes.esc && event.type === 'keydown') {
207 214 // If tooltip is active, cancel it. The call to
208 215 // remove_and_cancel_tooltip above doesn't pass, force=true.
209 216 // Because of this it won't actually close the tooltip
210 217 // if it is in sticky mode. Thus, we have to check again if it is open
211 218 // and close it with force=true.
212 219 if (!this.tooltip._hidden) {
213 220 this.tooltip.remove_and_cancel_tooltip(true);
214 221 }
215 222 // If we closed the tooltip, don't let CM or the global handlers
216 223 // handle this event.
217 224 event.stop();
218 225 return true;
219 226 } else if (event.keyCode === keycodes.tab && event.type === 'keydown' && event.shiftKey) {
220 227 if (editor.somethingSelected()){
221 228 var anchor = editor.getCursor("anchor");
222 229 var head = editor.getCursor("head");
223 230 if( anchor.line != head.line){
224 231 return false;
225 232 }
226 233 }
227 234 this.tooltip.request(that);
228 235 event.stop();
229 236 return true;
230 237 } else if (event.keyCode === keycodes.tab && event.type == 'keydown') {
231 238 // Tab completion.
232 239 this.tooltip.remove_and_cancel_tooltip();
233 240 if (editor.somethingSelected()) {
234 241 return false;
235 242 }
236 243 var pre_cursor = editor.getRange({line:cur.line,ch:0},cur);
237 244 if (pre_cursor.trim() === "") {
238 245 // Don't autocomplete if the part of the line before the cursor
239 246 // is empty. In this case, let CodeMirror handle indentation.
240 247 return false;
241 248 } else {
242 249 event.stop();
243 250 this.completer.startCompletion();
244 251 return true;
245 252 }
246 253 }
247 254
248 255 // keyboard event wasn't one of those unique to code cells, let's see
249 256 // if it's one of the generic ones (i.e. check edit mode shortcuts)
250 257 return Cell.prototype.handle_codemirror_keyevent.apply(this, [editor, event]);
251 258 };
252 259
253 260 // Kernel related calls.
254 261
255 262 CodeCell.prototype.set_kernel = function (kernel) {
256 263 this.kernel = kernel;
257 264 };
258 265
259 266 /**
260 267 * Execute current code cell to the kernel
261 268 * @method execute
262 269 */
263 270 CodeCell.prototype.execute = function () {
264 271 this.output_area.clear_output();
265 272
266 273 // Clear widget area
267 274 this.widget_subarea.html('');
268 275 this.widget_subarea.height('');
269 276 this.widget_area.height('');
270 277 this.widget_area.hide();
271 278
272 279 this.set_input_prompt('*');
273 280 this.element.addClass("running");
274 281 if (this.last_msg_id) {
275 282 this.kernel.clear_callbacks_for_msg(this.last_msg_id);
276 283 }
277 284 var callbacks = this.get_callbacks();
278 285
279 286 var old_msg_id = this.last_msg_id;
280 287 this.last_msg_id = this.kernel.execute(this.get_text(), callbacks, {silent: false, store_history: true});
281 288 if (old_msg_id) {
282 289 delete CodeCell.msg_cells[old_msg_id];
283 290 }
284 291 CodeCell.msg_cells[this.last_msg_id] = this;
285 292 };
286 293
287 294 /**
288 295 * Construct the default callbacks for
289 296 * @method get_callbacks
290 297 */
291 298 CodeCell.prototype.get_callbacks = function () {
292 299 return {
293 300 shell : {
294 301 reply : $.proxy(this._handle_execute_reply, this),
295 302 payload : {
296 303 set_next_input : $.proxy(this._handle_set_next_input, this),
297 304 page : $.proxy(this._open_with_pager, this)
298 305 }
299 306 },
300 307 iopub : {
301 308 output : $.proxy(this.output_area.handle_output, this.output_area),
302 309 clear_output : $.proxy(this.output_area.handle_clear_output, this.output_area),
303 310 },
304 311 input : $.proxy(this._handle_input_request, this)
305 312 };
306 313 };
307 314
308 315 CodeCell.prototype._open_with_pager = function (payload) {
309 316 this.events.trigger('open_with_text.Pager', payload);
310 317 };
311 318
312 319 /**
313 320 * @method _handle_execute_reply
314 321 * @private
315 322 */
316 323 CodeCell.prototype._handle_execute_reply = function (msg) {
317 324 this.set_input_prompt(msg.content.execution_count);
318 325 this.element.removeClass("running");
319 326 this.events.trigger('set_dirty.Notebook', {value: true});
320 327 };
321 328
322 329 /**
323 330 * @method _handle_set_next_input
324 331 * @private
325 332 */
326 333 CodeCell.prototype._handle_set_next_input = function (payload) {
327 334 var data = {'cell': this, 'text': payload.text};
328 335 this.events.trigger('set_next_input.Notebook', data);
329 336 };
330 337
331 338 /**
332 339 * @method _handle_input_request
333 340 * @private
334 341 */
335 342 CodeCell.prototype._handle_input_request = function (msg) {
336 343 this.output_area.append_raw_input(msg);
337 344 };
338 345
339 346
340 347 // Basic cell manipulation.
341 348
342 349 CodeCell.prototype.select = function () {
343 350 var cont = Cell.prototype.select.apply(this);
344 351 if (cont) {
345 352 this.code_mirror.refresh();
346 353 this.auto_highlight();
347 354 }
348 355 return cont;
349 356 };
350 357
351 358 CodeCell.prototype.render = function () {
352 359 var cont = Cell.prototype.render.apply(this);
353 360 // Always execute, even if we are already in the rendered state
354 361 return cont;
355 362 };
356 363
357 364 CodeCell.prototype.unrender = function () {
358 365 // CodeCell is always rendered
359 366 return false;
360 367 };
361 368
362 369 CodeCell.prototype.select_all = function () {
363 370 var start = {line: 0, ch: 0};
364 371 var nlines = this.code_mirror.lineCount();
365 372 var last_line = this.code_mirror.getLine(nlines-1);
366 373 var end = {line: nlines-1, ch: last_line.length};
367 374 this.code_mirror.setSelection(start, end);
368 375 };
369 376
370 377
371 378 CodeCell.prototype.collapse_output = function () {
372 379 this.collapsed = true;
373 380 this.output_area.collapse();
374 381 };
375 382
376 383
377 384 CodeCell.prototype.expand_output = function () {
378 385 this.collapsed = false;
379 386 this.output_area.expand();
380 387 this.output_area.unscroll_area();
381 388 };
382 389
383 390 CodeCell.prototype.scroll_output = function () {
384 391 this.output_area.expand();
385 392 this.output_area.scroll_if_long();
386 393 };
387 394
388 395 CodeCell.prototype.toggle_output = function () {
389 396 this.collapsed = Boolean(1 - this.collapsed);
390 397 this.output_area.toggle_output();
391 398 };
392 399
393 400 CodeCell.prototype.toggle_output_scroll = function () {
394 401 this.output_area.toggle_scroll();
395 402 };
396 403
397 404
398 405 CodeCell.input_prompt_classical = function (prompt_value, lines_number) {
399 406 var ns;
400 407 if (prompt_value === undefined) {
401 408 ns = "&nbsp;";
402 409 } else {
403 410 ns = encodeURIComponent(prompt_value);
404 411 }
405 412 return 'In&nbsp;[' + ns + ']:';
406 413 };
407 414
408 415 CodeCell.input_prompt_continuation = function (prompt_value, lines_number) {
409 416 var html = [CodeCell.input_prompt_classical(prompt_value, lines_number)];
410 417 for(var i=1; i < lines_number; i++) {
411 418 html.push(['...:']);
412 419 }
413 420 return html.join('<br/>');
414 421 };
415 422
416 423 CodeCell.input_prompt_function = CodeCell.input_prompt_classical;
417 424
418 425
419 426 CodeCell.prototype.set_input_prompt = function (number) {
420 427 var nline = 1;
421 428 if (this.code_mirror !== undefined) {
422 429 nline = this.code_mirror.lineCount();
423 430 }
424 431 this.input_prompt_number = number;
425 432 var prompt_html = CodeCell.input_prompt_function(this.input_prompt_number, nline);
426 433 // This HTML call is okay because the user contents are escaped.
427 434 this.element.find('div.input_prompt').html(prompt_html);
428 435 };
429 436
430 437
431 438 CodeCell.prototype.clear_input = function () {
432 439 this.code_mirror.setValue('');
433 440 };
434 441
435 442
436 443 CodeCell.prototype.get_text = function () {
437 444 return this.code_mirror.getValue();
438 445 };
439 446
440 447
441 448 CodeCell.prototype.set_text = function (code) {
442 449 return this.code_mirror.setValue(code);
443 450 };
444 451
445 452
446 453 CodeCell.prototype.clear_output = function (wait) {
447 454 this.output_area.clear_output(wait);
448 455 this.set_input_prompt();
449 456 };
450 457
451 458
452 459 // JSON serialization
453 460
454 461 CodeCell.prototype.fromJSON = function (data) {
455 462 Cell.prototype.fromJSON.apply(this, arguments);
456 463 if (data.cell_type === 'code') {
457 464 if (data.input !== undefined) {
458 465 this.set_text(data.input);
459 466 // make this value the starting point, so that we can only undo
460 467 // to this state, instead of a blank cell
461 468 this.code_mirror.clearHistory();
462 469 this.auto_highlight();
463 470 }
464 471 if (data.prompt_number !== undefined) {
465 472 this.set_input_prompt(data.prompt_number);
466 473 } else {
467 474 this.set_input_prompt();
468 475 }
469 476 this.output_area.trusted = data.trusted || false;
470 477 this.output_area.fromJSON(data.outputs);
471 478 if (data.collapsed !== undefined) {
472 479 if (data.collapsed) {
473 480 this.collapse_output();
474 481 } else {
475 482 this.expand_output();
476 483 }
477 484 }
478 485 }
479 486 };
480 487
481 488
482 489 CodeCell.prototype.toJSON = function () {
483 490 var data = Cell.prototype.toJSON.apply(this);
484 491 data.input = this.get_text();
485 492 // is finite protect against undefined and '*' value
486 493 if (isFinite(this.input_prompt_number)) {
487 494 data.prompt_number = this.input_prompt_number;
488 495 }
489 496 var outputs = this.output_area.toJSON();
490 497 data.outputs = outputs;
491 498 data.language = 'python';
492 499 data.trusted = this.output_area.trusted;
493 500 data.collapsed = this.collapsed;
494 501 return data;
495 502 };
496 503
497 504 /**
498 505 * handle cell level logic when a cell is unselected
499 506 * @method unselect
500 507 * @return is the action being taken
501 508 */
502 509 CodeCell.prototype.unselect = function () {
503 510 var cont = Cell.prototype.unselect.apply(this);
504 511 if (cont) {
505 512 // When a code cell is usnelected, make sure that the corresponding
506 513 // tooltip and completer to that cell is closed.
507 514 this.tooltip.remove_and_cancel_tooltip(true);
508 515 if (this.completer !== null) {
509 516 this.completer.close();
510 517 }
511 518 }
512 519 return cont;
513 520 };
514 521
515 522 // Backwards compatability.
516 523 IPython.CodeCell = CodeCell;
517 524
518 525 return {'CodeCell': CodeCell};
519 526 });
@@ -1,138 +1,135 b''
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 4 require([
5 5 'base/js/namespace',
6 6 'jquery',
7 7 'notebook/js/notebook',
8 8 'base/js/utils',
9 'base/js/keyboard',
10 9 'base/js/page',
11 10 'notebook/js/layoutmanager',
12 11 'base/js/events',
13 12 'auth/js/loginwidget',
14 13 'notebook/js/maintoolbar',
15 14 'notebook/js/pager',
16 15 'notebook/js/quickhelp',
17 16 'notebook/js/menubar',
18 17 'notebook/js/notificationarea',
19 18 'notebook/js/savewidget',
20 19 'notebook/js/keyboardmanager',
21 20 'notebook/js/config',
22 21 ], function(
23 22 IPython,
24 23 $,
25 24 notebook,
26 25 utils,
27 keyboard,
28 26 page,
29 27 layoutmanager,
30 28 events,
31 29 loginwidget,
32 30 maintoolbar,
33 31 pager,
34 32 quickhelp,
35 33 menubar,
36 34 notificationarea,
37 35 savewidget,
38 36 keyboardmanager,
39 37 config
40 38 ) {
41 39 "use strict";
42 40
43 41 $('#ipython-main-app').addClass('border-box-sizing');
44 42 $('div#notebook_panel').addClass('border-box-sizing');
45 43
46 44 var common_options = {
47 45 base_url : utils.get_body_data("baseUrl"),
48 46 notebook_path : utils.get_body_data("notebookPath"),
49 47 notebook_name : utils.get_body_data('notebookName')
50 48 };
51 49
52 50 var user_config = $.extend({}, config.default_config);
53 51 var page = new page.Page();
54 52 var layout_manager = new layoutmanager.LayoutManager();
55 53 var events = $([new events.Events()]);
56 54 var pager = new pager.Pager('div#pager', 'div#pager_splitter', {
57 55 layout_manager: layout_manager,
58 56 events: events});
59 57 var keyboard_manager = new keyboardmanager.KeyboardManager({
60 58 pager: pager,
61 59 events: events});
62 60 var save_widget = new savewidget.SaveWidget('span#save_widget', {
63 61 events: events,
64 62 keyboard_manager: keyboard_manager});
65 63 var notebook = new notebook.Notebook('div#notebook', $.extend({
66 64 events: events,
67 65 keyboard_manager: keyboard_manager,
68 66 save_widget: save_widget,
69 67 config: user_config},
70 68 common_options));
71 69 var login_widget = new loginwidget.LoginWidget('span#login_widget', common_options);
72 70 var toolbar = new maintoolbar.MainToolBar('#maintoolbar-container', {
73 71 notebook: notebook,
74 72 events: events});
75 73 var quick_help = new quickhelp.QuickHelp({
76 74 keyboard_manager: keyboard_manager,
77 75 events: events,
78 76 notebook: notebook});
79 77 var menubar = new menubar.MenuBar('#menubar', $.extend({
80 78 notebook: notebook,
81 79 layout_manager: layout_manager,
82 80 events: events,
83 81 save_widget: save_widget,
84 82 quick_help: quick_help},
85 83 common_options));
86 84 var notification_area = new notificationarea.NotificationArea(
87 85 '#notification_area', {
88 86 events: events,
89 87 save_widget: save_widget,
90 88 notebook: notebook,
91 89 keyboard_manager: keyboard_manager});
92 90 notification_area.init_notification_widgets();
93 91
94 92 $('body').append('<div id="fonttest"><pre><span id="test1">x</span>'+
95 93 '<span id="test2" style="font-weight: bold;">x</span>'+
96 94 '<span id="test3" style="font-style: italic;">x</span></pre></div>');
97 95 var nh = $('#test1').innerHeight();
98 96 var bh = $('#test2').innerHeight();
99 97 var ih = $('#test3').innerHeight();
100 98 if(nh != bh || nh != ih) {
101 99 $('head').append('<style>.CodeMirror span { vertical-align: bottom; }</style>');
102 100 }
103 101 $('#fonttest').remove();
104 102
105 103 page.show();
106 104
107 105 layout_manager.do_resize();
108 106 var first_load = function () {
109 107 layout_manager.do_resize();
110 108 var hash = document.location.hash;
111 109 if (hash) {
112 110 document.location.hash = '';
113 111 document.location.hash = hash;
114 112 }
115 113 notebook.set_autosave_interval(notebook.minimum_autosave_interval);
116 114 // only do this once
117 115 events.off('notebook_loaded.Notebook', first_load);
118 116 };
119 117
120 118 events.on('notebook_loaded.Notebook', first_load);
121 119 events.trigger('app_initialized.NotebookApp');
122 120 notebook.load_notebook(common_options.notebook_name, common_options.notebook_path);
123 121
124 122 IPython.page = page;
125 123 IPython.layout_manager = layout_manager;
126 124 IPython.notebook = notebook;
127 125 IPython.pager = pager;
128 126 IPython.quick_help = quick_help;
129 127 IPython.login_widget = login_widget;
130 128 IPython.menubar = menubar;
131 129 IPython.toolbar = toolbar;
132 130 IPython.notification_area = notification_area;
133 131 IPython.events = events;
134 132 IPython.keyboard_manager = keyboard_manager;
135 133 IPython.save_widget = save_widget;
136 134 IPython.config = user_config;
137 IPython.keyboard = keyboard;
138 135 });
@@ -1,348 +1,350 b''
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 4 define([
5 5 'base/js/namespace',
6 6 'jquery',
7 7 'base/js/utils',
8 8 'notebook/js/tour',
9 9 ], function(IPython, $, utils, tour) {
10 10 "use strict";
11 11
12 12 var MenuBar = function (selector, options) {
13 13 // Constructor
14 14 //
15 15 // A MenuBar Class to generate the menubar of IPython notebook
16 16 //
17 17 // Parameters:
18 18 // selector: string
19 19 // options: dictionary
20 20 // Dictionary of keyword arguments.
21 21 // notebook: Notebook instance
22 22 // layout_manager: LayoutManager instance
23 23 // events: $(Events) instance
24 24 // save_widget: SaveWidget instance
25 25 // quick_help: QuickHelp instance
26 26 // base_url : string
27 27 // notebook_path : string
28 28 // notebook_name : string
29 29 options = options || {};
30 30 this.base_url = options.base_url || utils.get_body_data("baseUrl");
31 31 this.selector = selector;
32 32 this.notebook = options.notebook;
33 33 this.layout_manager = options.layout_manager;
34 34 this.events = options.events;
35 35 this.save_widget = options.save_widget;
36 36 this.quick_help = options.quick_help;
37 37
38 38 try {
39 39 this.tour = new tour.Tour(this.notebook, this.events);
40 40 } catch (e) {
41 41 this.tour = undefined;
42 42 console.log("Failed to instantiate Notebook Tour", e);
43 43 }
44 44
45 45 if (this.selector !== undefined) {
46 46 this.element = $(selector);
47 47 this.style();
48 48 this.bind_events();
49 49 }
50 50 };
51 51
52 52 MenuBar.prototype.style = function () {
53 53 var that = this;
54 54 this.element.addClass('border-box-sizing');
55 55 this.element.find("li").click(function (event, ui) {
56 56 // The selected cell loses focus when the menu is entered, so we
57 57 // re-select it upon selection.
58 58 var i = that.notebook.get_selected_index();
59 59 that.notebook.select(i);
60 60 }
61 61 );
62 62 };
63 63
64 64 MenuBar.prototype._nbconvert = function (format, download) {
65 65 download = download || false;
66 66 var notebook_path = this.notebook.notebook_path;
67 67 var notebook_name = this.notebook.notebook_name;
68 68 if (this.notebook.dirty) {
69 69 this.notebook.save_notebook({async : false});
70 70 }
71 71 var url = utils.url_join_encode(
72 72 this.base_url,
73 73 'nbconvert',
74 74 format,
75 75 notebook_path,
76 76 notebook_name
77 77 ) + "?download=" + download.toString();
78 78
79 79 window.open(url);
80 80 };
81 81
82 82 MenuBar.prototype.bind_events = function () {
83 83 // File
84 84 var that = this;
85 85 this.element.find('#new_notebook').click(function () {
86 86 that.notebook.new_notebook();
87 87 });
88 88 this.element.find('#open_notebook').click(function () {
89 89 window.open(utils.url_join_encode(
90 90 that.notebook.base_url,
91 91 'tree',
92 92 that.notebook.notebook_path
93 93 ));
94 94 });
95 95 this.element.find('#copy_notebook').click(function () {
96 96 that.notebook.copy_notebook();
97 97 return false;
98 98 });
99 99 this.element.find('#download_ipynb').click(function () {
100 100 var base_url = that.notebook.base_url;
101 101 var notebook_path = that.notebook.notebook_path;
102 102 var notebook_name = that.notebook.notebook_name;
103 103 if (that.notebook.dirty) {
104 104 that.notebook.save_notebook({async : false});
105 105 }
106 106
107 107 var url = utils.url_join_encode(
108 108 base_url,
109 109 'files',
110 110 notebook_path,
111 111 notebook_name
112 112 );
113 113 window.location.assign(url);
114 114 });
115 115
116 116 this.element.find('#print_preview').click(function () {
117 117 that._nbconvert('html', false);
118 118 });
119 119
120 120 this.element.find('#download_py').click(function () {
121 121 that._nbconvert('python', true);
122 122 });
123 123
124 124 this.element.find('#download_html').click(function () {
125 125 that._nbconvert('html', true);
126 126 });
127 127
128 128 this.element.find('#download_rst').click(function () {
129 129 that._nbconvert('rst', true);
130 130 });
131 131
132 132 this.element.find('#download_pdf').click(function () {
133 133 that._nbconvert('pdf', true);
134 134 });
135 135
136 136 this.element.find('#rename_notebook').click(function () {
137 137 that.save_widget.rename_notebook({notebook: that.notebook});
138 138 });
139 139 this.element.find('#save_checkpoint').click(function () {
140 140 that.notebook.save_checkpoint();
141 141 });
142 142 this.element.find('#restore_checkpoint').click(function () {
143 143 });
144 144 this.element.find('#trust_notebook').click(function () {
145 145 that.notebook.trust_notebook();
146 146 });
147 147 this.events.on('trust_changed.Notebook', function (event, trusted) {
148 148 if (trusted) {
149 149 that.element.find('#trust_notebook')
150 150 .addClass("disabled")
151 151 .find("a").text("Trusted Notebook");
152 152 } else {
153 153 that.element.find('#trust_notebook')
154 154 .removeClass("disabled")
155 155 .find("a").text("Trust Notebook");
156 156 }
157 157 });
158 158 this.element.find('#kill_and_exit').click(function () {
159 159 that.notebook.session.delete();
160 160 setTimeout(function(){
161 161 // allow closing of new tabs in Chromium, impossible in FF
162 162 window.open('', '_self', '');
163 163 window.close();
164 164 }, 500);
165 165 });
166 166 // Edit
167 167 this.element.find('#cut_cell').click(function () {
168 168 that.notebook.cut_cell();
169 169 });
170 170 this.element.find('#copy_cell').click(function () {
171 171 that.notebook.copy_cell();
172 172 });
173 173 this.element.find('#delete_cell').click(function () {
174 174 that.notebook.delete_cell();
175 175 });
176 176 this.element.find('#undelete_cell').click(function () {
177 177 that.notebook.undelete_cell();
178 178 });
179 179 this.element.find('#split_cell').click(function () {
180 180 that.notebook.split_cell();
181 181 });
182 182 this.element.find('#merge_cell_above').click(function () {
183 183 that.notebook.merge_cell_above();
184 184 });
185 185 this.element.find('#merge_cell_below').click(function () {
186 186 that.notebook.merge_cell_below();
187 187 });
188 188 this.element.find('#move_cell_up').click(function () {
189 189 that.notebook.move_cell_up();
190 190 });
191 191 this.element.find('#move_cell_down').click(function () {
192 192 that.notebook.move_cell_down();
193 193 });
194 194 this.element.find('#edit_nb_metadata').click(function () {
195 that.notebook.edit_metadata();
195 that.notebook.edit_metadata({
196 notebook: that.notebook,
197 keyboard_manager: that.notebook.keyboard_manager});
196 198 });
197 199
198 200 // View
199 201 this.element.find('#toggle_header').click(function () {
200 202 $('div#header').toggle();
201 203 that.layout_manager.do_resize();
202 204 });
203 205 this.element.find('#toggle_toolbar').click(function () {
204 206 $('div#maintoolbar').toggle();
205 207 that.layout_manager.do_resize();
206 208 });
207 209 // Insert
208 210 this.element.find('#insert_cell_above').click(function () {
209 211 that.notebook.insert_cell_above('code');
210 212 that.notebook.select_prev();
211 213 });
212 214 this.element.find('#insert_cell_below').click(function () {
213 215 that.notebook.insert_cell_below('code');
214 216 that.notebook.select_next();
215 217 });
216 218 // Cell
217 219 this.element.find('#run_cell').click(function () {
218 220 that.notebook.execute_cell();
219 221 });
220 222 this.element.find('#run_cell_select_below').click(function () {
221 223 that.notebook.execute_cell_and_select_below();
222 224 });
223 225 this.element.find('#run_cell_insert_below').click(function () {
224 226 that.notebook.execute_cell_and_insert_below();
225 227 });
226 228 this.element.find('#run_all_cells').click(function () {
227 229 that.notebook.execute_all_cells();
228 230 });
229 231 this.element.find('#run_all_cells_above').click(function () {
230 232 that.notebook.execute_cells_above();
231 233 });
232 234 this.element.find('#run_all_cells_below').click(function () {
233 235 that.notebook.execute_cells_below();
234 236 });
235 237 this.element.find('#to_code').click(function () {
236 238 that.notebook.to_code();
237 239 });
238 240 this.element.find('#to_markdown').click(function () {
239 241 that.notebook.to_markdown();
240 242 });
241 243 this.element.find('#to_raw').click(function () {
242 244 that.notebook.to_raw();
243 245 });
244 246 this.element.find('#to_heading1').click(function () {
245 247 that.notebook.to_heading(undefined, 1);
246 248 });
247 249 this.element.find('#to_heading2').click(function () {
248 250 that.notebook.to_heading(undefined, 2);
249 251 });
250 252 this.element.find('#to_heading3').click(function () {
251 253 that.notebook.to_heading(undefined, 3);
252 254 });
253 255 this.element.find('#to_heading4').click(function () {
254 256 that.notebook.to_heading(undefined, 4);
255 257 });
256 258 this.element.find('#to_heading5').click(function () {
257 259 that.notebook.to_heading(undefined, 5);
258 260 });
259 261 this.element.find('#to_heading6').click(function () {
260 262 that.notebook.to_heading(undefined, 6);
261 263 });
262 264
263 265 this.element.find('#toggle_current_output').click(function () {
264 266 that.notebook.toggle_output();
265 267 });
266 268 this.element.find('#toggle_current_output_scroll').click(function () {
267 269 that.notebook.toggle_output_scroll();
268 270 });
269 271 this.element.find('#clear_current_output').click(function () {
270 272 that.notebook.clear_output();
271 273 });
272 274
273 275 this.element.find('#toggle_all_output').click(function () {
274 276 that.notebook.toggle_all_output();
275 277 });
276 278 this.element.find('#toggle_all_output_scroll').click(function () {
277 279 that.notebook.toggle_all_output_scroll();
278 280 });
279 281 this.element.find('#clear_all_output').click(function () {
280 282 that.notebook.clear_all_output();
281 283 });
282 284
283 285 // Kernel
284 286 this.element.find('#int_kernel').click(function () {
285 287 that.notebook.session.interrupt_kernel();
286 288 });
287 289 this.element.find('#restart_kernel').click(function () {
288 290 that.notebook.restart_kernel();
289 291 });
290 292 // Help
291 293 if (this.tour) {
292 294 this.element.find('#notebook_tour').click(function () {
293 295 that.tour.start();
294 296 });
295 297 } else {
296 298 this.element.find('#notebook_tour').addClass("disabled");
297 299 }
298 300 this.element.find('#keyboard_shortcuts').click(function () {
299 301 that.quick_help.show_keyboard_shortcuts();
300 302 });
301 303
302 304 this.update_restore_checkpoint(null);
303 305
304 306 this.events.on('checkpoints_listed.Notebook', function (event, data) {
305 307 that.update_restore_checkpoint(that.notebook.checkpoints);
306 308 });
307 309
308 310 this.events.on('checkpoint_created.Notebook', function (event, data) {
309 311 that.update_restore_checkpoint(that.notebook.checkpoints);
310 312 });
311 313 };
312 314
313 315 MenuBar.prototype.update_restore_checkpoint = function(checkpoints) {
314 316 var ul = this.element.find("#restore_checkpoint").find("ul");
315 317 ul.empty();
316 318 if (!checkpoints || checkpoints.length === 0) {
317 319 ul.append(
318 320 $("<li/>")
319 321 .addClass("disabled")
320 322 .append(
321 323 $("<a/>")
322 324 .text("No checkpoints")
323 325 )
324 326 );
325 327 return;
326 328 }
327 329
328 330 var that = this;
329 331 checkpoints.map(function (checkpoint) {
330 332 var d = new Date(checkpoint.last_modified);
331 333 ul.append(
332 334 $("<li/>").append(
333 335 $("<a/>")
334 336 .attr("href", "#")
335 337 .text(d.format("mmm dd HH:MM:ss"))
336 338 .click(function () {
337 339 that.notebook.restore_checkpoint_dialog(checkpoint);
338 340 })
339 341 )
340 342 );
341 343 });
342 344 };
343 345
344 346 // Backwards compatability.
345 347 IPython.MenuBar = MenuBar;
346 348
347 349 return {'MenuBar': MenuBar};
348 350 });
@@ -1,2514 +1,2512 b''
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 4 define([
5 5 'base/js/namespace',
6 6 'jquery',
7 7 'base/js/utils',
8 8 'base/js/dialog',
9 9 'notebook/js/textcell',
10 10 'notebook/js/codecell',
11 11 'services/sessions/js/session',
12 12 'notebook/js/celltoolbar',
13 13 'components/marked/lib/marked',
14 14 'notebook/js/mathjaxutils',
15 15 'base/js/keyboard',
16 'components/jquery-ui/ui/minified/jquery-ui.min',
17 'components/bootstrap/js/bootstrap.min',
18 16 ], function (
19 17 IPython,
20 18 $,
21 19 utils,
22 20 dialog,
23 21 textcell,
24 22 codecell,
25 23 session,
26 24 celltoolbar,
27 25 marked,
28 26 mathjaxutils,
29 27 keyboard
30 28 ) {
31 29
32 30 var Notebook = function (selector, options) {
33 31 // Constructor
34 32 //
35 33 // A notebook contains and manages cells.
36 34 //
37 35 // Parameters:
38 36 // selector: string
39 37 // options: dictionary
40 38 // Dictionary of keyword arguments.
41 39 // events: $(Events) instance
42 40 // keyboard_manager: KeyboardManager instance
43 41 // save_widget: SaveWidget instance
44 42 // config: dictionary
45 43 // base_url : string
46 44 // notebook_path : string
47 45 // notebook_name : string
48 46 this.config = options.config || {};
49 47 this.base_url = options.base_url;
50 48 this.notebook_path = options.notebook_path;
51 49 this.notebook_name = options.notebook_name;
52 50 this.events = options.events;
53 51 this.keyboard_manager = options.keyboard_manager;
54 52 this.save_widget = options.save_widget;
55 53 // TODO: This code smells (and the other `= this` line a couple lines down)
56 54 // We need a better way to deal with circular instance references.
57 55 this.keyboard_manager.notebook = this;
58 56 this.save_widget.notebook = this;
59 57
60 58 mathjaxutils.init();
61 59
62 60 if (marked) {
63 61 marked.setOptions({
64 62 gfm : true,
65 63 tables: true,
66 64 langPrefix: "language-",
67 65 highlight: function(code, lang) {
68 66 if (!lang) {
69 67 // no language, no highlight
70 68 return code;
71 69 }
72 70 var highlighted;
73 71 try {
74 72 highlighted = hljs.highlight(lang, code, false);
75 73 } catch(err) {
76 74 highlighted = hljs.highlightAuto(code);
77 75 }
78 76 return highlighted.value;
79 77 }
80 78 });
81 79 }
82 80
83 // Backwards compatability.
84 IPython.keyboard_manager = this.keyboard_manager;
85 IPython.save_widget = this.save_widget;
86 IPython.keyboard = this.keyboard;
87
88 81 this.element = $(selector);
89 82 this.element.scroll();
90 83 this.element.data("notebook", this);
91 84 this.next_prompt_number = 1;
92 85 this.session = null;
93 86 this.kernel = null;
94 87 this.clipboard = null;
95 88 this.undelete_backup = null;
96 89 this.undelete_index = null;
97 90 this.undelete_below = false;
98 91 this.paste_enabled = false;
99 92 // It is important to start out in command mode to match the intial mode
100 93 // of the KeyboardManager.
101 94 this.mode = 'command';
102 95 this.set_dirty(false);
103 96 this.metadata = {};
104 97 this._checkpoint_after_save = false;
105 98 this.last_checkpoint = null;
106 99 this.checkpoints = [];
107 100 this.autosave_interval = 0;
108 101 this.autosave_timer = null;
109 102 // autosave *at most* every two minutes
110 103 this.minimum_autosave_interval = 120000;
111 104 // single worksheet for now
112 105 this.worksheet_metadata = {};
113 106 this.notebook_name_blacklist_re = /[\/\\:]/;
114 107 this.nbformat = 3; // Increment this when changing the nbformat
115 108 this.nbformat_minor = 0; // Increment this when changing the nbformat
116 109 this.style();
117 110 this.create_elements();
118 111 this.bind_events();
119 112 this.save_notebook = function() { // don't allow save until notebook_loaded
120 113 this.save_notebook_error(null, null, "Load failed, save is disabled");
121 114 };
122 115 };
123 116
124 117 /**
125 118 * Tweak the notebook's CSS style.
126 119 *
127 120 * @method style
128 121 */
129 122 Notebook.prototype.style = function () {
130 123 $('div#notebook').addClass('border-box-sizing');
131 124 };
132 125
133 126 /**
134 127 * Create an HTML and CSS representation of the notebook.
135 128 *
136 129 * @method create_elements
137 130 */
138 131 Notebook.prototype.create_elements = function () {
139 132 var that = this;
140 133 this.element.attr('tabindex','-1');
141 134 this.container = $("<div/>").addClass("container").attr("id", "notebook-container");
142 135 // We add this end_space div to the end of the notebook div to:
143 136 // i) provide a margin between the last cell and the end of the notebook
144 137 // ii) to prevent the div from scrolling up when the last cell is being
145 138 // edited, but is too low on the page, which browsers will do automatically.
146 139 var end_space = $('<div/>').addClass('end_space');
147 140 end_space.dblclick(function (e) {
148 141 var ncells = that.ncells();
149 142 that.insert_cell_below('code',ncells-1);
150 143 });
151 144 this.element.append(this.container);
152 145 this.container.append(end_space);
153 146 };
154 147
155 148 /**
156 149 * Bind JavaScript events: key presses and custom IPython events.
157 150 *
158 151 * @method bind_events
159 152 */
160 153 Notebook.prototype.bind_events = function () {
161 154 var that = this;
162 155
163 156 this.events.on('set_next_input.Notebook', function (event, data) {
164 157 var index = that.find_cell_index(data.cell);
165 158 var new_cell = that.insert_cell_below('code',index);
166 159 new_cell.set_text(data.text);
167 160 that.dirty = true;
168 161 });
169 162
170 163 this.events.on('set_dirty.Notebook', function (event, data) {
171 164 that.dirty = data.value;
172 165 });
173 166
174 167 this.events.on('trust_changed.Notebook', function (event, data) {
175 168 that.trusted = data.value;
176 169 });
177 170
178 171 this.events.on('select.Cell', function (event, data) {
179 172 var index = that.find_cell_index(data.cell);
180 173 that.select(index);
181 174 });
182 175
183 176 this.events.on('edit_mode.Cell', function (event, data) {
184 177 that.handle_edit_mode(data.cell);
185 178 });
186 179
187 180 this.events.on('command_mode.Cell', function (event, data) {
188 181 that.handle_command_mode(data.cell);
189 182 });
190 183
191 184 this.events.on('status_autorestarting.Kernel', function () {
192 185 dialog.modal({
193 186 notebook: that,
194 187 keyboard_manager: that.keyboard_manager,
195 188 title: "Kernel Restarting",
196 189 body: "The kernel appears to have died. It will restart automatically.",
197 190 buttons: {
198 191 OK : {
199 192 class : "btn-primary"
200 193 }
201 194 }
202 195 });
203 196 });
204 197
205 198 var collapse_time = function (time) {
206 199 var app_height = $('#ipython-main-app').height(); // content height
207 200 var splitter_height = $('div#pager_splitter').outerHeight(true);
208 201 var new_height = app_height - splitter_height;
209 202 that.element.animate({height : new_height + 'px'}, time);
210 203 };
211 204
212 205 this.element.bind('collapse_pager', function (event, extrap) {
213 206 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
214 207 collapse_time(time);
215 208 });
216 209
217 210 var expand_time = function (time) {
218 211 var app_height = $('#ipython-main-app').height(); // content height
219 212 var splitter_height = $('div#pager_splitter').outerHeight(true);
220 213 var pager_height = $('div#pager').outerHeight(true);
221 214 var new_height = app_height - pager_height - splitter_height;
222 215 that.element.animate({height : new_height + 'px'}, time);
223 216 };
224 217
225 218 this.element.bind('expand_pager', function (event, extrap) {
226 219 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
227 220 expand_time(time);
228 221 });
229 222
230 223 // Firefox 22 broke $(window).on("beforeunload")
231 224 // I'm not sure why or how.
232 225 window.onbeforeunload = function (e) {
233 226 // TODO: Make killing the kernel configurable.
234 227 var kill_kernel = false;
235 228 if (kill_kernel) {
236 229 that.session.kill_kernel();
237 230 }
238 231 // if we are autosaving, trigger an autosave on nav-away.
239 232 // still warn, because if we don't the autosave may fail.
240 233 if (that.dirty) {
241 234 if ( that.autosave_interval ) {
242 235 // schedule autosave in a timeout
243 236 // this gives you a chance to forcefully discard changes
244 237 // by reloading the page if you *really* want to.
245 238 // the timer doesn't start until you *dismiss* the dialog.
246 239 setTimeout(function () {
247 240 if (that.dirty) {
248 241 that.save_notebook();
249 242 }
250 243 }, 1000);
251 244 return "Autosave in progress, latest changes may be lost.";
252 245 } else {
253 246 return "Unsaved changes will be lost.";
254 247 }
255 248 }
256 249 // Null is the *only* return value that will make the browser not
257 250 // pop up the "don't leave" dialog.
258 251 return null;
259 252 };
260 253 };
261 254
262 255 /**
263 256 * Set the dirty flag, and trigger the set_dirty.Notebook event
264 257 *
265 258 * @method set_dirty
266 259 */
267 260 Notebook.prototype.set_dirty = function (value) {
268 261 if (value === undefined) {
269 262 value = true;
270 263 }
271 264 if (this.dirty == value) {
272 265 return;
273 266 }
274 267 this.events.trigger('set_dirty.Notebook', {value: value});
275 268 };
276 269
277 270 /**
278 271 * Scroll the top of the page to a given cell.
279 272 *
280 273 * @method scroll_to_cell
281 274 * @param {Number} cell_number An index of the cell to view
282 275 * @param {Number} time Animation time in milliseconds
283 276 * @return {Number} Pixel offset from the top of the container
284 277 */
285 278 Notebook.prototype.scroll_to_cell = function (cell_number, time) {
286 279 var cells = this.get_cells();
287 280 time = time || 0;
288 281 cell_number = Math.min(cells.length-1,cell_number);
289 282 cell_number = Math.max(0 ,cell_number);
290 283 var scroll_value = cells[cell_number].element.position().top-cells[0].element.position().top ;
291 284 this.element.animate({scrollTop:scroll_value}, time);
292 285 return scroll_value;
293 286 };
294 287
295 288 /**
296 289 * Scroll to the bottom of the page.
297 290 *
298 291 * @method scroll_to_bottom
299 292 */
300 293 Notebook.prototype.scroll_to_bottom = function () {
301 294 this.element.animate({scrollTop:this.element.get(0).scrollHeight}, 0);
302 295 };
303 296
304 297 /**
305 298 * Scroll to the top of the page.
306 299 *
307 300 * @method scroll_to_top
308 301 */
309 302 Notebook.prototype.scroll_to_top = function () {
310 303 this.element.animate({scrollTop:0}, 0);
311 304 };
312 305
313 306 // Edit Notebook metadata
314 307
315 308 Notebook.prototype.edit_metadata = function () {
316 309 var that = this;
317 dialog.edit_metadata(this.metadata, function (md) {
318 that.metadata = md;
319 }, 'Notebook');
310 dialog.edit_metadata({
311 md: this.metadata,
312 callback: function (md) {
313 that.metadata = md;
314 },
315 name: 'Notebook',
316 notebook: this,
317 keyboard_manager: this.keyboard_manager});
320 318 };
321 319
322 320 // Cell indexing, retrieval, etc.
323 321
324 322 /**
325 323 * Get all cell elements in the notebook.
326 324 *
327 325 * @method get_cell_elements
328 326 * @return {jQuery} A selector of all cell elements
329 327 */
330 328 Notebook.prototype.get_cell_elements = function () {
331 329 return this.container.children("div.cell");
332 330 };
333 331
334 332 /**
335 333 * Get a particular cell element.
336 334 *
337 335 * @method get_cell_element
338 336 * @param {Number} index An index of a cell to select
339 337 * @return {jQuery} A selector of the given cell.
340 338 */
341 339 Notebook.prototype.get_cell_element = function (index) {
342 340 var result = null;
343 341 var e = this.get_cell_elements().eq(index);
344 342 if (e.length !== 0) {
345 343 result = e;
346 344 }
347 345 return result;
348 346 };
349 347
350 348 /**
351 349 * Try to get a particular cell by msg_id.
352 350 *
353 351 * @method get_msg_cell
354 352 * @param {String} msg_id A message UUID
355 353 * @return {Cell} Cell or null if no cell was found.
356 354 */
357 355 Notebook.prototype.get_msg_cell = function (msg_id) {
358 356 return codecell.CodeCell.msg_cells[msg_id] || null;
359 357 };
360 358
361 359 /**
362 360 * Count the cells in this notebook.
363 361 *
364 362 * @method ncells
365 363 * @return {Number} The number of cells in this notebook
366 364 */
367 365 Notebook.prototype.ncells = function () {
368 366 return this.get_cell_elements().length;
369 367 };
370 368
371 369 /**
372 370 * Get all Cell objects in this notebook.
373 371 *
374 372 * @method get_cells
375 373 * @return {Array} This notebook's Cell objects
376 374 */
377 375 // TODO: we are often calling cells as cells()[i], which we should optimize
378 376 // to cells(i) or a new method.
379 377 Notebook.prototype.get_cells = function () {
380 378 return this.get_cell_elements().toArray().map(function (e) {
381 379 return $(e).data("cell");
382 380 });
383 381 };
384 382
385 383 /**
386 384 * Get a Cell object from this notebook.
387 385 *
388 386 * @method get_cell
389 387 * @param {Number} index An index of a cell to retrieve
390 388 * @return {Cell} A particular cell
391 389 */
392 390 Notebook.prototype.get_cell = function (index) {
393 391 var result = null;
394 392 var ce = this.get_cell_element(index);
395 393 if (ce !== null) {
396 394 result = ce.data('cell');
397 395 }
398 396 return result;
399 397 };
400 398
401 399 /**
402 400 * Get the cell below a given cell.
403 401 *
404 402 * @method get_next_cell
405 403 * @param {Cell} cell The provided cell
406 404 * @return {Cell} The next cell
407 405 */
408 406 Notebook.prototype.get_next_cell = function (cell) {
409 407 var result = null;
410 408 var index = this.find_cell_index(cell);
411 409 if (this.is_valid_cell_index(index+1)) {
412 410 result = this.get_cell(index+1);
413 411 }
414 412 return result;
415 413 };
416 414
417 415 /**
418 416 * Get the cell above a given cell.
419 417 *
420 418 * @method get_prev_cell
421 419 * @param {Cell} cell The provided cell
422 420 * @return {Cell} The previous cell
423 421 */
424 422 Notebook.prototype.get_prev_cell = function (cell) {
425 423 // TODO: off-by-one
426 424 // nb.get_prev_cell(nb.get_cell(1)) is null
427 425 var result = null;
428 426 var index = this.find_cell_index(cell);
429 427 if (index !== null && index > 1) {
430 428 result = this.get_cell(index-1);
431 429 }
432 430 return result;
433 431 };
434 432
435 433 /**
436 434 * Get the numeric index of a given cell.
437 435 *
438 436 * @method find_cell_index
439 437 * @param {Cell} cell The provided cell
440 438 * @return {Number} The cell's numeric index
441 439 */
442 440 Notebook.prototype.find_cell_index = function (cell) {
443 441 var result = null;
444 442 this.get_cell_elements().filter(function (index) {
445 443 if ($(this).data("cell") === cell) {
446 444 result = index;
447 445 }
448 446 });
449 447 return result;
450 448 };
451 449
452 450 /**
453 451 * Get a given index , or the selected index if none is provided.
454 452 *
455 453 * @method index_or_selected
456 454 * @param {Number} index A cell's index
457 455 * @return {Number} The given index, or selected index if none is provided.
458 456 */
459 457 Notebook.prototype.index_or_selected = function (index) {
460 458 var i;
461 459 if (index === undefined || index === null) {
462 460 i = this.get_selected_index();
463 461 if (i === null) {
464 462 i = 0;
465 463 }
466 464 } else {
467 465 i = index;
468 466 }
469 467 return i;
470 468 };
471 469
472 470 /**
473 471 * Get the currently selected cell.
474 472 * @method get_selected_cell
475 473 * @return {Cell} The selected cell
476 474 */
477 475 Notebook.prototype.get_selected_cell = function () {
478 476 var index = this.get_selected_index();
479 477 return this.get_cell(index);
480 478 };
481 479
482 480 /**
483 481 * Check whether a cell index is valid.
484 482 *
485 483 * @method is_valid_cell_index
486 484 * @param {Number} index A cell index
487 485 * @return True if the index is valid, false otherwise
488 486 */
489 487 Notebook.prototype.is_valid_cell_index = function (index) {
490 488 if (index !== null && index >= 0 && index < this.ncells()) {
491 489 return true;
492 490 } else {
493 491 return false;
494 492 }
495 493 };
496 494
497 495 /**
498 496 * Get the index of the currently selected cell.
499 497
500 498 * @method get_selected_index
501 499 * @return {Number} The selected cell's numeric index
502 500 */
503 501 Notebook.prototype.get_selected_index = function () {
504 502 var result = null;
505 503 this.get_cell_elements().filter(function (index) {
506 504 if ($(this).data("cell").selected === true) {
507 505 result = index;
508 506 }
509 507 });
510 508 return result;
511 509 };
512 510
513 511
514 512 // Cell selection.
515 513
516 514 /**
517 515 * Programmatically select a cell.
518 516 *
519 517 * @method select
520 518 * @param {Number} index A cell's index
521 519 * @return {Notebook} This notebook
522 520 */
523 521 Notebook.prototype.select = function (index) {
524 522 if (this.is_valid_cell_index(index)) {
525 523 var sindex = this.get_selected_index();
526 524 if (sindex !== null && index !== sindex) {
527 525 // If we are about to select a different cell, make sure we are
528 526 // first in command mode.
529 527 if (this.mode !== 'command') {
530 528 this.command_mode();
531 529 }
532 530 this.get_cell(sindex).unselect();
533 531 }
534 532 var cell = this.get_cell(index);
535 533 cell.select();
536 534 if (cell.cell_type === 'heading') {
537 535 this.events.trigger('selected_cell_type_changed.Notebook',
538 536 {'cell_type':cell.cell_type,level:cell.level}
539 537 );
540 538 } else {
541 539 this.events.trigger('selected_cell_type_changed.Notebook',
542 540 {'cell_type':cell.cell_type}
543 541 );
544 542 }
545 543 }
546 544 return this;
547 545 };
548 546
549 547 /**
550 548 * Programmatically select the next cell.
551 549 *
552 550 * @method select_next
553 551 * @return {Notebook} This notebook
554 552 */
555 553 Notebook.prototype.select_next = function () {
556 554 var index = this.get_selected_index();
557 555 this.select(index+1);
558 556 return this;
559 557 };
560 558
561 559 /**
562 560 * Programmatically select the previous cell.
563 561 *
564 562 * @method select_prev
565 563 * @return {Notebook} This notebook
566 564 */
567 565 Notebook.prototype.select_prev = function () {
568 566 var index = this.get_selected_index();
569 567 this.select(index-1);
570 568 return this;
571 569 };
572 570
573 571
574 572 // Edit/Command mode
575 573
576 574 /**
577 575 * Gets the index of the cell that is in edit mode.
578 576 *
579 577 * @method get_edit_index
580 578 *
581 579 * @return index {int}
582 580 **/
583 581 Notebook.prototype.get_edit_index = function () {
584 582 var result = null;
585 583 this.get_cell_elements().filter(function (index) {
586 584 if ($(this).data("cell").mode === 'edit') {
587 585 result = index;
588 586 }
589 587 });
590 588 return result;
591 589 };
592 590
593 591 /**
594 592 * Handle when a a cell blurs and the notebook should enter command mode.
595 593 *
596 594 * @method handle_command_mode
597 595 * @param [cell] {Cell} Cell to enter command mode on.
598 596 **/
599 597 Notebook.prototype.handle_command_mode = function (cell) {
600 598 if (this.mode !== 'command') {
601 599 cell.command_mode();
602 600 this.mode = 'command';
603 601 this.events.trigger('command_mode.Notebook');
604 602 this.keyboard_manager.command_mode();
605 603 }
606 604 };
607 605
608 606 /**
609 607 * Make the notebook enter command mode.
610 608 *
611 609 * @method command_mode
612 610 **/
613 611 Notebook.prototype.command_mode = function () {
614 612 var cell = this.get_cell(this.get_edit_index());
615 613 if (cell && this.mode !== 'command') {
616 614 // We don't call cell.command_mode, but rather call cell.focus_cell()
617 615 // which will blur and CM editor and trigger the call to
618 616 // handle_command_mode.
619 617 cell.focus_cell();
620 618 }
621 619 };
622 620
623 621 /**
624 622 * Handle when a cell fires it's edit_mode event.
625 623 *
626 624 * @method handle_edit_mode
627 625 * @param [cell] {Cell} Cell to enter edit mode on.
628 626 **/
629 627 Notebook.prototype.handle_edit_mode = function (cell) {
630 628 if (cell && this.mode !== 'edit') {
631 629 cell.edit_mode();
632 630 this.mode = 'edit';
633 631 this.events.trigger('edit_mode.Notebook');
634 632 this.keyboard_manager.edit_mode();
635 633 }
636 634 };
637 635
638 636 /**
639 637 * Make a cell enter edit mode.
640 638 *
641 639 * @method edit_mode
642 640 **/
643 641 Notebook.prototype.edit_mode = function () {
644 642 var cell = this.get_selected_cell();
645 643 if (cell && this.mode !== 'edit') {
646 644 cell.unrender();
647 645 cell.focus_editor();
648 646 }
649 647 };
650 648
651 649 /**
652 650 * Focus the currently selected cell.
653 651 *
654 652 * @method focus_cell
655 653 **/
656 654 Notebook.prototype.focus_cell = function () {
657 655 var cell = this.get_selected_cell();
658 656 if (cell === null) {return;} // No cell is selected
659 657 cell.focus_cell();
660 658 };
661 659
662 660 // Cell movement
663 661
664 662 /**
665 663 * Move given (or selected) cell up and select it.
666 664 *
667 665 * @method move_cell_up
668 666 * @param [index] {integer} cell index
669 667 * @return {Notebook} This notebook
670 668 **/
671 669 Notebook.prototype.move_cell_up = function (index) {
672 670 var i = this.index_or_selected(index);
673 671 if (this.is_valid_cell_index(i) && i > 0) {
674 672 var pivot = this.get_cell_element(i-1);
675 673 var tomove = this.get_cell_element(i);
676 674 if (pivot !== null && tomove !== null) {
677 675 tomove.detach();
678 676 pivot.before(tomove);
679 677 this.select(i-1);
680 678 var cell = this.get_selected_cell();
681 679 cell.focus_cell();
682 680 }
683 681 this.set_dirty(true);
684 682 }
685 683 return this;
686 684 };
687 685
688 686
689 687 /**
690 688 * Move given (or selected) cell down and select it
691 689 *
692 690 * @method move_cell_down
693 691 * @param [index] {integer} cell index
694 692 * @return {Notebook} This notebook
695 693 **/
696 694 Notebook.prototype.move_cell_down = function (index) {
697 695 var i = this.index_or_selected(index);
698 696 if (this.is_valid_cell_index(i) && this.is_valid_cell_index(i+1)) {
699 697 var pivot = this.get_cell_element(i+1);
700 698 var tomove = this.get_cell_element(i);
701 699 if (pivot !== null && tomove !== null) {
702 700 tomove.detach();
703 701 pivot.after(tomove);
704 702 this.select(i+1);
705 703 var cell = this.get_selected_cell();
706 704 cell.focus_cell();
707 705 }
708 706 }
709 707 this.set_dirty();
710 708 return this;
711 709 };
712 710
713 711
714 712 // Insertion, deletion.
715 713
716 714 /**
717 715 * Delete a cell from the notebook.
718 716 *
719 717 * @method delete_cell
720 718 * @param [index] A cell's numeric index
721 719 * @return {Notebook} This notebook
722 720 */
723 721 Notebook.prototype.delete_cell = function (index) {
724 722 var i = this.index_or_selected(index);
725 723 var cell = this.get_selected_cell();
726 724 this.undelete_backup = cell.toJSON();
727 725 $('#undelete_cell').removeClass('disabled');
728 726 if (this.is_valid_cell_index(i)) {
729 727 var old_ncells = this.ncells();
730 728 var ce = this.get_cell_element(i);
731 729 ce.remove();
732 730 if (i === 0) {
733 731 // Always make sure we have at least one cell.
734 732 if (old_ncells === 1) {
735 733 this.insert_cell_below('code');
736 734 }
737 735 this.select(0);
738 736 this.undelete_index = 0;
739 737 this.undelete_below = false;
740 738 } else if (i === old_ncells-1 && i !== 0) {
741 739 this.select(i-1);
742 740 this.undelete_index = i - 1;
743 741 this.undelete_below = true;
744 742 } else {
745 743 this.select(i);
746 744 this.undelete_index = i;
747 745 this.undelete_below = false;
748 746 }
749 747 this.events.trigger('delete.Cell', {'cell': cell, 'index': i});
750 748 this.set_dirty(true);
751 749 }
752 750 return this;
753 751 };
754 752
755 753 /**
756 754 * Restore the most recently deleted cell.
757 755 *
758 756 * @method undelete
759 757 */
760 758 Notebook.prototype.undelete_cell = function() {
761 759 if (this.undelete_backup !== null && this.undelete_index !== null) {
762 760 var current_index = this.get_selected_index();
763 761 if (this.undelete_index < current_index) {
764 762 current_index = current_index + 1;
765 763 }
766 764 if (this.undelete_index >= this.ncells()) {
767 765 this.select(this.ncells() - 1);
768 766 }
769 767 else {
770 768 this.select(this.undelete_index);
771 769 }
772 770 var cell_data = this.undelete_backup;
773 771 var new_cell = null;
774 772 if (this.undelete_below) {
775 773 new_cell = this.insert_cell_below(cell_data.cell_type);
776 774 } else {
777 775 new_cell = this.insert_cell_above(cell_data.cell_type);
778 776 }
779 777 new_cell.fromJSON(cell_data);
780 778 if (this.undelete_below) {
781 779 this.select(current_index+1);
782 780 } else {
783 781 this.select(current_index);
784 782 }
785 783 this.undelete_backup = null;
786 784 this.undelete_index = null;
787 785 }
788 786 $('#undelete_cell').addClass('disabled');
789 787 };
790 788
791 789 /**
792 790 * Insert a cell so that after insertion the cell is at given index.
793 791 *
794 792 * If cell type is not provided, it will default to the type of the
795 793 * currently active cell.
796 794 *
797 795 * Similar to insert_above, but index parameter is mandatory
798 796 *
799 797 * Index will be brought back into the accessible range [0,n]
800 798 *
801 799 * @method insert_cell_at_index
802 800 * @param [type] {string} in ['code','markdown','heading'], defaults to 'code'
803 801 * @param [index] {int} a valid index where to insert cell
804 802 *
805 803 * @return cell {cell|null} created cell or null
806 804 **/
807 805 Notebook.prototype.insert_cell_at_index = function(type, index){
808 806
809 807 var ncells = this.ncells();
810 808 index = Math.min(index,ncells);
811 809 index = Math.max(index,0);
812 810 var cell = null;
813 811 type = type || this.get_selected_cell().cell_type;
814 812
815 813 if (ncells === 0 || this.is_valid_cell_index(index) || index === ncells) {
816 814 var cell_options = {
817 815 events: this.events,
818 816 config: this.config,
819 817 keyboard_manager: this.keyboard_manager,
820 818 notebook: this
821 819 };
822 820 if (type === 'code') {
823 821 cell = new codecell.CodeCell(this.kernel, cell_options);
824 822 cell.set_input_prompt();
825 823 } else if (type === 'markdown') {
826 824 cell = new textcell.MarkdownCell(cell_options);
827 825 } else if (type === 'raw') {
828 826 cell = new textcell.RawCell(cell_options);
829 827 } else if (type === 'heading') {
830 828 cell = new textcell.HeadingCell(cell_options);
831 829 }
832 830
833 831 if(this._insert_element_at_index(cell.element,index)) {
834 832 cell.render();
835 833 this.events.trigger('create.Cell', {'cell': cell, 'index': index});
836 834 cell.refresh();
837 835 // We used to select the cell after we refresh it, but there
838 836 // are now cases were this method is called where select is
839 837 // not appropriate. The selection logic should be handled by the
840 838 // caller of the the top level insert_cell methods.
841 839 this.set_dirty(true);
842 840 }
843 841 }
844 842 return cell;
845 843
846 844 };
847 845
848 846 /**
849 847 * Insert an element at given cell index.
850 848 *
851 849 * @method _insert_element_at_index
852 850 * @param element {dom element} a cell element
853 851 * @param [index] {int} a valid index where to inser cell
854 852 * @private
855 853 *
856 854 * return true if everything whent fine.
857 855 **/
858 856 Notebook.prototype._insert_element_at_index = function(element, index){
859 857 if (element === undefined){
860 858 return false;
861 859 }
862 860
863 861 var ncells = this.ncells();
864 862
865 863 if (ncells === 0) {
866 864 // special case append if empty
867 865 this.element.find('div.end_space').before(element);
868 866 } else if ( ncells === index ) {
869 867 // special case append it the end, but not empty
870 868 this.get_cell_element(index-1).after(element);
871 869 } else if (this.is_valid_cell_index(index)) {
872 870 // otherwise always somewhere to append to
873 871 this.get_cell_element(index).before(element);
874 872 } else {
875 873 return false;
876 874 }
877 875
878 876 if (this.undelete_index !== null && index <= this.undelete_index) {
879 877 this.undelete_index = this.undelete_index + 1;
880 878 this.set_dirty(true);
881 879 }
882 880 return true;
883 881 };
884 882
885 883 /**
886 884 * Insert a cell of given type above given index, or at top
887 885 * of notebook if index smaller than 0.
888 886 *
889 887 * default index value is the one of currently selected cell
890 888 *
891 889 * @method insert_cell_above
892 890 * @param [type] {string} cell type
893 891 * @param [index] {integer}
894 892 *
895 893 * @return handle to created cell or null
896 894 **/
897 895 Notebook.prototype.insert_cell_above = function (type, index) {
898 896 index = this.index_or_selected(index);
899 897 return this.insert_cell_at_index(type, index);
900 898 };
901 899
902 900 /**
903 901 * Insert a cell of given type below given index, or at bottom
904 902 * of notebook if index greater than number of cells
905 903 *
906 904 * default index value is the one of currently selected cell
907 905 *
908 906 * @method insert_cell_below
909 907 * @param [type] {string} cell type
910 908 * @param [index] {integer}
911 909 *
912 910 * @return handle to created cell or null
913 911 *
914 912 **/
915 913 Notebook.prototype.insert_cell_below = function (type, index) {
916 914 index = this.index_or_selected(index);
917 915 return this.insert_cell_at_index(type, index+1);
918 916 };
919 917
920 918
921 919 /**
922 920 * Insert cell at end of notebook
923 921 *
924 922 * @method insert_cell_at_bottom
925 923 * @param {String} type cell type
926 924 *
927 925 * @return the added cell; or null
928 926 **/
929 927 Notebook.prototype.insert_cell_at_bottom = function (type){
930 928 var len = this.ncells();
931 929 return this.insert_cell_below(type,len-1);
932 930 };
933 931
934 932 /**
935 933 * Turn a cell into a code cell.
936 934 *
937 935 * @method to_code
938 936 * @param {Number} [index] A cell's index
939 937 */
940 938 Notebook.prototype.to_code = function (index) {
941 939 var i = this.index_or_selected(index);
942 940 if (this.is_valid_cell_index(i)) {
943 941 var source_element = this.get_cell_element(i);
944 942 var source_cell = source_element.data("cell");
945 943 if (!(source_cell instanceof codecell.CodeCell)) {
946 944 var target_cell = this.insert_cell_below('code',i);
947 945 var text = source_cell.get_text();
948 946 if (text === source_cell.placeholder) {
949 947 text = '';
950 948 }
951 949 target_cell.set_text(text);
952 950 // make this value the starting point, so that we can only undo
953 951 // to this state, instead of a blank cell
954 952 target_cell.code_mirror.clearHistory();
955 953 source_element.remove();
956 954 this.select(i);
957 955 var cursor = source_cell.code_mirror.getCursor();
958 956 target_cell.code_mirror.setCursor(cursor);
959 957 this.set_dirty(true);
960 958 }
961 959 }
962 960 };
963 961
964 962 /**
965 963 * Turn a cell into a Markdown cell.
966 964 *
967 965 * @method to_markdown
968 966 * @param {Number} [index] A cell's index
969 967 */
970 968 Notebook.prototype.to_markdown = function (index) {
971 969 var i = this.index_or_selected(index);
972 970 if (this.is_valid_cell_index(i)) {
973 971 var source_element = this.get_cell_element(i);
974 972 var source_cell = source_element.data("cell");
975 973 if (!(source_cell instanceof textcell.MarkdownCell)) {
976 974 var target_cell = this.insert_cell_below('markdown',i);
977 975 var text = source_cell.get_text();
978 976 if (text === source_cell.placeholder) {
979 977 text = '';
980 978 }
981 979 // We must show the editor before setting its contents
982 980 target_cell.unrender();
983 981 target_cell.set_text(text);
984 982 // make this value the starting point, so that we can only undo
985 983 // to this state, instead of a blank cell
986 984 target_cell.code_mirror.clearHistory();
987 985 source_element.remove();
988 986 this.select(i);
989 987 if ((source_cell instanceof textcell.TextCell) && source_cell.rendered) {
990 988 target_cell.render();
991 989 }
992 990 var cursor = source_cell.code_mirror.getCursor();
993 991 target_cell.code_mirror.setCursor(cursor);
994 992 this.set_dirty(true);
995 993 }
996 994 }
997 995 };
998 996
999 997 /**
1000 998 * Turn a cell into a raw text cell.
1001 999 *
1002 1000 * @method to_raw
1003 1001 * @param {Number} [index] A cell's index
1004 1002 */
1005 1003 Notebook.prototype.to_raw = function (index) {
1006 1004 var i = this.index_or_selected(index);
1007 1005 if (this.is_valid_cell_index(i)) {
1008 1006 var source_element = this.get_cell_element(i);
1009 1007 var source_cell = source_element.data("cell");
1010 1008 var target_cell = null;
1011 1009 if (!(source_cell instanceof textcell.RawCell)) {
1012 1010 target_cell = this.insert_cell_below('raw',i);
1013 1011 var text = source_cell.get_text();
1014 1012 if (text === source_cell.placeholder) {
1015 1013 text = '';
1016 1014 }
1017 1015 // We must show the editor before setting its contents
1018 1016 target_cell.unrender();
1019 1017 target_cell.set_text(text);
1020 1018 // make this value the starting point, so that we can only undo
1021 1019 // to this state, instead of a blank cell
1022 1020 target_cell.code_mirror.clearHistory();
1023 1021 source_element.remove();
1024 1022 this.select(i);
1025 1023 var cursor = source_cell.code_mirror.getCursor();
1026 1024 target_cell.code_mirror.setCursor(cursor);
1027 1025 this.set_dirty(true);
1028 1026 }
1029 1027 }
1030 1028 };
1031 1029
1032 1030 /**
1033 1031 * Turn a cell into a heading cell.
1034 1032 *
1035 1033 * @method to_heading
1036 1034 * @param {Number} [index] A cell's index
1037 1035 * @param {Number} [level] A heading level (e.g., 1 becomes &lt;h1&gt;)
1038 1036 */
1039 1037 Notebook.prototype.to_heading = function (index, level) {
1040 1038 level = level || 1;
1041 1039 var i = this.index_or_selected(index);
1042 1040 if (this.is_valid_cell_index(i)) {
1043 1041 var source_element = this.get_cell_element(i);
1044 1042 var source_cell = source_element.data("cell");
1045 1043 var target_cell = null;
1046 1044 if (source_cell instanceof textcell.HeadingCell) {
1047 1045 source_cell.set_level(level);
1048 1046 } else {
1049 1047 target_cell = this.insert_cell_below('heading',i);
1050 1048 var text = source_cell.get_text();
1051 1049 if (text === source_cell.placeholder) {
1052 1050 text = '';
1053 1051 }
1054 1052 // We must show the editor before setting its contents
1055 1053 target_cell.set_level(level);
1056 1054 target_cell.unrender();
1057 1055 target_cell.set_text(text);
1058 1056 // make this value the starting point, so that we can only undo
1059 1057 // to this state, instead of a blank cell
1060 1058 target_cell.code_mirror.clearHistory();
1061 1059 source_element.remove();
1062 1060 this.select(i);
1063 1061 var cursor = source_cell.code_mirror.getCursor();
1064 1062 target_cell.code_mirror.setCursor(cursor);
1065 1063 if ((source_cell instanceof textcell.TextCell) && source_cell.rendered) {
1066 1064 target_cell.render();
1067 1065 }
1068 1066 }
1069 1067 this.set_dirty(true);
1070 1068 this.events.trigger('selected_cell_type_changed.Notebook',
1071 1069 {'cell_type':'heading',level:level}
1072 1070 );
1073 1071 }
1074 1072 };
1075 1073
1076 1074
1077 1075 // Cut/Copy/Paste
1078 1076
1079 1077 /**
1080 1078 * Enable UI elements for pasting cells.
1081 1079 *
1082 1080 * @method enable_paste
1083 1081 */
1084 1082 Notebook.prototype.enable_paste = function () {
1085 1083 var that = this;
1086 1084 if (!this.paste_enabled) {
1087 1085 $('#paste_cell_replace').removeClass('disabled')
1088 1086 .on('click', function () {that.paste_cell_replace();});
1089 1087 $('#paste_cell_above').removeClass('disabled')
1090 1088 .on('click', function () {that.paste_cell_above();});
1091 1089 $('#paste_cell_below').removeClass('disabled')
1092 1090 .on('click', function () {that.paste_cell_below();});
1093 1091 this.paste_enabled = true;
1094 1092 }
1095 1093 };
1096 1094
1097 1095 /**
1098 1096 * Disable UI elements for pasting cells.
1099 1097 *
1100 1098 * @method disable_paste
1101 1099 */
1102 1100 Notebook.prototype.disable_paste = function () {
1103 1101 if (this.paste_enabled) {
1104 1102 $('#paste_cell_replace').addClass('disabled').off('click');
1105 1103 $('#paste_cell_above').addClass('disabled').off('click');
1106 1104 $('#paste_cell_below').addClass('disabled').off('click');
1107 1105 this.paste_enabled = false;
1108 1106 }
1109 1107 };
1110 1108
1111 1109 /**
1112 1110 * Cut a cell.
1113 1111 *
1114 1112 * @method cut_cell
1115 1113 */
1116 1114 Notebook.prototype.cut_cell = function () {
1117 1115 this.copy_cell();
1118 1116 this.delete_cell();
1119 1117 };
1120 1118
1121 1119 /**
1122 1120 * Copy a cell.
1123 1121 *
1124 1122 * @method copy_cell
1125 1123 */
1126 1124 Notebook.prototype.copy_cell = function () {
1127 1125 var cell = this.get_selected_cell();
1128 1126 this.clipboard = cell.toJSON();
1129 1127 this.enable_paste();
1130 1128 };
1131 1129
1132 1130 /**
1133 1131 * Replace the selected cell with a cell in the clipboard.
1134 1132 *
1135 1133 * @method paste_cell_replace
1136 1134 */
1137 1135 Notebook.prototype.paste_cell_replace = function () {
1138 1136 if (this.clipboard !== null && this.paste_enabled) {
1139 1137 var cell_data = this.clipboard;
1140 1138 var new_cell = this.insert_cell_above(cell_data.cell_type);
1141 1139 new_cell.fromJSON(cell_data);
1142 1140 var old_cell = this.get_next_cell(new_cell);
1143 1141 this.delete_cell(this.find_cell_index(old_cell));
1144 1142 this.select(this.find_cell_index(new_cell));
1145 1143 }
1146 1144 };
1147 1145
1148 1146 /**
1149 1147 * Paste a cell from the clipboard above the selected cell.
1150 1148 *
1151 1149 * @method paste_cell_above
1152 1150 */
1153 1151 Notebook.prototype.paste_cell_above = function () {
1154 1152 if (this.clipboard !== null && this.paste_enabled) {
1155 1153 var cell_data = this.clipboard;
1156 1154 var new_cell = this.insert_cell_above(cell_data.cell_type);
1157 1155 new_cell.fromJSON(cell_data);
1158 1156 new_cell.focus_cell();
1159 1157 }
1160 1158 };
1161 1159
1162 1160 /**
1163 1161 * Paste a cell from the clipboard below the selected cell.
1164 1162 *
1165 1163 * @method paste_cell_below
1166 1164 */
1167 1165 Notebook.prototype.paste_cell_below = function () {
1168 1166 if (this.clipboard !== null && this.paste_enabled) {
1169 1167 var cell_data = this.clipboard;
1170 1168 var new_cell = this.insert_cell_below(cell_data.cell_type);
1171 1169 new_cell.fromJSON(cell_data);
1172 1170 new_cell.focus_cell();
1173 1171 }
1174 1172 };
1175 1173
1176 1174 // Split/merge
1177 1175
1178 1176 /**
1179 1177 * Split the selected cell into two, at the cursor.
1180 1178 *
1181 1179 * @method split_cell
1182 1180 */
1183 1181 Notebook.prototype.split_cell = function () {
1184 1182 var mdc = textcell.MarkdownCell;
1185 1183 var rc = textcell.RawCell;
1186 1184 var cell = this.get_selected_cell();
1187 1185 if (cell.is_splittable()) {
1188 1186 var texta = cell.get_pre_cursor();
1189 1187 var textb = cell.get_post_cursor();
1190 1188 if (cell instanceof codecell.CodeCell) {
1191 1189 // In this case the operations keep the notebook in its existing mode
1192 1190 // so we don't need to do any post-op mode changes.
1193 1191 cell.set_text(textb);
1194 1192 var new_cell = this.insert_cell_above('code');
1195 1193 new_cell.set_text(texta);
1196 1194 } else if ((cell instanceof mdc && !cell.rendered) || (cell instanceof rc)) {
1197 1195 // We know cell is !rendered so we can use set_text.
1198 1196 cell.set_text(textb);
1199 1197 var new_cell = this.insert_cell_above(cell.cell_type);
1200 1198 // Unrender the new cell so we can call set_text.
1201 1199 new_cell.unrender();
1202 1200 new_cell.set_text(texta);
1203 1201 }
1204 1202 }
1205 1203 };
1206 1204
1207 1205 /**
1208 1206 * Combine the selected cell into the cell above it.
1209 1207 *
1210 1208 * @method merge_cell_above
1211 1209 */
1212 1210 Notebook.prototype.merge_cell_above = function () {
1213 1211 var mdc = textcell.MarkdownCell;
1214 1212 var rc = textcell.RawCell;
1215 1213 var index = this.get_selected_index();
1216 1214 var cell = this.get_cell(index);
1217 1215 var render = cell.rendered;
1218 1216 if (!cell.is_mergeable()) {
1219 1217 return;
1220 1218 }
1221 1219 if (index > 0) {
1222 1220 var upper_cell = this.get_cell(index-1);
1223 1221 if (!upper_cell.is_mergeable()) {
1224 1222 return;
1225 1223 }
1226 1224 var upper_text = upper_cell.get_text();
1227 1225 var text = cell.get_text();
1228 1226 if (cell instanceof codecell.CodeCell) {
1229 1227 cell.set_text(upper_text+'\n'+text);
1230 1228 } else if ((cell instanceof mdc) || (cell instanceof rc)) {
1231 1229 cell.unrender(); // Must unrender before we set_text.
1232 1230 cell.set_text(upper_text+'\n\n'+text);
1233 1231 if (render) {
1234 1232 // The rendered state of the final cell should match
1235 1233 // that of the original selected cell;
1236 1234 cell.render();
1237 1235 }
1238 1236 }
1239 1237 this.delete_cell(index-1);
1240 1238 this.select(this.find_cell_index(cell));
1241 1239 }
1242 1240 };
1243 1241
1244 1242 /**
1245 1243 * Combine the selected cell into the cell below it.
1246 1244 *
1247 1245 * @method merge_cell_below
1248 1246 */
1249 1247 Notebook.prototype.merge_cell_below = function () {
1250 1248 var mdc = textcell.MarkdownCell;
1251 1249 var rc = textcell.RawCell;
1252 1250 var index = this.get_selected_index();
1253 1251 var cell = this.get_cell(index);
1254 1252 var render = cell.rendered;
1255 1253 if (!cell.is_mergeable()) {
1256 1254 return;
1257 1255 }
1258 1256 if (index < this.ncells()-1) {
1259 1257 var lower_cell = this.get_cell(index+1);
1260 1258 if (!lower_cell.is_mergeable()) {
1261 1259 return;
1262 1260 }
1263 1261 var lower_text = lower_cell.get_text();
1264 1262 var text = cell.get_text();
1265 1263 if (cell instanceof codecell.CodeCell) {
1266 1264 cell.set_text(text+'\n'+lower_text);
1267 1265 } else if ((cell instanceof mdc) || (cell instanceof rc)) {
1268 1266 cell.unrender(); // Must unrender before we set_text.
1269 1267 cell.set_text(text+'\n\n'+lower_text);
1270 1268 if (render) {
1271 1269 // The rendered state of the final cell should match
1272 1270 // that of the original selected cell;
1273 1271 cell.render();
1274 1272 }
1275 1273 }
1276 1274 this.delete_cell(index+1);
1277 1275 this.select(this.find_cell_index(cell));
1278 1276 }
1279 1277 };
1280 1278
1281 1279
1282 1280 // Cell collapsing and output clearing
1283 1281
1284 1282 /**
1285 1283 * Hide a cell's output.
1286 1284 *
1287 1285 * @method collapse_output
1288 1286 * @param {Number} index A cell's numeric index
1289 1287 */
1290 1288 Notebook.prototype.collapse_output = function (index) {
1291 1289 var i = this.index_or_selected(index);
1292 1290 var cell = this.get_cell(i);
1293 1291 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1294 1292 cell.collapse_output();
1295 1293 this.set_dirty(true);
1296 1294 }
1297 1295 };
1298 1296
1299 1297 /**
1300 1298 * Hide each code cell's output area.
1301 1299 *
1302 1300 * @method collapse_all_output
1303 1301 */
1304 1302 Notebook.prototype.collapse_all_output = function () {
1305 1303 $.map(this.get_cells(), function (cell, i) {
1306 1304 if (cell instanceof codecell.CodeCell) {
1307 1305 cell.collapse_output();
1308 1306 }
1309 1307 });
1310 1308 // this should not be set if the `collapse` key is removed from nbformat
1311 1309 this.set_dirty(true);
1312 1310 };
1313 1311
1314 1312 /**
1315 1313 * Show a cell's output.
1316 1314 *
1317 1315 * @method expand_output
1318 1316 * @param {Number} index A cell's numeric index
1319 1317 */
1320 1318 Notebook.prototype.expand_output = function (index) {
1321 1319 var i = this.index_or_selected(index);
1322 1320 var cell = this.get_cell(i);
1323 1321 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1324 1322 cell.expand_output();
1325 1323 this.set_dirty(true);
1326 1324 }
1327 1325 };
1328 1326
1329 1327 /**
1330 1328 * Expand each code cell's output area, and remove scrollbars.
1331 1329 *
1332 1330 * @method expand_all_output
1333 1331 */
1334 1332 Notebook.prototype.expand_all_output = function () {
1335 1333 $.map(this.get_cells(), function (cell, i) {
1336 1334 if (cell instanceof codecell.CodeCell) {
1337 1335 cell.expand_output();
1338 1336 }
1339 1337 });
1340 1338 // this should not be set if the `collapse` key is removed from nbformat
1341 1339 this.set_dirty(true);
1342 1340 };
1343 1341
1344 1342 /**
1345 1343 * Clear the selected CodeCell's output area.
1346 1344 *
1347 1345 * @method clear_output
1348 1346 * @param {Number} index A cell's numeric index
1349 1347 */
1350 1348 Notebook.prototype.clear_output = function (index) {
1351 1349 var i = this.index_or_selected(index);
1352 1350 var cell = this.get_cell(i);
1353 1351 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1354 1352 cell.clear_output();
1355 1353 this.set_dirty(true);
1356 1354 }
1357 1355 };
1358 1356
1359 1357 /**
1360 1358 * Clear each code cell's output area.
1361 1359 *
1362 1360 * @method clear_all_output
1363 1361 */
1364 1362 Notebook.prototype.clear_all_output = function () {
1365 1363 $.map(this.get_cells(), function (cell, i) {
1366 1364 if (cell instanceof codecell.CodeCell) {
1367 1365 cell.clear_output();
1368 1366 }
1369 1367 });
1370 1368 this.set_dirty(true);
1371 1369 };
1372 1370
1373 1371 /**
1374 1372 * Scroll the selected CodeCell's output area.
1375 1373 *
1376 1374 * @method scroll_output
1377 1375 * @param {Number} index A cell's numeric index
1378 1376 */
1379 1377 Notebook.prototype.scroll_output = function (index) {
1380 1378 var i = this.index_or_selected(index);
1381 1379 var cell = this.get_cell(i);
1382 1380 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1383 1381 cell.scroll_output();
1384 1382 this.set_dirty(true);
1385 1383 }
1386 1384 };
1387 1385
1388 1386 /**
1389 1387 * Expand each code cell's output area, and add a scrollbar for long output.
1390 1388 *
1391 1389 * @method scroll_all_output
1392 1390 */
1393 1391 Notebook.prototype.scroll_all_output = function () {
1394 1392 $.map(this.get_cells(), function (cell, i) {
1395 1393 if (cell instanceof codecell.CodeCell) {
1396 1394 cell.scroll_output();
1397 1395 }
1398 1396 });
1399 1397 // this should not be set if the `collapse` key is removed from nbformat
1400 1398 this.set_dirty(true);
1401 1399 };
1402 1400
1403 1401 /** Toggle whether a cell's output is collapsed or expanded.
1404 1402 *
1405 1403 * @method toggle_output
1406 1404 * @param {Number} index A cell's numeric index
1407 1405 */
1408 1406 Notebook.prototype.toggle_output = function (index) {
1409 1407 var i = this.index_or_selected(index);
1410 1408 var cell = this.get_cell(i);
1411 1409 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1412 1410 cell.toggle_output();
1413 1411 this.set_dirty(true);
1414 1412 }
1415 1413 };
1416 1414
1417 1415 /**
1418 1416 * Hide/show the output of all cells.
1419 1417 *
1420 1418 * @method toggle_all_output
1421 1419 */
1422 1420 Notebook.prototype.toggle_all_output = function () {
1423 1421 $.map(this.get_cells(), function (cell, i) {
1424 1422 if (cell instanceof codecell.CodeCell) {
1425 1423 cell.toggle_output();
1426 1424 }
1427 1425 });
1428 1426 // this should not be set if the `collapse` key is removed from nbformat
1429 1427 this.set_dirty(true);
1430 1428 };
1431 1429
1432 1430 /**
1433 1431 * Toggle a scrollbar for long cell outputs.
1434 1432 *
1435 1433 * @method toggle_output_scroll
1436 1434 * @param {Number} index A cell's numeric index
1437 1435 */
1438 1436 Notebook.prototype.toggle_output_scroll = function (index) {
1439 1437 var i = this.index_or_selected(index);
1440 1438 var cell = this.get_cell(i);
1441 1439 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1442 1440 cell.toggle_output_scroll();
1443 1441 this.set_dirty(true);
1444 1442 }
1445 1443 };
1446 1444
1447 1445 /**
1448 1446 * Toggle the scrolling of long output on all cells.
1449 1447 *
1450 1448 * @method toggle_all_output_scrolling
1451 1449 */
1452 1450 Notebook.prototype.toggle_all_output_scroll = function () {
1453 1451 $.map(this.get_cells(), function (cell, i) {
1454 1452 if (cell instanceof codecell.CodeCell) {
1455 1453 cell.toggle_output_scroll();
1456 1454 }
1457 1455 });
1458 1456 // this should not be set if the `collapse` key is removed from nbformat
1459 1457 this.set_dirty(true);
1460 1458 };
1461 1459
1462 1460 // Other cell functions: line numbers, ...
1463 1461
1464 1462 /**
1465 1463 * Toggle line numbers in the selected cell's input area.
1466 1464 *
1467 1465 * @method cell_toggle_line_numbers
1468 1466 */
1469 1467 Notebook.prototype.cell_toggle_line_numbers = function() {
1470 1468 this.get_selected_cell().toggle_line_numbers();
1471 1469 };
1472 1470
1473 1471 // Session related things
1474 1472
1475 1473 /**
1476 1474 * Start a new session and set it on each code cell.
1477 1475 *
1478 1476 * @method start_session
1479 1477 */
1480 1478 Notebook.prototype.start_session = function () {
1481 1479 this.session = new session.Session({
1482 1480 base_url: this.base_url,
1483 1481 notebook_path: this.notebook_path,
1484 1482 notebook_name: this.notebook_name,
1485 1483 notebook: this});
1486 1484 this.session.start($.proxy(this._session_started, this));
1487 1485 };
1488 1486
1489 1487
1490 1488 /**
1491 1489 * Once a session is started, link the code cells to the kernel and pass the
1492 1490 * comm manager to the widget manager
1493 1491 *
1494 1492 */
1495 1493 Notebook.prototype._session_started = function(){
1496 1494 this.kernel = this.session.kernel;
1497 1495 var ncells = this.ncells();
1498 1496 for (var i=0; i<ncells; i++) {
1499 1497 var cell = this.get_cell(i);
1500 1498 if (cell instanceof codecell.CodeCell) {
1501 1499 cell.set_kernel(this.session.kernel);
1502 1500 }
1503 1501 }
1504 1502 };
1505 1503
1506 1504 /**
1507 1505 * Prompt the user to restart the IPython kernel.
1508 1506 *
1509 1507 * @method restart_kernel
1510 1508 */
1511 1509 Notebook.prototype.restart_kernel = function () {
1512 1510 var that = this;
1513 1511 dialog.modal({
1514 1512 notebook: this,
1515 1513 keyboard_manager: this.keyboard_manager,
1516 1514 title : "Restart kernel or continue running?",
1517 1515 body : $("<p/>").text(
1518 1516 'Do you want to restart the current kernel? You will lose all variables defined in it.'
1519 1517 ),
1520 1518 buttons : {
1521 1519 "Continue running" : {},
1522 1520 "Restart" : {
1523 1521 "class" : "btn-danger",
1524 1522 "click" : function() {
1525 1523 that.session.restart_kernel();
1526 1524 }
1527 1525 }
1528 1526 }
1529 1527 });
1530 1528 };
1531 1529
1532 1530 /**
1533 1531 * Execute or render cell outputs and go into command mode.
1534 1532 *
1535 1533 * @method execute_cell
1536 1534 */
1537 1535 Notebook.prototype.execute_cell = function () {
1538 1536 // mode = shift, ctrl, alt
1539 1537 var cell = this.get_selected_cell();
1540 1538 var cell_index = this.find_cell_index(cell);
1541 1539
1542 1540 cell.execute();
1543 1541 this.command_mode();
1544 1542 this.set_dirty(true);
1545 1543 };
1546 1544
1547 1545 /**
1548 1546 * Execute or render cell outputs and insert a new cell below.
1549 1547 *
1550 1548 * @method execute_cell_and_insert_below
1551 1549 */
1552 1550 Notebook.prototype.execute_cell_and_insert_below = function () {
1553 1551 var cell = this.get_selected_cell();
1554 1552 var cell_index = this.find_cell_index(cell);
1555 1553
1556 1554 cell.execute();
1557 1555
1558 1556 // If we are at the end always insert a new cell and return
1559 1557 if (cell_index === (this.ncells()-1)) {
1560 1558 this.command_mode();
1561 1559 this.insert_cell_below();
1562 1560 this.select(cell_index+1);
1563 1561 this.edit_mode();
1564 1562 this.scroll_to_bottom();
1565 1563 this.set_dirty(true);
1566 1564 return;
1567 1565 }
1568 1566
1569 1567 this.command_mode();
1570 1568 this.insert_cell_below();
1571 1569 this.select(cell_index+1);
1572 1570 this.edit_mode();
1573 1571 this.set_dirty(true);
1574 1572 };
1575 1573
1576 1574 /**
1577 1575 * Execute or render cell outputs and select the next cell.
1578 1576 *
1579 1577 * @method execute_cell_and_select_below
1580 1578 */
1581 1579 Notebook.prototype.execute_cell_and_select_below = function () {
1582 1580
1583 1581 var cell = this.get_selected_cell();
1584 1582 var cell_index = this.find_cell_index(cell);
1585 1583
1586 1584 cell.execute();
1587 1585
1588 1586 // If we are at the end always insert a new cell and return
1589 1587 if (cell_index === (this.ncells()-1)) {
1590 1588 this.command_mode();
1591 1589 this.insert_cell_below();
1592 1590 this.select(cell_index+1);
1593 1591 this.edit_mode();
1594 1592 this.scroll_to_bottom();
1595 1593 this.set_dirty(true);
1596 1594 return;
1597 1595 }
1598 1596
1599 1597 this.command_mode();
1600 1598 this.select(cell_index+1);
1601 1599 this.focus_cell();
1602 1600 this.set_dirty(true);
1603 1601 };
1604 1602
1605 1603 /**
1606 1604 * Execute all cells below the selected cell.
1607 1605 *
1608 1606 * @method execute_cells_below
1609 1607 */
1610 1608 Notebook.prototype.execute_cells_below = function () {
1611 1609 this.execute_cell_range(this.get_selected_index(), this.ncells());
1612 1610 this.scroll_to_bottom();
1613 1611 };
1614 1612
1615 1613 /**
1616 1614 * Execute all cells above the selected cell.
1617 1615 *
1618 1616 * @method execute_cells_above
1619 1617 */
1620 1618 Notebook.prototype.execute_cells_above = function () {
1621 1619 this.execute_cell_range(0, this.get_selected_index());
1622 1620 };
1623 1621
1624 1622 /**
1625 1623 * Execute all cells.
1626 1624 *
1627 1625 * @method execute_all_cells
1628 1626 */
1629 1627 Notebook.prototype.execute_all_cells = function () {
1630 1628 this.execute_cell_range(0, this.ncells());
1631 1629 this.scroll_to_bottom();
1632 1630 };
1633 1631
1634 1632 /**
1635 1633 * Execute a contiguous range of cells.
1636 1634 *
1637 1635 * @method execute_cell_range
1638 1636 * @param {Number} start Index of the first cell to execute (inclusive)
1639 1637 * @param {Number} end Index of the last cell to execute (exclusive)
1640 1638 */
1641 1639 Notebook.prototype.execute_cell_range = function (start, end) {
1642 1640 this.command_mode();
1643 1641 for (var i=start; i<end; i++) {
1644 1642 this.select(i);
1645 1643 this.execute_cell();
1646 1644 }
1647 1645 };
1648 1646
1649 1647 // Persistance and loading
1650 1648
1651 1649 /**
1652 1650 * Getter method for this notebook's name.
1653 1651 *
1654 1652 * @method get_notebook_name
1655 1653 * @return {String} This notebook's name (excluding file extension)
1656 1654 */
1657 1655 Notebook.prototype.get_notebook_name = function () {
1658 1656 var nbname = this.notebook_name.substring(0,this.notebook_name.length-6);
1659 1657 return nbname;
1660 1658 };
1661 1659
1662 1660 /**
1663 1661 * Setter method for this notebook's name.
1664 1662 *
1665 1663 * @method set_notebook_name
1666 1664 * @param {String} name A new name for this notebook
1667 1665 */
1668 1666 Notebook.prototype.set_notebook_name = function (name) {
1669 1667 this.notebook_name = name;
1670 1668 };
1671 1669
1672 1670 /**
1673 1671 * Check that a notebook's name is valid.
1674 1672 *
1675 1673 * @method test_notebook_name
1676 1674 * @param {String} nbname A name for this notebook
1677 1675 * @return {Boolean} True if the name is valid, false if invalid
1678 1676 */
1679 1677 Notebook.prototype.test_notebook_name = function (nbname) {
1680 1678 nbname = nbname || '';
1681 1679 if (nbname.length>0 && !this.notebook_name_blacklist_re.test(nbname)) {
1682 1680 return true;
1683 1681 } else {
1684 1682 return false;
1685 1683 }
1686 1684 };
1687 1685
1688 1686 /**
1689 1687 * Load a notebook from JSON (.ipynb).
1690 1688 *
1691 1689 * This currently handles one worksheet: others are deleted.
1692 1690 *
1693 1691 * @method fromJSON
1694 1692 * @param {Object} data JSON representation of a notebook
1695 1693 */
1696 1694 Notebook.prototype.fromJSON = function (data) {
1697 1695 var content = data.content;
1698 1696 var ncells = this.ncells();
1699 1697 var i;
1700 1698 for (i=0; i<ncells; i++) {
1701 1699 // Always delete cell 0 as they get renumbered as they are deleted.
1702 1700 this.delete_cell(0);
1703 1701 }
1704 1702 // Save the metadata and name.
1705 1703 this.metadata = content.metadata;
1706 1704 this.notebook_name = data.name;
1707 1705 var trusted = true;
1708 1706 // Only handle 1 worksheet for now.
1709 1707 var worksheet = content.worksheets[0];
1710 1708 if (worksheet !== undefined) {
1711 1709 if (worksheet.metadata) {
1712 1710 this.worksheet_metadata = worksheet.metadata;
1713 1711 }
1714 1712 var new_cells = worksheet.cells;
1715 1713 ncells = new_cells.length;
1716 1714 var cell_data = null;
1717 1715 var new_cell = null;
1718 1716 for (i=0; i<ncells; i++) {
1719 1717 cell_data = new_cells[i];
1720 1718 // VERSIONHACK: plaintext -> raw
1721 1719 // handle never-released plaintext name for raw cells
1722 1720 if (cell_data.cell_type === 'plaintext'){
1723 1721 cell_data.cell_type = 'raw';
1724 1722 }
1725 1723
1726 1724 new_cell = this.insert_cell_at_index(cell_data.cell_type, i);
1727 1725 new_cell.fromJSON(cell_data);
1728 1726 if (new_cell.cell_type == 'code' && !new_cell.output_area.trusted) {
1729 1727 trusted = false;
1730 1728 }
1731 1729 }
1732 1730 }
1733 1731 if (trusted != this.trusted) {
1734 1732 this.trusted = trusted;
1735 1733 this.events.trigger("trust_changed.Notebook", trusted);
1736 1734 }
1737 1735 if (content.worksheets.length > 1) {
1738 1736 dialog.modal({
1739 1737 notebook: this,
1740 1738 keyboard_manager: this.keyboard_manager,
1741 1739 title : "Multiple worksheets",
1742 1740 body : "This notebook has " + data.worksheets.length + " worksheets, " +
1743 1741 "but this version of IPython can only handle the first. " +
1744 1742 "If you save this notebook, worksheets after the first will be lost.",
1745 1743 buttons : {
1746 1744 OK : {
1747 1745 class : "btn-danger"
1748 1746 }
1749 1747 }
1750 1748 });
1751 1749 }
1752 1750 };
1753 1751
1754 1752 /**
1755 1753 * Dump this notebook into a JSON-friendly object.
1756 1754 *
1757 1755 * @method toJSON
1758 1756 * @return {Object} A JSON-friendly representation of this notebook.
1759 1757 */
1760 1758 Notebook.prototype.toJSON = function () {
1761 1759 var cells = this.get_cells();
1762 1760 var ncells = cells.length;
1763 1761 var cell_array = new Array(ncells);
1764 1762 var trusted = true;
1765 1763 for (var i=0; i<ncells; i++) {
1766 1764 var cell = cells[i];
1767 1765 if (cell.cell_type == 'code' && !cell.output_area.trusted) {
1768 1766 trusted = false;
1769 1767 }
1770 1768 cell_array[i] = cell.toJSON();
1771 1769 }
1772 1770 var data = {
1773 1771 // Only handle 1 worksheet for now.
1774 1772 worksheets : [{
1775 1773 cells: cell_array,
1776 1774 metadata: this.worksheet_metadata
1777 1775 }],
1778 1776 metadata : this.metadata
1779 1777 };
1780 1778 if (trusted != this.trusted) {
1781 1779 this.trusted = trusted;
1782 1780 this.events.trigger("trust_changed.Notebook", trusted);
1783 1781 }
1784 1782 return data;
1785 1783 };
1786 1784
1787 1785 /**
1788 1786 * Start an autosave timer, for periodically saving the notebook.
1789 1787 *
1790 1788 * @method set_autosave_interval
1791 1789 * @param {Integer} interval the autosave interval in milliseconds
1792 1790 */
1793 1791 Notebook.prototype.set_autosave_interval = function (interval) {
1794 1792 var that = this;
1795 1793 // clear previous interval, so we don't get simultaneous timers
1796 1794 if (this.autosave_timer) {
1797 1795 clearInterval(this.autosave_timer);
1798 1796 }
1799 1797
1800 1798 this.autosave_interval = this.minimum_autosave_interval = interval;
1801 1799 if (interval) {
1802 1800 this.autosave_timer = setInterval(function() {
1803 1801 if (that.dirty) {
1804 1802 that.save_notebook();
1805 1803 }
1806 1804 }, interval);
1807 1805 this.events.trigger("autosave_enabled.Notebook", interval);
1808 1806 } else {
1809 1807 this.autosave_timer = null;
1810 1808 this.events.trigger("autosave_disabled.Notebook");
1811 1809 }
1812 1810 };
1813 1811
1814 1812 /**
1815 1813 * Save this notebook on the server. This becomes a notebook instance's
1816 1814 * .save_notebook method *after* the entire notebook has been loaded.
1817 1815 *
1818 1816 * @method save_notebook
1819 1817 */
1820 1818 Notebook.prototype.save_notebook = function (extra_settings) {
1821 1819 // Create a JSON model to be sent to the server.
1822 1820 var model = {};
1823 1821 model.name = this.notebook_name;
1824 1822 model.path = this.notebook_path;
1825 1823 model.content = this.toJSON();
1826 1824 model.content.nbformat = this.nbformat;
1827 1825 model.content.nbformat_minor = this.nbformat_minor;
1828 1826 // time the ajax call for autosave tuning purposes.
1829 1827 var start = new Date().getTime();
1830 1828 // We do the call with settings so we can set cache to false.
1831 1829 var settings = {
1832 1830 processData : false,
1833 1831 cache : false,
1834 1832 type : "PUT",
1835 1833 data : JSON.stringify(model),
1836 1834 headers : {'Content-Type': 'application/json'},
1837 1835 success : $.proxy(this.save_notebook_success, this, start),
1838 1836 error : $.proxy(this.save_notebook_error, this)
1839 1837 };
1840 1838 if (extra_settings) {
1841 1839 for (var key in extra_settings) {
1842 1840 settings[key] = extra_settings[key];
1843 1841 }
1844 1842 }
1845 1843 this.events.trigger('notebook_saving.Notebook');
1846 1844 var url = utils.url_join_encode(
1847 1845 this.base_url,
1848 1846 'api/notebooks',
1849 1847 this.notebook_path,
1850 1848 this.notebook_name
1851 1849 );
1852 1850 $.ajax(url, settings);
1853 1851 };
1854 1852
1855 1853 /**
1856 1854 * Success callback for saving a notebook.
1857 1855 *
1858 1856 * @method save_notebook_success
1859 1857 * @param {Integer} start the time when the save request started
1860 1858 * @param {Object} data JSON representation of a notebook
1861 1859 * @param {String} status Description of response status
1862 1860 * @param {jqXHR} xhr jQuery Ajax object
1863 1861 */
1864 1862 Notebook.prototype.save_notebook_success = function (start, data, status, xhr) {
1865 1863 this.set_dirty(false);
1866 1864 this.events.trigger('notebook_saved.Notebook');
1867 1865 this._update_autosave_interval(start);
1868 1866 if (this._checkpoint_after_save) {
1869 1867 this.create_checkpoint();
1870 1868 this._checkpoint_after_save = false;
1871 1869 }
1872 1870 };
1873 1871
1874 1872 /**
1875 1873 * update the autosave interval based on how long the last save took
1876 1874 *
1877 1875 * @method _update_autosave_interval
1878 1876 * @param {Integer} timestamp when the save request started
1879 1877 */
1880 1878 Notebook.prototype._update_autosave_interval = function (start) {
1881 1879 var duration = (new Date().getTime() - start);
1882 1880 if (this.autosave_interval) {
1883 1881 // new save interval: higher of 10x save duration or parameter (default 30 seconds)
1884 1882 var interval = Math.max(10 * duration, this.minimum_autosave_interval);
1885 1883 // round to 10 seconds, otherwise we will be setting a new interval too often
1886 1884 interval = 10000 * Math.round(interval / 10000);
1887 1885 // set new interval, if it's changed
1888 1886 if (interval != this.autosave_interval) {
1889 1887 this.set_autosave_interval(interval);
1890 1888 }
1891 1889 }
1892 1890 };
1893 1891
1894 1892 /**
1895 1893 * Failure callback for saving a notebook.
1896 1894 *
1897 1895 * @method save_notebook_error
1898 1896 * @param {jqXHR} xhr jQuery Ajax object
1899 1897 * @param {String} status Description of response status
1900 1898 * @param {String} error HTTP error message
1901 1899 */
1902 1900 Notebook.prototype.save_notebook_error = function (xhr, status, error) {
1903 1901 this.events.trigger('notebook_save_failed.Notebook', [xhr, status, error]);
1904 1902 };
1905 1903
1906 1904 /**
1907 1905 * Explicitly trust the output of this notebook.
1908 1906 *
1909 1907 * @method trust_notebook
1910 1908 */
1911 1909 Notebook.prototype.trust_notebook = function (extra_settings) {
1912 1910 var body = $("<div>").append($("<p>")
1913 1911 .text("A trusted IPython notebook may execute hidden malicious code ")
1914 1912 .append($("<strong>")
1915 1913 .append(
1916 1914 $("<em>").text("when you open it")
1917 1915 )
1918 1916 ).append(".").append(
1919 1917 " Selecting trust will immediately reload this notebook in a trusted state."
1920 1918 ).append(
1921 1919 " For more information, see the "
1922 1920 ).append($("<a>").attr("href", "http://ipython.org/ipython-doc/2/notebook/security.html")
1923 1921 .text("IPython security documentation")
1924 1922 ).append(".")
1925 1923 );
1926 1924
1927 1925 var nb = this;
1928 1926 dialog.modal({
1929 1927 notebook: this,
1930 1928 keyboard_manager: this.keyboard_manager,
1931 1929 title: "Trust this notebook?",
1932 1930 body: body,
1933 1931
1934 1932 buttons: {
1935 1933 Cancel : {},
1936 1934 Trust : {
1937 1935 class : "btn-danger",
1938 1936 click : function () {
1939 1937 var cells = nb.get_cells();
1940 1938 for (var i = 0; i < cells.length; i++) {
1941 1939 var cell = cells[i];
1942 1940 if (cell.cell_type == 'code') {
1943 1941 cell.output_area.trusted = true;
1944 1942 }
1945 1943 }
1946 1944 this.events.on('notebook_saved.Notebook', function () {
1947 1945 window.location.reload();
1948 1946 });
1949 1947 nb.save_notebook();
1950 1948 }
1951 1949 }
1952 1950 }
1953 1951 });
1954 1952 };
1955 1953
1956 1954 Notebook.prototype.new_notebook = function(){
1957 1955 var path = this.notebook_path;
1958 1956 var base_url = this.base_url;
1959 1957 var settings = {
1960 1958 processData : false,
1961 1959 cache : false,
1962 1960 type : "POST",
1963 1961 dataType : "json",
1964 1962 async : false,
1965 1963 success : function (data, status, xhr){
1966 1964 var notebook_name = data.name;
1967 1965 window.open(
1968 1966 utils.url_join_encode(
1969 1967 base_url,
1970 1968 'notebooks',
1971 1969 path,
1972 1970 notebook_name
1973 1971 ),
1974 1972 '_blank'
1975 1973 );
1976 1974 },
1977 1975 error : utils.log_ajax_error,
1978 1976 };
1979 1977 var url = utils.url_join_encode(
1980 1978 base_url,
1981 1979 'api/notebooks',
1982 1980 path
1983 1981 );
1984 1982 $.ajax(url,settings);
1985 1983 };
1986 1984
1987 1985
1988 1986 Notebook.prototype.copy_notebook = function(){
1989 1987 var path = this.notebook_path;
1990 1988 var base_url = this.base_url;
1991 1989 var settings = {
1992 1990 processData : false,
1993 1991 cache : false,
1994 1992 type : "POST",
1995 1993 dataType : "json",
1996 1994 data : JSON.stringify({copy_from : this.notebook_name}),
1997 1995 async : false,
1998 1996 success : function (data, status, xhr) {
1999 1997 window.open(utils.url_join_encode(
2000 1998 base_url,
2001 1999 'notebooks',
2002 2000 data.path,
2003 2001 data.name
2004 2002 ), '_blank');
2005 2003 },
2006 2004 error : utils.log_ajax_error,
2007 2005 };
2008 2006 var url = utils.url_join_encode(
2009 2007 base_url,
2010 2008 'api/notebooks',
2011 2009 path
2012 2010 );
2013 2011 $.ajax(url,settings);
2014 2012 };
2015 2013
2016 2014 Notebook.prototype.rename = function (nbname) {
2017 2015 var that = this;
2018 2016 if (!nbname.match(/\.ipynb$/)) {
2019 2017 nbname = nbname + ".ipynb";
2020 2018 }
2021 2019 var data = {name: nbname};
2022 2020 var settings = {
2023 2021 processData : false,
2024 2022 cache : false,
2025 2023 type : "PATCH",
2026 2024 data : JSON.stringify(data),
2027 2025 dataType: "json",
2028 2026 headers : {'Content-Type': 'application/json'},
2029 2027 success : $.proxy(that.rename_success, this),
2030 2028 error : $.proxy(that.rename_error, this)
2031 2029 };
2032 2030 this.events.trigger('rename_notebook.Notebook', data);
2033 2031 var url = utils.url_join_encode(
2034 2032 this.base_url,
2035 2033 'api/notebooks',
2036 2034 this.notebook_path,
2037 2035 this.notebook_name
2038 2036 );
2039 2037 $.ajax(url, settings);
2040 2038 };
2041 2039
2042 2040 Notebook.prototype.delete = function () {
2043 2041 var that = this;
2044 2042 var settings = {
2045 2043 processData : false,
2046 2044 cache : false,
2047 2045 type : "DELETE",
2048 2046 dataType: "json",
2049 2047 error : utils.log_ajax_error,
2050 2048 };
2051 2049 var url = utils.url_join_encode(
2052 2050 this.base_url,
2053 2051 'api/notebooks',
2054 2052 this.notebook_path,
2055 2053 this.notebook_name
2056 2054 );
2057 2055 $.ajax(url, settings);
2058 2056 };
2059 2057
2060 2058
2061 2059 Notebook.prototype.rename_success = function (json, status, xhr) {
2062 2060 var name = this.notebook_name = json.name;
2063 2061 var path = json.path;
2064 2062 this.session.rename_notebook(name, path);
2065 2063 this.events.trigger('notebook_renamed.Notebook', json);
2066 2064 };
2067 2065
2068 2066 Notebook.prototype.rename_error = function (xhr, status, error) {
2069 2067 var that = this;
2070 2068 var dialog_body = $('<div/>').append(
2071 2069 $("<p/>").addClass("rename-message")
2072 2070 .text('This notebook name already exists.')
2073 2071 );
2074 2072 this.events.trigger('notebook_rename_failed.Notebook', [xhr, status, error]);
2075 2073 dialog.modal({
2076 2074 notebook: this,
2077 2075 keyboard_manager: this.keyboard_manager,
2078 2076 title: "Notebook Rename Error!",
2079 2077 body: dialog_body,
2080 2078 buttons : {
2081 2079 "Cancel": {},
2082 2080 "OK": {
2083 2081 class: "btn-primary",
2084 2082 click: function () {
2085 2083 this.save_widget.rename_notebook({notebook:that});
2086 2084 }}
2087 2085 },
2088 2086 open : function (event, ui) {
2089 2087 var that = $(this);
2090 2088 // Upon ENTER, click the OK button.
2091 2089 that.find('input[type="text"]').keydown(function (event, ui) {
2092 2090 if (event.which === this.keyboard.keycodes.enter) {
2093 2091 that.find('.btn-primary').first().click();
2094 2092 }
2095 2093 });
2096 2094 that.find('input[type="text"]').focus();
2097 2095 }
2098 2096 });
2099 2097 };
2100 2098
2101 2099 /**
2102 2100 * Request a notebook's data from the server.
2103 2101 *
2104 2102 * @method load_notebook
2105 2103 * @param {String} notebook_name and path A notebook to load
2106 2104 */
2107 2105 Notebook.prototype.load_notebook = function (notebook_name, notebook_path) {
2108 2106 var that = this;
2109 2107 this.notebook_name = notebook_name;
2110 2108 this.notebook_path = notebook_path;
2111 2109 // We do the call with settings so we can set cache to false.
2112 2110 var settings = {
2113 2111 processData : false,
2114 2112 cache : false,
2115 2113 type : "GET",
2116 2114 dataType : "json",
2117 2115 success : $.proxy(this.load_notebook_success,this),
2118 2116 error : $.proxy(this.load_notebook_error,this),
2119 2117 };
2120 2118 this.events.trigger('notebook_loading.Notebook');
2121 2119 var url = utils.url_join_encode(
2122 2120 this.base_url,
2123 2121 'api/notebooks',
2124 2122 this.notebook_path,
2125 2123 this.notebook_name
2126 2124 );
2127 2125 $.ajax(url, settings);
2128 2126 };
2129 2127
2130 2128 /**
2131 2129 * Success callback for loading a notebook from the server.
2132 2130 *
2133 2131 * Load notebook data from the JSON response.
2134 2132 *
2135 2133 * @method load_notebook_success
2136 2134 * @param {Object} data JSON representation of a notebook
2137 2135 * @param {String} status Description of response status
2138 2136 * @param {jqXHR} xhr jQuery Ajax object
2139 2137 */
2140 2138 Notebook.prototype.load_notebook_success = function (data, status, xhr) {
2141 2139 this.fromJSON(data);
2142 2140 if (this.ncells() === 0) {
2143 2141 this.insert_cell_below('code');
2144 2142 this.edit_mode(0);
2145 2143 } else {
2146 2144 this.select(0);
2147 2145 this.handle_command_mode(this.get_cell(0));
2148 2146 }
2149 2147 this.set_dirty(false);
2150 2148 this.scroll_to_top();
2151 2149 if (data.orig_nbformat !== undefined && data.nbformat !== data.orig_nbformat) {
2152 2150 var msg = "This notebook has been converted from an older " +
2153 2151 "notebook format (v"+data.orig_nbformat+") to the current notebook " +
2154 2152 "format (v"+data.nbformat+"). The next time you save this notebook, the " +
2155 2153 "newer notebook format will be used and older versions of IPython " +
2156 2154 "may not be able to read it. To keep the older version, close the " +
2157 2155 "notebook without saving it.";
2158 2156 dialog.modal({
2159 2157 notebook: this,
2160 2158 keyboard_manager: this.keyboard_manager,
2161 2159 title : "Notebook converted",
2162 2160 body : msg,
2163 2161 buttons : {
2164 2162 OK : {
2165 2163 class : "btn-primary"
2166 2164 }
2167 2165 }
2168 2166 });
2169 2167 } else if (data.orig_nbformat_minor !== undefined && data.nbformat_minor !== data.orig_nbformat_minor) {
2170 2168 var that = this;
2171 2169 var orig_vs = 'v' + data.nbformat + '.' + data.orig_nbformat_minor;
2172 2170 var this_vs = 'v' + data.nbformat + '.' + this.nbformat_minor;
2173 2171 var msg = "This notebook is version " + orig_vs + ", but we only fully support up to " +
2174 2172 this_vs + ". You can still work with this notebook, but some features " +
2175 2173 "introduced in later notebook versions may not be available.";
2176 2174
2177 2175 dialog.modal({
2178 2176 notebook: this,
2179 2177 keyboard_manager: this.keyboard_manager,
2180 2178 title : "Newer Notebook",
2181 2179 body : msg,
2182 2180 buttons : {
2183 2181 OK : {
2184 2182 class : "btn-danger"
2185 2183 }
2186 2184 }
2187 2185 });
2188 2186
2189 2187 }
2190 2188
2191 2189 // Create the session after the notebook is completely loaded to prevent
2192 2190 // code execution upon loading, which is a security risk.
2193 2191 if (this.session === null) {
2194 2192 this.start_session();
2195 2193 }
2196 2194 // load our checkpoint list
2197 2195 this.list_checkpoints();
2198 2196
2199 2197 // load toolbar state
2200 2198 if (this.metadata.celltoolbar) {
2201 2199 celltoolbar.CellToolbar.global_show();
2202 2200 celltoolbar.CellToolbar.activate_preset(this.metadata.celltoolbar);
2203 2201 } else {
2204 2202 celltoolbar.CellToolbar.global_hide();
2205 2203 }
2206 2204
2207 2205 // now that we're fully loaded, it is safe to restore save functionality
2208 2206 delete(this.save_notebook);
2209 2207 this.events.trigger('notebook_loaded.Notebook');
2210 2208 };
2211 2209
2212 2210 /**
2213 2211 * Failure callback for loading a notebook from the server.
2214 2212 *
2215 2213 * @method load_notebook_error
2216 2214 * @param {jqXHR} xhr jQuery Ajax object
2217 2215 * @param {String} status Description of response status
2218 2216 * @param {String} error HTTP error message
2219 2217 */
2220 2218 Notebook.prototype.load_notebook_error = function (xhr, status, error) {
2221 2219 this.events.trigger('notebook_load_failed.Notebook', [xhr, status, error]);
2222 2220 var msg;
2223 2221 if (xhr.status === 400) {
2224 2222 msg = error;
2225 2223 } else if (xhr.status === 500) {
2226 2224 msg = "An unknown error occurred while loading this notebook. " +
2227 2225 "This version can load notebook formats " +
2228 2226 "v" + this.nbformat + " or earlier.";
2229 2227 }
2230 2228 dialog.modal({
2231 2229 notebook: this,
2232 2230 keyboard_manager: this.keyboard_manager,
2233 2231 title: "Error loading notebook",
2234 2232 body : msg,
2235 2233 buttons : {
2236 2234 "OK": {}
2237 2235 }
2238 2236 });
2239 2237 };
2240 2238
2241 2239 /********************* checkpoint-related *********************/
2242 2240
2243 2241 /**
2244 2242 * Save the notebook then immediately create a checkpoint.
2245 2243 *
2246 2244 * @method save_checkpoint
2247 2245 */
2248 2246 Notebook.prototype.save_checkpoint = function () {
2249 2247 this._checkpoint_after_save = true;
2250 2248 this.save_notebook();
2251 2249 };
2252 2250
2253 2251 /**
2254 2252 * Add a checkpoint for this notebook.
2255 2253 * for use as a callback from checkpoint creation.
2256 2254 *
2257 2255 * @method add_checkpoint
2258 2256 */
2259 2257 Notebook.prototype.add_checkpoint = function (checkpoint) {
2260 2258 var found = false;
2261 2259 for (var i = 0; i < this.checkpoints.length; i++) {
2262 2260 var existing = this.checkpoints[i];
2263 2261 if (existing.id == checkpoint.id) {
2264 2262 found = true;
2265 2263 this.checkpoints[i] = checkpoint;
2266 2264 break;
2267 2265 }
2268 2266 }
2269 2267 if (!found) {
2270 2268 this.checkpoints.push(checkpoint);
2271 2269 }
2272 2270 this.last_checkpoint = this.checkpoints[this.checkpoints.length - 1];
2273 2271 };
2274 2272
2275 2273 /**
2276 2274 * List checkpoints for this notebook.
2277 2275 *
2278 2276 * @method list_checkpoints
2279 2277 */
2280 2278 Notebook.prototype.list_checkpoints = function () {
2281 2279 var url = utils.url_join_encode(
2282 2280 this.base_url,
2283 2281 'api/notebooks',
2284 2282 this.notebook_path,
2285 2283 this.notebook_name,
2286 2284 'checkpoints'
2287 2285 );
2288 2286 $.get(url).done(
2289 2287 $.proxy(this.list_checkpoints_success, this)
2290 2288 ).fail(
2291 2289 $.proxy(this.list_checkpoints_error, this)
2292 2290 );
2293 2291 };
2294 2292
2295 2293 /**
2296 2294 * Success callback for listing checkpoints.
2297 2295 *
2298 2296 * @method list_checkpoint_success
2299 2297 * @param {Object} data JSON representation of a checkpoint
2300 2298 * @param {String} status Description of response status
2301 2299 * @param {jqXHR} xhr jQuery Ajax object
2302 2300 */
2303 2301 Notebook.prototype.list_checkpoints_success = function (data, status, xhr) {
2304 2302 data = $.parseJSON(data);
2305 2303 this.checkpoints = data;
2306 2304 if (data.length) {
2307 2305 this.last_checkpoint = data[data.length - 1];
2308 2306 } else {
2309 2307 this.last_checkpoint = null;
2310 2308 }
2311 2309 this.events.trigger('checkpoints_listed.Notebook', [data]);
2312 2310 };
2313 2311
2314 2312 /**
2315 2313 * Failure callback for listing a checkpoint.
2316 2314 *
2317 2315 * @method list_checkpoint_error
2318 2316 * @param {jqXHR} xhr jQuery Ajax object
2319 2317 * @param {String} status Description of response status
2320 2318 * @param {String} error_msg HTTP error message
2321 2319 */
2322 2320 Notebook.prototype.list_checkpoints_error = function (xhr, status, error_msg) {
2323 2321 this.events.trigger('list_checkpoints_failed.Notebook');
2324 2322 };
2325 2323
2326 2324 /**
2327 2325 * Create a checkpoint of this notebook on the server from the most recent save.
2328 2326 *
2329 2327 * @method create_checkpoint
2330 2328 */
2331 2329 Notebook.prototype.create_checkpoint = function () {
2332 2330 var url = utils.url_join_encode(
2333 2331 this.base_url,
2334 2332 'api/notebooks',
2335 2333 this.notebook_path,
2336 2334 this.notebook_name,
2337 2335 'checkpoints'
2338 2336 );
2339 2337 $.post(url).done(
2340 2338 $.proxy(this.create_checkpoint_success, this)
2341 2339 ).fail(
2342 2340 $.proxy(this.create_checkpoint_error, this)
2343 2341 );
2344 2342 };
2345 2343
2346 2344 /**
2347 2345 * Success callback for creating a checkpoint.
2348 2346 *
2349 2347 * @method create_checkpoint_success
2350 2348 * @param {Object} data JSON representation of a checkpoint
2351 2349 * @param {String} status Description of response status
2352 2350 * @param {jqXHR} xhr jQuery Ajax object
2353 2351 */
2354 2352 Notebook.prototype.create_checkpoint_success = function (data, status, xhr) {
2355 2353 data = $.parseJSON(data);
2356 2354 this.add_checkpoint(data);
2357 2355 this.events.trigger('checkpoint_created.Notebook', data);
2358 2356 };
2359 2357
2360 2358 /**
2361 2359 * Failure callback for creating a checkpoint.
2362 2360 *
2363 2361 * @method create_checkpoint_error
2364 2362 * @param {jqXHR} xhr jQuery Ajax object
2365 2363 * @param {String} status Description of response status
2366 2364 * @param {String} error_msg HTTP error message
2367 2365 */
2368 2366 Notebook.prototype.create_checkpoint_error = function (xhr, status, error_msg) {
2369 2367 this.events.trigger('checkpoint_failed.Notebook');
2370 2368 };
2371 2369
2372 2370 Notebook.prototype.restore_checkpoint_dialog = function (checkpoint) {
2373 2371 var that = this;
2374 2372 checkpoint = checkpoint || this.last_checkpoint;
2375 2373 if ( ! checkpoint ) {
2376 2374 console.log("restore dialog, but no checkpoint to restore to!");
2377 2375 return;
2378 2376 }
2379 2377 var body = $('<div/>').append(
2380 2378 $('<p/>').addClass("p-space").text(
2381 2379 "Are you sure you want to revert the notebook to " +
2382 2380 "the latest checkpoint?"
2383 2381 ).append(
2384 2382 $("<strong/>").text(
2385 2383 " This cannot be undone."
2386 2384 )
2387 2385 )
2388 2386 ).append(
2389 2387 $('<p/>').addClass("p-space").text("The checkpoint was last updated at:")
2390 2388 ).append(
2391 2389 $('<p/>').addClass("p-space").text(
2392 2390 Date(checkpoint.last_modified)
2393 2391 ).css("text-align", "center")
2394 2392 );
2395 2393
2396 2394 dialog.modal({
2397 2395 notebook: this,
2398 2396 keyboard_manager: this.keyboard_manager,
2399 2397 title : "Revert notebook to checkpoint",
2400 2398 body : body,
2401 2399 buttons : {
2402 2400 Revert : {
2403 2401 class : "btn-danger",
2404 2402 click : function () {
2405 2403 that.restore_checkpoint(checkpoint.id);
2406 2404 }
2407 2405 },
2408 2406 Cancel : {}
2409 2407 }
2410 2408 });
2411 2409 };
2412 2410
2413 2411 /**
2414 2412 * Restore the notebook to a checkpoint state.
2415 2413 *
2416 2414 * @method restore_checkpoint
2417 2415 * @param {String} checkpoint ID
2418 2416 */
2419 2417 Notebook.prototype.restore_checkpoint = function (checkpoint) {
2420 2418 this.events.trigger('notebook_restoring.Notebook', checkpoint);
2421 2419 var url = utils.url_join_encode(
2422 2420 this.base_url,
2423 2421 'api/notebooks',
2424 2422 this.notebook_path,
2425 2423 this.notebook_name,
2426 2424 'checkpoints',
2427 2425 checkpoint
2428 2426 );
2429 2427 $.post(url).done(
2430 2428 $.proxy(this.restore_checkpoint_success, this)
2431 2429 ).fail(
2432 2430 $.proxy(this.restore_checkpoint_error, this)
2433 2431 );
2434 2432 };
2435 2433
2436 2434 /**
2437 2435 * Success callback for restoring a notebook to a checkpoint.
2438 2436 *
2439 2437 * @method restore_checkpoint_success
2440 2438 * @param {Object} data (ignored, should be empty)
2441 2439 * @param {String} status Description of response status
2442 2440 * @param {jqXHR} xhr jQuery Ajax object
2443 2441 */
2444 2442 Notebook.prototype.restore_checkpoint_success = function (data, status, xhr) {
2445 2443 this.events.trigger('checkpoint_restored.Notebook');
2446 2444 this.load_notebook(this.notebook_name, this.notebook_path);
2447 2445 };
2448 2446
2449 2447 /**
2450 2448 * Failure callback for restoring a notebook to a checkpoint.
2451 2449 *
2452 2450 * @method restore_checkpoint_error
2453 2451 * @param {jqXHR} xhr jQuery Ajax object
2454 2452 * @param {String} status Description of response status
2455 2453 * @param {String} error_msg HTTP error message
2456 2454 */
2457 2455 Notebook.prototype.restore_checkpoint_error = function (xhr, status, error_msg) {
2458 2456 this.events.trigger('checkpoint_restore_failed.Notebook');
2459 2457 };
2460 2458
2461 2459 /**
2462 2460 * Delete a notebook checkpoint.
2463 2461 *
2464 2462 * @method delete_checkpoint
2465 2463 * @param {String} checkpoint ID
2466 2464 */
2467 2465 Notebook.prototype.delete_checkpoint = function (checkpoint) {
2468 2466 this.events.trigger('notebook_restoring.Notebook', checkpoint);
2469 2467 var url = utils.url_join_encode(
2470 2468 this.base_url,
2471 2469 'api/notebooks',
2472 2470 this.notebook_path,
2473 2471 this.notebook_name,
2474 2472 'checkpoints',
2475 2473 checkpoint
2476 2474 );
2477 2475 $.ajax(url, {
2478 2476 type: 'DELETE',
2479 2477 success: $.proxy(this.delete_checkpoint_success, this),
2480 2478 error: $.proxy(this.delete_checkpoint_error, this)
2481 2479 });
2482 2480 };
2483 2481
2484 2482 /**
2485 2483 * Success callback for deleting a notebook checkpoint
2486 2484 *
2487 2485 * @method delete_checkpoint_success
2488 2486 * @param {Object} data (ignored, should be empty)
2489 2487 * @param {String} status Description of response status
2490 2488 * @param {jqXHR} xhr jQuery Ajax object
2491 2489 */
2492 2490 Notebook.prototype.delete_checkpoint_success = function (data, status, xhr) {
2493 2491 this.events.trigger('checkpoint_deleted.Notebook', data);
2494 2492 this.load_notebook(this.notebook_name, this.notebook_path);
2495 2493 };
2496 2494
2497 2495 /**
2498 2496 * Failure callback for deleting a notebook checkpoint.
2499 2497 *
2500 2498 * @method delete_checkpoint_error
2501 2499 * @param {jqXHR} xhr jQuery Ajax object
2502 2500 * @param {String} status Description of response status
2503 2501 * @param {String} error_msg HTTP error message
2504 2502 */
2505 2503 Notebook.prototype.delete_checkpoint_error = function (xhr, status, error_msg) {
2506 2504 this.events.trigger('checkpoint_delete_failed.Notebook');
2507 2505 };
2508 2506
2509 2507
2510 2508 // For backwards compatability.
2511 2509 IPython.Notebook = Notebook;
2512 2510
2513 2511 return {'Notebook': Notebook};
2514 2512 });
@@ -1,77 +1,77 b''
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 4 define([
5 5 'base/js/namespace',
6 6 'jquery',
7 7 ], function(IPython, $) {
8 8 "use strict";
9 9
10 10 var NotificationWidget = function (selector) {
11 11 this.selector = selector;
12 12 this.timeout = null;
13 13 this.busy = false;
14 14 if (this.selector !== undefined) {
15 15 this.element = $(selector);
16 16 this.style();
17 17 }
18 18 this.element.button();
19 19 this.element.hide();
20 20 var that = this;
21 21
22 22 this.inner = $('<span/>');
23 23 this.element.append(this.inner);
24 24
25 25 };
26 26
27 27 NotificationWidget.prototype.style = function () {
28 28 this.element.addClass('notification_widget pull-right');
29 29 this.element.addClass('border-box-sizing');
30 30 };
31 31
32 32 // msg : message to display
33 33 // timeout : time in ms before diseapearing
34 34 //
35 35 // if timeout <= 0
36 36 // click_callback : function called if user click on notification
37 37 // could return false to prevent the notification to be dismissed
38 NotificationWidget.prototype.set_message = function (msg, timeout, click_callback, opts) {
39 opts = opts || {};
38 NotificationWidget.prototype.set_message = function (msg, timeout, click_callback, options) {
39 options = options || {};
40 40 var callback = click_callback || function() {return false;};
41 41 var that = this;
42 this.inner.attr('class', opts.icon);
43 this.inner.attr('title', opts.title);
42 this.inner.attr('class', options.icon);
43 this.inner.attr('title', options.title);
44 44 this.inner.text(msg);
45 45 this.element.fadeIn(100);
46 46 if (this.timeout !== null) {
47 47 clearTimeout(this.timeout);
48 48 this.timeout = null;
49 49 }
50 50 if (timeout !== undefined && timeout >=0) {
51 51 this.timeout = setTimeout(function () {
52 52 that.element.fadeOut(100, function () {that.inner.text('');});
53 53 that.timeout = null;
54 54 }, timeout);
55 55 } else {
56 56 this.element.click(function() {
57 57 if( callback() !== false ) {
58 58 that.element.fadeOut(100, function () {that.inner.text('');});
59 59 that.element.unbind('click');
60 60 }
61 61 if (that.timeout !== undefined) {
62 62 that.timeout = undefined;
63 63 clearTimeout(that.timeout);
64 64 }
65 65 });
66 66 }
67 67 };
68 68
69 69 NotificationWidget.prototype.get_message = function () {
70 70 return this.inner.html();
71 71 };
72 72
73 73 // For backwards compatability.
74 74 IPython.NotificationWidget = NotificationWidget;
75 75
76 76 return {'NotificationWidget': NotificationWidget};
77 77 });
@@ -1,992 +1,992 b''
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 4 define([
5 5 'base/js/namespace',
6 6 'jquery',
7 7 'base/js/utils',
8 8 'base/js/security',
9 9 'base/js/keyboard',
10 10 'notebook/js/mathjaxutils',
11 11 ], function(IPython, $, utils, security, keyboard, mathjaxutils) {
12 12 "use strict";
13 13
14 14 /**
15 15 * @class OutputArea
16 16 *
17 17 * @constructor
18 18 */
19 19
20 var OutputArea = function (selector, prompt_area, events, keyboard_manager) {
21 this.selector = selector;
22 this.events = events;
23 this.keyboard_manager = keyboard_manager;
24 this.wrapper = $(selector);
20 var OutputArea = function (options) {
21 this.selector = options.selector;
22 this.events = options.events;
23 this.keyboard_manager = options.keyboard_manager;
24 this.wrapper = $(options.selector);
25 25 this.outputs = [];
26 26 this.collapsed = false;
27 27 this.scrolled = false;
28 28 this.trusted = true;
29 29 this.clear_queued = null;
30 if (prompt_area === undefined) {
30 if (options.prompt_area === undefined) {
31 31 this.prompt_area = true;
32 32 } else {
33 this.prompt_area = prompt_area;
33 this.prompt_area = options.prompt_area;
34 34 }
35 35 this.create_elements();
36 36 this.style();
37 37 this.bind_events();
38 38 };
39 39
40 40
41 41 /**
42 42 * Class prototypes
43 43 **/
44 44
45 45 OutputArea.prototype.create_elements = function () {
46 46 this.element = $("<div/>");
47 47 this.collapse_button = $("<div/>");
48 48 this.prompt_overlay = $("<div/>");
49 49 this.wrapper.append(this.prompt_overlay);
50 50 this.wrapper.append(this.element);
51 51 this.wrapper.append(this.collapse_button);
52 52 };
53 53
54 54
55 55 OutputArea.prototype.style = function () {
56 56 this.collapse_button.hide();
57 57 this.prompt_overlay.hide();
58 58
59 59 this.wrapper.addClass('output_wrapper');
60 60 this.element.addClass('output');
61 61
62 62 this.collapse_button.addClass("btn btn-default output_collapsed");
63 63 this.collapse_button.attr('title', 'click to expand output');
64 64 this.collapse_button.text('. . .');
65 65
66 66 this.prompt_overlay.addClass('out_prompt_overlay prompt');
67 67 this.prompt_overlay.attr('title', 'click to expand output; double click to hide output');
68 68
69 69 this.collapse();
70 70 };
71 71
72 72 /**
73 73 * Should the OutputArea scroll?
74 74 * Returns whether the height (in lines) exceeds a threshold.
75 75 *
76 76 * @private
77 77 * @method _should_scroll
78 78 * @param [lines=100]{Integer}
79 79 * @return {Bool}
80 80 *
81 81 */
82 82 OutputArea.prototype._should_scroll = function (lines) {
83 83 if (lines <=0 ){ return }
84 84 if (!lines) {
85 85 lines = 100;
86 86 }
87 87 // line-height from http://stackoverflow.com/questions/1185151
88 88 var fontSize = this.element.css('font-size');
89 89 var lineHeight = Math.floor(parseInt(fontSize.replace('px','')) * 1.5);
90 90
91 91 return (this.element.height() > lines * lineHeight);
92 92 };
93 93
94 94
95 95 OutputArea.prototype.bind_events = function () {
96 96 var that = this;
97 97 this.prompt_overlay.dblclick(function () { that.toggle_output(); });
98 98 this.prompt_overlay.click(function () { that.toggle_scroll(); });
99 99
100 100 this.element.resize(function () {
101 101 // FIXME: Firefox on Linux misbehaves, so automatic scrolling is disabled
102 102 if ( utils.browser[0] === "Firefox" ) {
103 103 return;
104 104 }
105 105 // maybe scroll output,
106 106 // if it's grown large enough and hasn't already been scrolled.
107 107 if ( !that.scrolled && that._should_scroll(OutputArea.auto_scroll_threshold)) {
108 108 that.scroll_area();
109 109 }
110 110 });
111 111 this.collapse_button.click(function () {
112 112 that.expand();
113 113 });
114 114 };
115 115
116 116
117 117 OutputArea.prototype.collapse = function () {
118 118 if (!this.collapsed) {
119 119 this.element.hide();
120 120 this.prompt_overlay.hide();
121 121 if (this.element.html()){
122 122 this.collapse_button.show();
123 123 }
124 124 this.collapsed = true;
125 125 }
126 126 };
127 127
128 128
129 129 OutputArea.prototype.expand = function () {
130 130 if (this.collapsed) {
131 131 this.collapse_button.hide();
132 132 this.element.show();
133 133 this.prompt_overlay.show();
134 134 this.collapsed = false;
135 135 }
136 136 };
137 137
138 138
139 139 OutputArea.prototype.toggle_output = function () {
140 140 if (this.collapsed) {
141 141 this.expand();
142 142 } else {
143 143 this.collapse();
144 144 }
145 145 };
146 146
147 147
148 148 OutputArea.prototype.scroll_area = function () {
149 149 this.element.addClass('output_scroll');
150 150 this.prompt_overlay.attr('title', 'click to unscroll output; double click to hide');
151 151 this.scrolled = true;
152 152 };
153 153
154 154
155 155 OutputArea.prototype.unscroll_area = function () {
156 156 this.element.removeClass('output_scroll');
157 157 this.prompt_overlay.attr('title', 'click to scroll output; double click to hide');
158 158 this.scrolled = false;
159 159 };
160 160
161 161 /**
162 162 *
163 163 * Scroll OutputArea if height supperior than a threshold (in lines).
164 164 *
165 165 * Threshold is a maximum number of lines. If unspecified, defaults to
166 166 * OutputArea.minimum_scroll_threshold.
167 167 *
168 168 * Negative threshold will prevent the OutputArea from ever scrolling.
169 169 *
170 170 * @method scroll_if_long
171 171 *
172 172 * @param [lines=20]{Number} Default to 20 if not set,
173 173 * behavior undefined for value of `0`.
174 174 *
175 175 **/
176 176 OutputArea.prototype.scroll_if_long = function (lines) {
177 177 var n = lines | OutputArea.minimum_scroll_threshold;
178 178 if(n <= 0){
179 179 return
180 180 }
181 181
182 182 if (this._should_scroll(n)) {
183 183 // only allow scrolling long-enough output
184 184 this.scroll_area();
185 185 }
186 186 };
187 187
188 188
189 189 OutputArea.prototype.toggle_scroll = function () {
190 190 if (this.scrolled) {
191 191 this.unscroll_area();
192 192 } else {
193 193 // only allow scrolling long-enough output
194 194 this.scroll_if_long();
195 195 }
196 196 };
197 197
198 198
199 199 // typeset with MathJax if MathJax is available
200 200 OutputArea.prototype.typeset = function () {
201 201 if (window.MathJax){
202 202 MathJax.Hub.Queue(["Typeset",MathJax.Hub]);
203 203 }
204 204 };
205 205
206 206
207 207 OutputArea.prototype.handle_output = function (msg) {
208 208 var json = {};
209 209 var msg_type = json.output_type = msg.header.msg_type;
210 210 var content = msg.content;
211 211 if (msg_type === "stream") {
212 212 json.text = content.data;
213 213 json.stream = content.name;
214 214 } else if (msg_type === "display_data") {
215 215 json = content.data;
216 216 json.output_type = msg_type;
217 217 json.metadata = content.metadata;
218 218 } else if (msg_type === "execute_result") {
219 219 json = content.data;
220 220 json.output_type = msg_type;
221 221 json.metadata = content.metadata;
222 222 json.prompt_number = content.execution_count;
223 223 } else if (msg_type === "error") {
224 224 json.ename = content.ename;
225 225 json.evalue = content.evalue;
226 226 json.traceback = content.traceback;
227 227 } else {
228 228 console.log("unhandled output message", msg);
229 229 return;
230 230 }
231 231 this.append_output(json);
232 232 };
233 233
234 234
235 235 OutputArea.prototype.rename_keys = function (data, key_map) {
236 236 var remapped = {};
237 237 for (var key in data) {
238 238 var new_key = key_map[key] || key;
239 239 remapped[new_key] = data[key];
240 240 }
241 241 return remapped;
242 242 };
243 243
244 244
245 245 OutputArea.output_types = [
246 246 'application/javascript',
247 247 'text/html',
248 248 'text/markdown',
249 249 'text/latex',
250 250 'image/svg+xml',
251 251 'image/png',
252 252 'image/jpeg',
253 253 'application/pdf',
254 254 'text/plain'
255 255 ];
256 256
257 257 OutputArea.prototype.validate_output = function (json) {
258 258 // scrub invalid outputs
259 259 // TODO: right now everything is a string, but JSON really shouldn't be.
260 260 // nbformat 4 will fix that.
261 261 $.map(OutputArea.output_types, function(key){
262 262 if (json[key] !== undefined && typeof json[key] !== 'string') {
263 263 console.log("Invalid type for " + key, json[key]);
264 264 delete json[key];
265 265 }
266 266 });
267 267 return json;
268 268 };
269 269
270 270 OutputArea.prototype.append_output = function (json) {
271 271 this.expand();
272 272
273 273 // validate output data types
274 274 json = this.validate_output(json);
275 275
276 276 // Clear the output if clear is queued.
277 277 var needs_height_reset = false;
278 278 if (this.clear_queued) {
279 279 this.clear_output(false);
280 280 needs_height_reset = true;
281 281 }
282 282
283 283 if (json.output_type === 'execute_result') {
284 284 this.append_execute_result(json);
285 285 } else if (json.output_type === 'error') {
286 286 this.append_error(json);
287 287 } else if (json.output_type === 'stream') {
288 288 this.append_stream(json);
289 289 }
290 290
291 291 // We must release the animation fixed height in a callback since Gecko
292 292 // (FireFox) doesn't render the image immediately as the data is
293 293 // available.
294 294 var that = this;
295 295 var handle_appended = function ($el) {
296 296 // Only reset the height to automatic if the height is currently
297 297 // fixed (done by wait=True flag on clear_output).
298 298 if (needs_height_reset) {
299 299 that.element.height('');
300 300 }
301 301 that.element.trigger('resize');
302 302 };
303 303 if (json.output_type === 'display_data') {
304 304 this.append_display_data(json, handle_appended);
305 305 } else {
306 306 handle_appended();
307 307 }
308 308
309 309 this.outputs.push(json);
310 310 };
311 311
312 312
313 313 OutputArea.prototype.create_output_area = function () {
314 314 var oa = $("<div/>").addClass("output_area");
315 315 if (this.prompt_area) {
316 316 oa.append($('<div/>').addClass('prompt'));
317 317 }
318 318 return oa;
319 319 };
320 320
321 321
322 322 function _get_metadata_key(metadata, key, mime) {
323 323 var mime_md = metadata[mime];
324 324 // mime-specific higher priority
325 325 if (mime_md && mime_md[key] !== undefined) {
326 326 return mime_md[key];
327 327 }
328 328 // fallback on global
329 329 return metadata[key];
330 330 }
331 331
332 332 OutputArea.prototype.create_output_subarea = function(md, classes, mime) {
333 333 var subarea = $('<div/>').addClass('output_subarea').addClass(classes);
334 334 if (_get_metadata_key(md, 'isolated', mime)) {
335 335 // Create an iframe to isolate the subarea from the rest of the
336 336 // document
337 337 var iframe = $('<iframe/>').addClass('box-flex1');
338 338 iframe.css({'height':1, 'width':'100%', 'display':'block'});
339 339 iframe.attr('frameborder', 0);
340 340 iframe.attr('scrolling', 'auto');
341 341
342 342 // Once the iframe is loaded, the subarea is dynamically inserted
343 343 iframe.on('load', function() {
344 344 // Workaround needed by Firefox, to properly render svg inside
345 345 // iframes, see http://stackoverflow.com/questions/10177190/
346 346 // svg-dynamically-added-to-iframe-does-not-render-correctly
347 347 this.contentDocument.open();
348 348
349 349 // Insert the subarea into the iframe
350 350 // We must directly write the html. When using Jquery's append
351 351 // method, javascript is evaluated in the parent document and
352 352 // not in the iframe document. At this point, subarea doesn't
353 353 // contain any user content.
354 354 this.contentDocument.write(subarea.html());
355 355
356 356 this.contentDocument.close();
357 357
358 358 var body = this.contentDocument.body;
359 359 // Adjust the iframe height automatically
360 360 iframe.height(body.scrollHeight + 'px');
361 361 });
362 362
363 363 // Elements should be appended to the inner subarea and not to the
364 364 // iframe
365 365 iframe.append = function(that) {
366 366 subarea.append(that);
367 367 };
368 368
369 369 return iframe;
370 370 } else {
371 371 return subarea;
372 372 }
373 373 }
374 374
375 375
376 376 OutputArea.prototype._append_javascript_error = function (err, element) {
377 377 // display a message when a javascript error occurs in display output
378 378 var msg = "Javascript error adding output!"
379 379 if ( element === undefined ) return;
380 380 element
381 381 .append($('<div/>').text(msg).addClass('js-error'))
382 382 .append($('<div/>').text(err.toString()).addClass('js-error'))
383 383 .append($('<div/>').text('See your browser Javascript console for more details.').addClass('js-error'));
384 384 };
385 385
386 386 OutputArea.prototype._safe_append = function (toinsert) {
387 387 // safely append an item to the document
388 388 // this is an object created by user code,
389 389 // and may have errors, which should not be raised
390 390 // under any circumstances.
391 391 try {
392 392 this.element.append(toinsert);
393 393 } catch(err) {
394 394 console.log(err);
395 395 // Create an actual output_area and output_subarea, which creates
396 396 // the prompt area and the proper indentation.
397 397 var toinsert = this.create_output_area();
398 398 var subarea = $('<div/>').addClass('output_subarea');
399 399 toinsert.append(subarea);
400 400 this._append_javascript_error(err, subarea);
401 401 this.element.append(toinsert);
402 402 }
403 403 };
404 404
405 405
406 406 OutputArea.prototype.append_execute_result = function (json) {
407 407 var n = json.prompt_number || ' ';
408 408 var toinsert = this.create_output_area();
409 409 if (this.prompt_area) {
410 410 toinsert.find('div.prompt').addClass('output_prompt').text('Out[' + n + ']:');
411 411 }
412 412 var inserted = this.append_mime_type(json, toinsert);
413 413 if (inserted) {
414 414 inserted.addClass('output_result');
415 415 }
416 416 this._safe_append(toinsert);
417 417 // If we just output latex, typeset it.
418 418 if ((json['text/latex'] !== undefined) ||
419 419 (json['text/html'] !== undefined) ||
420 420 (json['text/markdown'] !== undefined)) {
421 421 this.typeset();
422 422 }
423 423 };
424 424
425 425
426 426 OutputArea.prototype.append_error = function (json) {
427 427 var tb = json.traceback;
428 428 if (tb !== undefined && tb.length > 0) {
429 429 var s = '';
430 430 var len = tb.length;
431 431 for (var i=0; i<len; i++) {
432 432 s = s + tb[i] + '\n';
433 433 }
434 434 s = s + '\n';
435 435 var toinsert = this.create_output_area();
436 436 var append_text = OutputArea.append_map['text/plain'];
437 437 if (append_text) {
438 438 append_text.apply(this, [s, {}, toinsert]).addClass('output_error');
439 439 }
440 440 this._safe_append(toinsert);
441 441 }
442 442 };
443 443
444 444
445 445 OutputArea.prototype.append_stream = function (json) {
446 446 // temporary fix: if stream undefined (json file written prior to this patch),
447 447 // default to most likely stdout:
448 448 if (json.stream === undefined){
449 449 json.stream = 'stdout';
450 450 }
451 451 var text = json.text;
452 452 var subclass = "output_"+json.stream;
453 453 if (this.outputs.length > 0){
454 454 // have at least one output to consider
455 455 var last = this.outputs[this.outputs.length-1];
456 456 if (last.output_type == 'stream' && json.stream == last.stream){
457 457 // latest output was in the same stream,
458 458 // so append directly into its pre tag
459 459 // escape ANSI & HTML specials:
460 460 var pre = this.element.find('div.'+subclass).last().find('pre');
461 461 var html = utils.fixCarriageReturn(
462 462 pre.html() + utils.fixConsole(text));
463 463 // The only user content injected with this HTML call is
464 464 // escaped by the fixConsole() method.
465 465 pre.html(html);
466 466 return;
467 467 }
468 468 }
469 469
470 470 if (!text.replace("\r", "")) {
471 471 // text is nothing (empty string, \r, etc.)
472 472 // so don't append any elements, which might add undesirable space
473 473 return;
474 474 }
475 475
476 476 // If we got here, attach a new div
477 477 var toinsert = this.create_output_area();
478 478 var append_text = OutputArea.append_map['text/plain'];
479 479 if (append_text) {
480 480 append_text.apply(this, [text, {}, toinsert]).addClass("output_stream " + subclass);
481 481 }
482 482 this._safe_append(toinsert);
483 483 };
484 484
485 485
486 486 OutputArea.prototype.append_display_data = function (json, handle_inserted) {
487 487 var toinsert = this.create_output_area();
488 488 if (this.append_mime_type(json, toinsert, handle_inserted)) {
489 489 this._safe_append(toinsert);
490 490 // If we just output latex, typeset it.
491 491 if ((json['text/latex'] !== undefined) ||
492 492 (json['text/html'] !== undefined) ||
493 493 (json['text/markdown'] !== undefined)) {
494 494 this.typeset();
495 495 }
496 496 }
497 497 };
498 498
499 499
500 500 OutputArea.safe_outputs = {
501 501 'text/plain' : true,
502 502 'text/latex' : true,
503 503 'image/png' : true,
504 504 'image/jpeg' : true
505 505 };
506 506
507 507 OutputArea.prototype.append_mime_type = function (json, element, handle_inserted) {
508 508 for (var i=0; i < OutputArea.display_order.length; i++) {
509 509 var type = OutputArea.display_order[i];
510 510 var append = OutputArea.append_map[type];
511 511 if ((json[type] !== undefined) && append) {
512 512 var value = json[type];
513 513 if (!this.trusted && !OutputArea.safe_outputs[type]) {
514 514 // not trusted, sanitize HTML
515 515 if (type==='text/html' || type==='text/svg') {
516 516 value = security.sanitize_html(value);
517 517 } else {
518 518 // don't display if we don't know how to sanitize it
519 519 console.log("Ignoring untrusted " + type + " output.");
520 520 continue;
521 521 }
522 522 }
523 523 var md = json.metadata || {};
524 524 var toinsert = append.apply(this, [value, md, element, handle_inserted]);
525 525 // Since only the png and jpeg mime types call the inserted
526 526 // callback, if the mime type is something other we must call the
527 527 // inserted callback only when the element is actually inserted
528 528 // into the DOM. Use a timeout of 0 to do this.
529 529 if (['image/png', 'image/jpeg'].indexOf(type) < 0 && handle_inserted !== undefined) {
530 530 setTimeout(handle_inserted, 0);
531 531 }
532 532 this.events.trigger('output_appended.OutputArea', [type, value, md, toinsert]);
533 533 return toinsert;
534 534 }
535 535 }
536 536 return null;
537 537 };
538 538
539 539
540 540 var append_html = function (html, md, element) {
541 541 var type = 'text/html';
542 542 var toinsert = this.create_output_subarea(md, "output_html rendered_html", type);
543 543 this.keyboard_manager.register_events(toinsert);
544 544 toinsert.append(html);
545 545 element.append(toinsert);
546 546 return toinsert;
547 547 };
548 548
549 549
550 550 var append_markdown = function(markdown, md, element) {
551 551 var type = 'text/markdown';
552 552 var toinsert = this.create_output_subarea(md, "output_markdown", type);
553 553 var text_and_math = mathjaxutils.remove_math(markdown);
554 554 var text = text_and_math[0];
555 555 var math = text_and_math[1];
556 556 var html = marked.parser(marked.lexer(text));
557 557 html = mathjaxutils.replace_math(html, math);
558 558 toinsert.append(html);
559 559 element.append(toinsert);
560 560 return toinsert;
561 561 };
562 562
563 563
564 564 var append_javascript = function (js, md, element) {
565 565 // We just eval the JS code, element appears in the local scope.
566 566 var type = 'application/javascript';
567 567 var toinsert = this.create_output_subarea(md, "output_javascript", type);
568 568 this.keyboard_manager.register_events(toinsert);
569 569 element.append(toinsert);
570 570
571 571 // Fix for ipython/issues/5293, make sure `element` is the area which
572 572 // output can be inserted into at the time of JS execution.
573 573 element = toinsert;
574 574 try {
575 575 eval(js);
576 576 } catch(err) {
577 577 console.log(err);
578 578 this._append_javascript_error(err, toinsert);
579 579 }
580 580 return toinsert;
581 581 };
582 582
583 583
584 584 var append_text = function (data, md, element) {
585 585 var type = 'text/plain';
586 586 var toinsert = this.create_output_subarea(md, "output_text", type);
587 587 // escape ANSI & HTML specials in plaintext:
588 588 data = utils.fixConsole(data);
589 589 data = utils.fixCarriageReturn(data);
590 590 data = utils.autoLinkUrls(data);
591 591 // The only user content injected with this HTML call is
592 592 // escaped by the fixConsole() method.
593 593 toinsert.append($("<pre/>").html(data));
594 594 element.append(toinsert);
595 595 return toinsert;
596 596 };
597 597
598 598
599 599 var append_svg = function (svg_html, md, element) {
600 600 var type = 'image/svg+xml';
601 601 var toinsert = this.create_output_subarea(md, "output_svg", type);
602 602
603 603 // Get the svg element from within the HTML.
604 604 var svg = $('<div />').html(svg_html).find('svg');
605 605 var svg_area = $('<div />');
606 606 var width = svg.attr('width');
607 607 var height = svg.attr('height');
608 608 svg
609 609 .width('100%')
610 610 .height('100%');
611 611 svg_area
612 612 .width(width)
613 613 .height(height);
614 614
615 615 // The jQuery resize handlers don't seem to work on the svg element.
616 616 // When the svg renders completely, measure it's size and set the parent
617 617 // div to that size. Then set the svg to 100% the size of the parent
618 618 // div and make the parent div resizable.
619 619 this._dblclick_to_reset_size(svg_area, true, false);
620 620
621 621 svg_area.append(svg);
622 622 toinsert.append(svg_area);
623 623 element.append(toinsert);
624 624
625 625 return toinsert;
626 626 };
627 627
628 628 OutputArea.prototype._dblclick_to_reset_size = function (img, immediately, resize_parent) {
629 629 // Add a resize handler to an element
630 630 //
631 631 // img: jQuery element
632 632 // immediately: bool=False
633 633 // Wait for the element to load before creating the handle.
634 634 // resize_parent: bool=True
635 635 // Should the parent of the element be resized when the element is
636 636 // reset (by double click).
637 637 var callback = function (){
638 638 var h0 = img.height();
639 639 var w0 = img.width();
640 640 if (!(h0 && w0)) {
641 641 // zero size, don't make it resizable
642 642 return;
643 643 }
644 644 img.resizable({
645 645 aspectRatio: true,
646 646 autoHide: true
647 647 });
648 648 img.dblclick(function () {
649 649 // resize wrapper & image together for some reason:
650 650 img.height(h0);
651 651 img.width(w0);
652 652 if (resize_parent === undefined || resize_parent) {
653 653 img.parent().height(h0);
654 654 img.parent().width(w0);
655 655 }
656 656 });
657 657 };
658 658
659 659 if (immediately) {
660 660 callback();
661 661 } else {
662 662 img.on("load", callback);
663 663 }
664 664 };
665 665
666 666 var set_width_height = function (img, md, mime) {
667 667 // set width and height of an img element from metadata
668 668 var height = _get_metadata_key(md, 'height', mime);
669 669 if (height !== undefined) img.attr('height', height);
670 670 var width = _get_metadata_key(md, 'width', mime);
671 671 if (width !== undefined) img.attr('width', width);
672 672 };
673 673
674 674 var append_png = function (png, md, element, handle_inserted) {
675 675 var type = 'image/png';
676 676 var toinsert = this.create_output_subarea(md, "output_png", type);
677 677 var img = $("<img/>");
678 678 if (handle_inserted !== undefined) {
679 679 img.on('load', function(){
680 680 handle_inserted(img);
681 681 });
682 682 }
683 683 img[0].src = 'data:image/png;base64,'+ png;
684 684 set_width_height(img, md, 'image/png');
685 685 this._dblclick_to_reset_size(img);
686 686 toinsert.append(img);
687 687 element.append(toinsert);
688 688 return toinsert;
689 689 };
690 690
691 691
692 692 var append_jpeg = function (jpeg, md, element, handle_inserted) {
693 693 var type = 'image/jpeg';
694 694 var toinsert = this.create_output_subarea(md, "output_jpeg", type);
695 695 var img = $("<img/>");
696 696 if (handle_inserted !== undefined) {
697 697 img.on('load', function(){
698 698 handle_inserted(img);
699 699 });
700 700 }
701 701 img[0].src = 'data:image/jpeg;base64,'+ jpeg;
702 702 set_width_height(img, md, 'image/jpeg');
703 703 this._dblclick_to_reset_size(img);
704 704 toinsert.append(img);
705 705 element.append(toinsert);
706 706 return toinsert;
707 707 };
708 708
709 709
710 710 var append_pdf = function (pdf, md, element) {
711 711 var type = 'application/pdf';
712 712 var toinsert = this.create_output_subarea(md, "output_pdf", type);
713 713 var a = $('<a/>').attr('href', 'data:application/pdf;base64,'+pdf);
714 714 a.attr('target', '_blank');
715 715 a.text('View PDF')
716 716 toinsert.append(a);
717 717 element.append(toinsert);
718 718 return toinsert;
719 719 }
720 720
721 721 var append_latex = function (latex, md, element) {
722 722 // This method cannot do the typesetting because the latex first has to
723 723 // be on the page.
724 724 var type = 'text/latex';
725 725 var toinsert = this.create_output_subarea(md, "output_latex", type);
726 726 toinsert.append(latex);
727 727 element.append(toinsert);
728 728 return toinsert;
729 729 };
730 730
731 731
732 732 OutputArea.prototype.append_raw_input = function (msg) {
733 733 var that = this;
734 734 this.expand();
735 735 var content = msg.content;
736 736 var area = this.create_output_area();
737 737
738 738 // disable any other raw_inputs, if they are left around
739 739 $("div.output_subarea.raw_input_container").remove();
740 740
741 741 var input_type = content.password ? 'password' : 'text';
742 742
743 743 area.append(
744 744 $("<div/>")
745 745 .addClass("box-flex1 output_subarea raw_input_container")
746 746 .append(
747 747 $("<span/>")
748 748 .addClass("raw_input_prompt")
749 749 .text(content.prompt)
750 750 )
751 751 .append(
752 752 $("<input/>")
753 753 .addClass("raw_input")
754 754 .attr('type', input_type)
755 755 .attr("size", 47)
756 756 .keydown(function (event, ui) {
757 757 // make sure we submit on enter,
758 758 // and don't re-execute the *cell* on shift-enter
759 759 if (event.which === keyboard.keycodes.enter) {
760 760 that._submit_raw_input();
761 761 return false;
762 762 }
763 763 })
764 764 )
765 765 );
766 766
767 767 this.element.append(area);
768 768 var raw_input = area.find('input.raw_input');
769 769 // Register events that enable/disable the keyboard manager while raw
770 770 // input is focused.
771 771 this.keyboard_manager.register_events(raw_input);
772 772 // Note, the following line used to read raw_input.focus().focus().
773 773 // This seemed to be needed otherwise only the cell would be focused.
774 774 // But with the modal UI, this seems to work fine with one call to focus().
775 775 raw_input.focus();
776 776 }
777 777
778 778 OutputArea.prototype._submit_raw_input = function (evt) {
779 779 var container = this.element.find("div.raw_input_container");
780 780 var theprompt = container.find("span.raw_input_prompt");
781 781 var theinput = container.find("input.raw_input");
782 782 var value = theinput.val();
783 783 var echo = value;
784 784 // don't echo if it's a password
785 785 if (theinput.attr('type') == 'password') {
786 786 echo = 'Β·Β·Β·Β·Β·Β·Β·Β·';
787 787 }
788 788 var content = {
789 789 output_type : 'stream',
790 790 stream : 'stdout',
791 791 text : theprompt.text() + echo + '\n'
792 792 }
793 793 // remove form container
794 794 container.parent().remove();
795 795 // replace with plaintext version in stdout
796 796 this.append_output(content, false);
797 797 this.events.trigger('send_input_reply.Kernel', value);
798 798 }
799 799
800 800
801 801 OutputArea.prototype.handle_clear_output = function (msg) {
802 802 // msg spec v4 had stdout, stderr, display keys
803 803 // v4.1 replaced these with just wait
804 804 // The default behavior is the same (stdout=stderr=display=True, wait=False),
805 805 // so v4 messages will still be properly handled,
806 806 // except for the rarely used clearing less than all output.
807 807 this.clear_output(msg.content.wait || false);
808 808 };
809 809
810 810
811 811 OutputArea.prototype.clear_output = function(wait) {
812 812 if (wait) {
813 813
814 814 // If a clear is queued, clear before adding another to the queue.
815 815 if (this.clear_queued) {
816 816 this.clear_output(false);
817 817 };
818 818
819 819 this.clear_queued = true;
820 820 } else {
821 821
822 822 // Fix the output div's height if the clear_output is waiting for
823 823 // new output (it is being used in an animation).
824 824 if (this.clear_queued) {
825 825 var height = this.element.height();
826 826 this.element.height(height);
827 827 this.clear_queued = false;
828 828 }
829 829
830 830 // Clear all
831 831 // Remove load event handlers from img tags because we don't want
832 832 // them to fire if the image is never added to the page.
833 833 this.element.find('img').off('load');
834 834 this.element.html("");
835 835 this.outputs = [];
836 836 this.trusted = true;
837 837 this.unscroll_area();
838 838 return;
839 839 };
840 840 };
841 841
842 842
843 843 // JSON serialization
844 844
845 845 OutputArea.prototype.fromJSON = function (outputs) {
846 846 var len = outputs.length;
847 847 var data;
848 848
849 849 for (var i=0; i<len; i++) {
850 850 data = outputs[i];
851 851 var msg_type = data.output_type;
852 852 if (msg_type == "pyout") {
853 853 // pyout message has been renamed to execute_result,
854 854 // but the nbformat has not been updated,
855 855 // so transform back to pyout for json.
856 856 msg_type = data.output_type = "execute_result";
857 857 } else if (msg_type == "pyerr") {
858 858 // pyerr message has been renamed to error,
859 859 // but the nbformat has not been updated,
860 860 // so transform back to pyerr for json.
861 861 msg_type = data.output_type = "error";
862 862 }
863 863 if (msg_type === "display_data" || msg_type === "execute_result") {
864 864 // convert short keys to mime keys
865 865 // TODO: remove mapping of short keys when we update to nbformat 4
866 866 data = this.rename_keys(data, OutputArea.mime_map_r);
867 867 data.metadata = this.rename_keys(data.metadata, OutputArea.mime_map_r);
868 868 // msg spec JSON is an object, nbformat v3 JSON is a JSON string
869 869 if (data["application/json"] !== undefined && typeof data["application/json"] === 'string') {
870 870 data["application/json"] = JSON.parse(data["application/json"]);
871 871 }
872 872 }
873 873
874 874 this.append_output(data);
875 875 }
876 876 };
877 877
878 878
879 879 OutputArea.prototype.toJSON = function () {
880 880 var outputs = [];
881 881 var len = this.outputs.length;
882 882 var data;
883 883 for (var i=0; i<len; i++) {
884 884 data = this.outputs[i];
885 885 var msg_type = data.output_type;
886 886 if (msg_type === "display_data" || msg_type === "execute_result") {
887 887 // convert mime keys to short keys
888 888 data = this.rename_keys(data, OutputArea.mime_map);
889 889 data.metadata = this.rename_keys(data.metadata, OutputArea.mime_map);
890 890 // msg spec JSON is an object, nbformat v3 JSON is a JSON string
891 891 if (data.json !== undefined && typeof data.json !== 'string') {
892 892 data.json = JSON.stringify(data.json);
893 893 }
894 894 }
895 895 if (msg_type == "execute_result") {
896 896 // pyout message has been renamed to execute_result,
897 897 // but the nbformat has not been updated,
898 898 // so transform back to pyout for json.
899 899 data.output_type = "pyout";
900 900 } else if (msg_type == "error") {
901 901 // pyerr message has been renamed to error,
902 902 // but the nbformat has not been updated,
903 903 // so transform back to pyerr for json.
904 904 data.output_type = "pyerr";
905 905 }
906 906 outputs[i] = data;
907 907 }
908 908 return outputs;
909 909 };
910 910
911 911 /**
912 912 * Class properties
913 913 **/
914 914
915 915 /**
916 916 * Threshold to trigger autoscroll when the OutputArea is resized,
917 917 * typically when new outputs are added.
918 918 *
919 919 * Behavior is undefined if autoscroll is lower than minimum_scroll_threshold,
920 920 * unless it is < 0, in which case autoscroll will never be triggered
921 921 *
922 922 * @property auto_scroll_threshold
923 923 * @type Number
924 924 * @default 100
925 925 *
926 926 **/
927 927 OutputArea.auto_scroll_threshold = 100;
928 928
929 929 /**
930 930 * Lower limit (in lines) for OutputArea to be made scrollable. OutputAreas
931 931 * shorter than this are never scrolled.
932 932 *
933 933 * @property minimum_scroll_threshold
934 934 * @type Number
935 935 * @default 20
936 936 *
937 937 **/
938 938 OutputArea.minimum_scroll_threshold = 20;
939 939
940 940
941 941
942 942 OutputArea.mime_map = {
943 943 "text/plain" : "text",
944 944 "text/html" : "html",
945 945 "image/svg+xml" : "svg",
946 946 "image/png" : "png",
947 947 "image/jpeg" : "jpeg",
948 948 "text/latex" : "latex",
949 949 "application/json" : "json",
950 950 "application/javascript" : "javascript",
951 951 };
952 952
953 953 OutputArea.mime_map_r = {
954 954 "text" : "text/plain",
955 955 "html" : "text/html",
956 956 "svg" : "image/svg+xml",
957 957 "png" : "image/png",
958 958 "jpeg" : "image/jpeg",
959 959 "latex" : "text/latex",
960 960 "json" : "application/json",
961 961 "javascript" : "application/javascript",
962 962 };
963 963
964 964 OutputArea.display_order = [
965 965 'application/javascript',
966 966 'text/html',
967 967 'text/markdown',
968 968 'text/latex',
969 969 'image/svg+xml',
970 970 'image/png',
971 971 'image/jpeg',
972 972 'application/pdf',
973 973 'text/plain'
974 974 ];
975 975
976 976 OutputArea.append_map = {
977 977 "text/plain" : append_text,
978 978 "text/html" : append_html,
979 979 "text/markdown": append_markdown,
980 980 "image/svg+xml" : append_svg,
981 981 "image/png" : append_png,
982 982 "image/jpeg" : append_jpeg,
983 983 "text/latex" : append_latex,
984 984 "application/javascript" : append_javascript,
985 985 "application/pdf" : append_pdf
986 986 };
987 987
988 988 // For backwards compatability.
989 989 IPython.OutputArea = OutputArea;
990 990
991 991 return {'OutputArea': OutputArea};
992 992 });
@@ -1,177 +1,177 b''
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 4 define([
5 5 'base/js/namespace',
6 6 'jquery',
7 7 'base/js/utils',
8 8 'base/js/dialog',
9 9 'base/js/keyboard',
10 'dateformat/date.format',
10 'dateformat',
11 11 ], function(IPython, $, utils, dialog, keyboard) {
12 12 "use strict";
13 13
14 14 var SaveWidget = function (selector, options) {
15 15 // TODO: Remove circulat ref.
16 16 this.notebook = undefined;
17 17 this.selector = selector;
18 18 this.events = options.events;
19 19 this.keyboard_manager = options.keyboard_manager;
20 20 if (this.selector !== undefined) {
21 21 this.element = $(selector);
22 22 this.style();
23 23 this.bind_events();
24 24 }
25 25 };
26 26
27 27 SaveWidget.prototype.style = function () {
28 28 };
29 29
30 30
31 31 SaveWidget.prototype.bind_events = function () {
32 32 var that = this;
33 33 this.element.find('span#notebook_name').click(function () {
34 34 that.rename_notebook();
35 35 });
36 36 this.element.find('span#notebook_name').hover(function () {
37 37 $(this).addClass("ui-state-hover");
38 38 }, function () {
39 39 $(this).removeClass("ui-state-hover");
40 40 });
41 41 this.events.on('notebook_loaded.Notebook', function () {
42 42 that.update_notebook_name();
43 43 that.update_document_title();
44 44 });
45 45 this.events.on('notebook_saved.Notebook', function () {
46 46 that.update_notebook_name();
47 47 that.update_document_title();
48 48 });
49 49 this.events.on('notebook_renamed.Notebook', function () {
50 50 that.update_notebook_name();
51 51 that.update_document_title();
52 52 that.update_address_bar();
53 53 });
54 54 this.events.on('notebook_save_failed.Notebook', function () {
55 55 that.set_save_status('Autosave Failed!');
56 56 });
57 57 this.events.on('checkpoints_listed.Notebook', function (event, data) {
58 58 that.set_last_checkpoint(data[0]);
59 59 });
60 60
61 61 this.events.on('checkpoint_created.Notebook', function (event, data) {
62 62 that.set_last_checkpoint(data);
63 63 });
64 64 this.events.on('set_dirty.Notebook', function (event, data) {
65 65 that.set_autosaved(data.value);
66 66 });
67 67 };
68 68
69 69
70 70 SaveWidget.prototype.rename_notebook = function (options) {
71 71 options = options || {};
72 72 var that = this;
73 73 var dialog_body = $('<div/>').append(
74 74 $("<p/>").addClass("rename-message")
75 75 .text('Enter a new notebook name:')
76 76 ).append(
77 77 $("<br/>")
78 78 ).append(
79 79 $('<input/>').attr('type','text').attr('size','25').addClass('form-control')
80 80 .val(that.notebook.get_notebook_name())
81 81 );
82 82 dialog.modal({
83 83 title: "Rename Notebook",
84 84 body: dialog_body,
85 85 notebook: options.notebook,
86 86 keyboard_manager: this.keyboard_manager,
87 87 buttons : {
88 88 "Cancel": {},
89 89 "OK": {
90 90 class: "btn-primary",
91 91 click: function () {
92 92 var new_name = $(this).find('input').val();
93 93 if (!that.notebook.test_notebook_name(new_name)) {
94 94 $(this).find('.rename-message').text(
95 95 "Invalid notebook name. Notebook names must "+
96 96 "have 1 or more characters and can contain any characters " +
97 97 "except :/\\. Please enter a new notebook name:"
98 98 );
99 99 return false;
100 100 } else {
101 101 that.notebook.rename(new_name);
102 102 }
103 103 }}
104 104 },
105 105 open : function (event, ui) {
106 106 var that = $(this);
107 107 // Upon ENTER, click the OK button.
108 108 that.find('input[type="text"]').keydown(function (event, ui) {
109 109 if (event.which === keyboard.keycodes.enter) {
110 110 that.find('.btn-primary').first().click();
111 111 return false;
112 112 }
113 113 });
114 114 that.find('input[type="text"]').focus().select();
115 115 }
116 116 });
117 117 };
118 118
119 119
120 120 SaveWidget.prototype.update_notebook_name = function () {
121 121 var nbname = this.notebook.get_notebook_name();
122 122 this.element.find('span#notebook_name').text(nbname);
123 123 };
124 124
125 125
126 126 SaveWidget.prototype.update_document_title = function () {
127 127 var nbname = this.notebook.get_notebook_name();
128 128 document.title = nbname;
129 129 };
130 130
131 131 SaveWidget.prototype.update_address_bar = function(){
132 132 var base_url = this.notebook.base_url;
133 133 var nbname = this.notebook.notebook_name;
134 134 var path = this.notebook.notebook_path;
135 135 var state = {path : path, name: nbname};
136 136 window.history.replaceState(state, "", utils.url_join_encode(
137 137 base_url,
138 138 "notebooks",
139 139 path,
140 140 nbname)
141 141 );
142 142 };
143 143
144 144
145 145 SaveWidget.prototype.set_save_status = function (msg) {
146 146 this.element.find('span#autosave_status').text(msg);
147 147 };
148 148
149 149 SaveWidget.prototype.set_checkpoint_status = function (msg) {
150 150 this.element.find('span#checkpoint_status').text(msg);
151 151 };
152 152
153 153 SaveWidget.prototype.set_last_checkpoint = function (checkpoint) {
154 154 if (!checkpoint) {
155 155 this.set_checkpoint_status("");
156 156 return;
157 157 }
158 158 var d = new Date(checkpoint.last_modified);
159 159 this.set_checkpoint_status(
160 160 "Last Checkpoint: " + d.format('mmm dd HH:MM')
161 161 );
162 162 };
163 163
164 164 SaveWidget.prototype.set_autosaved = function (dirty) {
165 165 if (dirty) {
166 166 this.set_save_status("(unsaved changes)");
167 167 } else {
168 168 this.set_save_status("(autosaved)");
169 169 }
170 170 };
171 171
172 172 // Backwards compatability.
173 173 IPython.SaveWidget = SaveWidget;
174 174
175 175 return {'SaveWidget': SaveWidget};
176 176
177 177 });
@@ -1,461 +1,464 b''
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 4 define([
5 5 'base/js/namespace',
6 6 'jquery',
7 7 'notebook/js/cell',
8 8 'base/js/security',
9 9 'notebook/js/mathjaxutils',
10 10 'notebook/js/celltoolbar',
11 11 'components/marked/lib/marked',
12 12 ], function(IPython, $, cell, security, mathjaxutils, celltoolbar, marked) {
13 13 "use strict";
14 14 var Cell = cell.Cell;
15 15
16 16 var TextCell = function (options) {
17 17 // Constructor
18 18 //
19 19 // Construct a new TextCell, codemirror mode is by default 'htmlmixed',
20 20 // and cell type is 'text' cell start as not redered.
21 21 //
22 22 // Parameters:
23 23 // options: dictionary
24 24 // Dictionary of keyword arguments.
25 25 // events: $(Events) instance
26 26 // config: dictionary
27 27 // keyboard_manager: KeyboardManager instance
28 28 // notebook: Notebook instance
29 29 options = options || {};
30 30
31 31 // in all TextCell/Cell subclasses
32 32 // do not assign most of members here, just pass it down
33 33 // in the options dict potentially overwriting what you wish.
34 34 // they will be assigned in the base class.
35 35 this.notebook = options.notebook;
36 36 this.events = options.events;
37 37 this.config = options.config;
38 38
39 39 // we cannot put this as a class key as it has handle to "this".
40 40 var cm_overwrite_options = {
41 41 onKeyEvent: $.proxy(this.handle_keyevent,this)
42 42 };
43 43 var config = this.mergeopt(TextCell, this.config, {cm_config:cm_overwrite_options});
44 44 Cell.apply(this, [{
45 45 config: config,
46 46 keyboard_manager: options.keyboard_manager,
47 47 events: this.events}]);
48 48
49 49 this.cell_type = this.cell_type || 'text';
50 50 mathjaxutils = mathjaxutils;
51 51 this.rendered = false;
52 52 };
53 53
54 54 TextCell.prototype = new Cell();
55 55
56 56 TextCell.options_default = {
57 57 cm_config : {
58 58 extraKeys: {"Tab": "indentMore","Shift-Tab" : "indentLess"},
59 59 mode: 'htmlmixed',
60 60 lineWrapping : true,
61 61 }
62 62 };
63 63
64 64
65 65 /**
66 66 * Create the DOM element of the TextCell
67 67 * @method create_element
68 68 * @private
69 69 */
70 70 TextCell.prototype.create_element = function () {
71 71 Cell.prototype.create_element.apply(this, arguments);
72 72
73 73 var cell = $("<div>").addClass('cell text_cell border-box-sizing');
74 74 cell.attr('tabindex','2');
75 75
76 76 var prompt = $('<div/>').addClass('prompt input_prompt');
77 77 cell.append(prompt);
78 78 var inner_cell = $('<div/>').addClass('inner_cell');
79 this.celltoolbar = new celltoolbar.CellToolbar(this, this.events, this.notebook);
79 this.celltoolbar = new celltoolbar.CellToolbar({
80 cell: this,
81 events: this.events,
82 notebook: this.notebook});
80 83 inner_cell.append(this.celltoolbar.element);
81 84 var input_area = $('<div/>').addClass('input_area');
82 85 this.code_mirror = new CodeMirror(input_area.get(0), this.cm_config);
83 86 // The tabindex=-1 makes this div focusable.
84 87 var render_area = $('<div/>').addClass('text_cell_render border-box-sizing').
85 88 addClass('rendered_html').attr('tabindex','-1');
86 89 inner_cell.append(input_area).append(render_area);
87 90 cell.append(inner_cell);
88 91 this.element = cell;
89 92 };
90 93
91 94
92 95 /**
93 96 * Bind the DOM evet to cell actions
94 97 * Need to be called after TextCell.create_element
95 98 * @private
96 99 * @method bind_event
97 100 */
98 101 TextCell.prototype.bind_events = function () {
99 102 Cell.prototype.bind_events.apply(this);
100 103 var that = this;
101 104
102 105 this.element.dblclick(function () {
103 106 if (that.selected === false) {
104 107 this.events.trigger('select.Cell', {'cell':that});
105 108 }
106 109 var cont = that.unrender();
107 110 if (cont) {
108 111 that.focus_editor();
109 112 }
110 113 });
111 114 };
112 115
113 116 // Cell level actions
114 117
115 118 TextCell.prototype.select = function () {
116 119 var cont = Cell.prototype.select.apply(this);
117 120 if (cont) {
118 121 if (this.mode === 'edit') {
119 122 this.code_mirror.refresh();
120 123 }
121 124 }
122 125 return cont;
123 126 };
124 127
125 128 TextCell.prototype.unrender = function () {
126 129 if (this.read_only) return;
127 130 var cont = Cell.prototype.unrender.apply(this);
128 131 if (cont) {
129 132 var text_cell = this.element;
130 133 var output = text_cell.find("div.text_cell_render");
131 134 output.hide();
132 135 text_cell.find('div.input_area').show();
133 136 if (this.get_text() === this.placeholder) {
134 137 this.set_text('');
135 138 }
136 139 this.refresh();
137 140 }
138 141 if (this.celltoolbar.ui_controls_list.length) {
139 142 this.celltoolbar.show();
140 143 }
141 144 return cont;
142 145 };
143 146
144 147 TextCell.prototype.execute = function () {
145 148 this.render();
146 149 };
147 150
148 151 /**
149 152 * setter: {{#crossLink "TextCell/set_text"}}{{/crossLink}}
150 153 * @method get_text
151 154 * @retrun {string} CodeMirror current text value
152 155 */
153 156 TextCell.prototype.get_text = function() {
154 157 return this.code_mirror.getValue();
155 158 };
156 159
157 160 /**
158 161 * @param {string} text - Codemiror text value
159 162 * @see TextCell#get_text
160 163 * @method set_text
161 164 * */
162 165 TextCell.prototype.set_text = function(text) {
163 166 this.code_mirror.setValue(text);
164 167 this.code_mirror.refresh();
165 168 };
166 169
167 170 /**
168 171 * setter :{{#crossLink "TextCell/set_rendered"}}{{/crossLink}}
169 172 * @method get_rendered
170 173 * */
171 174 TextCell.prototype.get_rendered = function() {
172 175 return this.element.find('div.text_cell_render').html();
173 176 };
174 177
175 178 /**
176 179 * @method set_rendered
177 180 */
178 181 TextCell.prototype.set_rendered = function(text) {
179 182 this.element.find('div.text_cell_render').html(text);
180 183 this.celltoolbar.hide();
181 184 };
182 185
183 186
184 187 /**
185 188 * Create Text cell from JSON
186 189 * @param {json} data - JSON serialized text-cell
187 190 * @method fromJSON
188 191 */
189 192 TextCell.prototype.fromJSON = function (data) {
190 193 Cell.prototype.fromJSON.apply(this, arguments);
191 194 if (data.cell_type === this.cell_type) {
192 195 if (data.source !== undefined) {
193 196 this.set_text(data.source);
194 197 // make this value the starting point, so that we can only undo
195 198 // to this state, instead of a blank cell
196 199 this.code_mirror.clearHistory();
197 200 // TODO: This HTML needs to be treated as potentially dangerous
198 201 // user input and should be handled before set_rendered.
199 202 this.set_rendered(data.rendered || '');
200 203 this.rendered = false;
201 204 this.render();
202 205 }
203 206 }
204 207 };
205 208
206 209 /** Generate JSON from cell
207 210 * @return {object} cell data serialised to json
208 211 */
209 212 TextCell.prototype.toJSON = function () {
210 213 var data = Cell.prototype.toJSON.apply(this);
211 214 data.source = this.get_text();
212 215 if (data.source == this.placeholder) {
213 216 data.source = "";
214 217 }
215 218 return data;
216 219 };
217 220
218 221
219 222 var MarkdownCell = function (options) {
220 223 // Constructor
221 224 //
222 225 // Parameters:
223 226 // options: dictionary
224 227 // Dictionary of keyword arguments.
225 228 // events: $(Events) instance
226 229 // config: dictionary
227 230 // keyboard_manager: KeyboardManager instance
228 231 // notebook: Notebook instance
229 232 options = options || {};
230 233 var config = this.mergeopt(MarkdownCell, options.config);
231 234 TextCell.apply(this, [$.extend({}, options, {config: config})]);
232 235
233 236 this.cell_type = 'markdown';
234 237 };
235 238
236 239 MarkdownCell.options_default = {
237 240 cm_config: {
238 241 mode: 'ipythongfm'
239 242 },
240 243 placeholder: "Type *Markdown* and LaTeX: $\\alpha^2$"
241 244 };
242 245
243 246 MarkdownCell.prototype = new TextCell();
244 247
245 248 /**
246 249 * @method render
247 250 */
248 251 MarkdownCell.prototype.render = function () {
249 252 var cont = TextCell.prototype.render.apply(this);
250 253 if (cont) {
251 254 var text = this.get_text();
252 255 var math = null;
253 256 if (text === "") { text = this.placeholder; }
254 257 var text_and_math = mathjaxutils.remove_math(text);
255 258 text = text_and_math[0];
256 259 math = text_and_math[1];
257 260 var html = marked.parser(marked.lexer(text));
258 261 html = mathjaxutils.replace_math(html, math);
259 262 html = security.sanitize_html(html);
260 263 html = $($.parseHTML(html));
261 264 // links in markdown cells should open in new tabs
262 265 html.find("a[href]").not('[href^="#"]').attr("target", "_blank");
263 266 this.set_rendered(html);
264 267 this.element.find('div.input_area').hide();
265 268 this.element.find("div.text_cell_render").show();
266 269 this.typeset();
267 270 }
268 271 return cont;
269 272 };
270 273
271 274
272 275 var RawCell = function (options) {
273 276 // Constructor
274 277 //
275 278 // Parameters:
276 279 // options: dictionary
277 280 // Dictionary of keyword arguments.
278 281 // events: $(Events) instance
279 282 // config: dictionary
280 283 // keyboard_manager: KeyboardManager instance
281 284 // notebook: Notebook instance
282 285 options = options || {};
283 286 var config = this.mergeopt(RawCell, options.config);
284 287 TextCell.apply(this, [$.extend({}, options, {config: config})]);
285 288
286 289 // RawCell should always hide its rendered div
287 290 this.element.find('div.text_cell_render').hide();
288 291 this.cell_type = 'raw';
289 292 };
290 293
291 294 RawCell.options_default = {
292 295 placeholder : "Write raw LaTeX or other formats here, for use with nbconvert. " +
293 296 "It will not be rendered in the notebook. " +
294 297 "When passing through nbconvert, a Raw Cell's content is added to the output unmodified."
295 298 };
296 299
297 300 RawCell.prototype = new TextCell();
298 301
299 302 /** @method bind_events **/
300 303 RawCell.prototype.bind_events = function () {
301 304 TextCell.prototype.bind_events.apply(this);
302 305 var that = this;
303 306 this.element.focusout(function() {
304 307 that.auto_highlight();
305 308 that.render();
306 309 });
307 310
308 311 this.code_mirror.on('focus', function() { that.unrender(); });
309 312 };
310 313
311 314 /**
312 315 * Trigger autodetection of highlight scheme for current cell
313 316 * @method auto_highlight
314 317 */
315 318 RawCell.prototype.auto_highlight = function () {
316 319 this._auto_highlight(this.config.raw_cell_highlight);
317 320 };
318 321
319 322 /** @method render **/
320 323 RawCell.prototype.render = function () {
321 324 var cont = TextCell.prototype.render.apply(this);
322 325 if (cont){
323 326 var text = this.get_text();
324 327 if (text === "") { text = this.placeholder; }
325 328 this.set_text(text);
326 329 this.element.removeClass('rendered');
327 330 }
328 331 return cont;
329 332 };
330 333
331 334
332 335 var HeadingCell = function (options) {
333 336 // Constructor
334 337 //
335 338 // Parameters:
336 339 // options: dictionary
337 340 // Dictionary of keyword arguments.
338 341 // events: $(Events) instance
339 342 // config: dictionary
340 343 // keyboard_manager: KeyboardManager instance
341 344 // notebook: Notebook instance
342 345 options = options || {};
343 346 var config = this.mergeopt(HeadingCell, options.config);
344 347 TextCell.apply(this, [$.extend({}, options, {config: config})]);
345 348
346 349 this.level = 1;
347 350 this.cell_type = 'heading';
348 351 };
349 352
350 353 HeadingCell.options_default = {
351 354 placeholder: "Type Heading Here"
352 355 };
353 356
354 357 HeadingCell.prototype = new TextCell();
355 358
356 359 /** @method fromJSON */
357 360 HeadingCell.prototype.fromJSON = function (data) {
358 361 if (data.level !== undefined){
359 362 this.level = data.level;
360 363 }
361 364 TextCell.prototype.fromJSON.apply(this, arguments);
362 365 };
363 366
364 367
365 368 /** @method toJSON */
366 369 HeadingCell.prototype.toJSON = function () {
367 370 var data = TextCell.prototype.toJSON.apply(this);
368 371 data.level = this.get_level();
369 372 return data;
370 373 };
371 374
372 375 /**
373 376 * can the cell be split into two cells
374 377 * @method is_splittable
375 378 **/
376 379 HeadingCell.prototype.is_splittable = function () {
377 380 return false;
378 381 };
379 382
380 383
381 384 /**
382 385 * can the cell be merged with other cells
383 386 * @method is_mergeable
384 387 **/
385 388 HeadingCell.prototype.is_mergeable = function () {
386 389 return false;
387 390 };
388 391
389 392 /**
390 393 * Change heading level of cell, and re-render
391 394 * @method set_level
392 395 */
393 396 HeadingCell.prototype.set_level = function (level) {
394 397 this.level = level;
395 398 if (this.rendered) {
396 399 this.rendered = false;
397 400 this.render();
398 401 }
399 402 };
400 403
401 404 /** The depth of header cell, based on html (h1 to h6)
402 405 * @method get_level
403 406 * @return {integer} level - for 1 to 6
404 407 */
405 408 HeadingCell.prototype.get_level = function () {
406 409 return this.level;
407 410 };
408 411
409 412
410 413 HeadingCell.prototype.get_rendered = function () {
411 414 var r = this.element.find("div.text_cell_render");
412 415 return r.children().first().html();
413 416 };
414 417
415 418 HeadingCell.prototype.render = function () {
416 419 var cont = TextCell.prototype.render.apply(this);
417 420 if (cont) {
418 421 var text = this.get_text();
419 422 var math = null;
420 423 // Markdown headings must be a single line
421 424 text = text.replace(/\n/g, ' ');
422 425 if (text === "") { text = this.placeholder; }
423 426 text = new Array(this.level + 1).join("#") + " " + text;
424 427 var text_and_math = mathjaxutils.remove_math(text);
425 428 text = text_and_math[0];
426 429 math = text_and_math[1];
427 430 var html = marked.parser(marked.lexer(text));
428 431 html = mathjaxutils.replace_math(html, math);
429 432 html = security.sanitize_html(html);
430 433 var h = $($.parseHTML(html));
431 434 // add id and linkback anchor
432 435 var hash = h.text().replace(/ /g, '-');
433 436 h.attr('id', hash);
434 437 h.append(
435 438 $('<a/>')
436 439 .addClass('anchor-link')
437 440 .attr('href', '#' + hash)
438 441 .text('ΒΆ')
439 442 );
440 443 this.set_rendered(h);
441 444 this.element.find('div.input_area').hide();
442 445 this.element.find("div.text_cell_render").show();
443 446 this.typeset();
444 447 }
445 448 return cont;
446 449 };
447 450
448 451 // Backwards compatability.
449 452 IPython.TextCell = TextCell;
450 453 IPython.MarkdownCell = MarkdownCell;
451 454 IPython.RawCell = RawCell;
452 455 IPython.HeadingCell = HeadingCell;
453 456
454 var Cells = {
457 var textcell = {
455 458 'TextCell': TextCell,
456 459 'MarkdownCell': MarkdownCell,
457 460 'RawCell': RawCell,
458 461 'HeadingCell': HeadingCell,
459 462 };
460 return Cells;
463 return textcell;
461 464 });
@@ -1,91 +1,95 b''
1 1 <!DOCTYPE HTML>
2 2 <html>
3 3
4 4 <head>
5 5 <meta charset="utf-8">
6 6
7 7 <title>{% block title %}IPython Notebook{% endblock %}</title>
8 8 <link rel="shortcut icon" type="image/x-icon" href="{{static_url("base/images/favicon.ico") }}">
9 9 <meta http-equiv="X-UA-Compatible" content="chrome=1">
10 10 <link rel="stylesheet" href="{{static_url("components/jquery-ui/themes/smoothness/jquery-ui.min.css") }}" type="text/css" />
11 11 <meta name="viewport" content="width=device-width, initial-scale=1.0">
12 12
13 13 {% block stylesheet %}
14 14 <link rel="stylesheet" href="{{ static_url("style/style.min.css") }}" type="text/css"/>
15 15 {% endblock %}
16 16 <link rel="stylesheet" href="{{ static_url("custom/custom.css") }}" type="text/css" />
17 17 <script src="{{static_url("components/requirejs/require.js") }}" type="text/javascript" charset="utf-8"></script>
18 18 <script>
19 19 require.config({
20 20 baseUrl: '{{static_url("", include_version=False)}}',
21 21 paths: {
22 22 nbextensions : '{{ base_url }}nbextensions',
23 23 underscore : 'components/underscore/underscore-min',
24 24 backbone : 'components/backbone/backbone-min',
25 25 jquery: 'components/jquery/jquery.min',
26 26 bootstraptour: 'components/bootstrap-tour/build/js/bootstrap-tour.min',
27 dateformat: 'dateformat/date.format',
27 28 },
28 29 shim: {
29 30 underscore: {
30 31 exports: '_'
31 32 },
32 33 backbone: {
33 34 deps: ["underscore", "jquery"],
34 35 exports: "Backbone"
35 36 },
36 37 bootstraptour: {
37 38 exports: "Tour"
39 },
40 dateformat: {
41 exports: "dateFormat"
38 42 }
39 43 }
40 44 });
41 45 </script>
42 46
43 47 {% block meta %}
44 48 {% endblock %}
45 49
46 50 </head>
47 51
48 52 <body {% block params %}{% endblock %}>
49 53
50 54 <noscript>
51 55 <div id='noscript'>
52 56 IPython Notebook requires JavaScript.<br>
53 57 Please enable it to proceed.
54 58 </div>
55 59 </noscript>
56 60
57 61 <div id="header" class="navbar navbar-static-top">
58 62 <div class="container">
59 63 <div id="ipython_notebook" class="nav navbar-brand pull-left"><a href="{{base_url}}tree/{{notebook_path}}" alt='dashboard'><img src='{{static_url("base/images/ipynblogo.png") }}' alt='IPython Notebook'/></a></div>
60 64
61 65 {% block login_widget %}
62 66
63 67 <span id="login_widget">
64 68 {% if logged_in %}
65 69 <button id="logout">Logout</button>
66 70 {% elif login_available and not logged_in %}
67 71 <button id="login">Login</button>
68 72 {% endif %}
69 73 </span>
70 74
71 75 {% endblock %}
72 76
73 77 {% block header %}
74 78 {% endblock %}
75 79 </div>
76 80 </div>
77 81
78 82 <div id="site">
79 83 {% block site %}
80 84 {% endblock %}
81 85 </div>
82 86
83 87 {% block script %}
84 88 <script src="{{static_url("base/js/pagemain.js") }}" type="text/javascript" charset="utf-8"></script>
85 89 {% endblock %}
86 90
87 91 <script src="{{static_url("custom/custom.js") }}" type="text/javascript" charset="utf-8"></script>
88 92
89 93 </body>
90 94
91 95 </html>
General Comments 0
You need to be logged in to leave comments. Login now