##// END OF EJS Templates
More review changes
Jonathan Frederic -
Show More
@@ -1,161 +1,163 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 define([
4 define([
5 'base/js/namespace',
5 'base/js/namespace',
6 'jquery',
6 'jquery',
7 ], function(IPython, $) {
7 ], function(IPython, $) {
8 "use strict";
8 "use strict";
9
9
10 var modal = function (options) {
10 var modal = function (options) {
11
11
12 var modal = $("<div/>")
12 var modal = $("<div/>")
13 .addClass("modal")
13 .addClass("modal")
14 .addClass("fade")
14 .addClass("fade")
15 .attr("role", "dialog");
15 .attr("role", "dialog");
16 var dialog = $("<div/>")
16 var dialog = $("<div/>")
17 .addClass("modal-dialog")
17 .addClass("modal-dialog")
18 .appendTo(modal);
18 .appendTo(modal);
19 var dialog_content = $("<div/>")
19 var dialog_content = $("<div/>")
20 .addClass("modal-content")
20 .addClass("modal-content")
21 .appendTo(dialog);
21 .appendTo(dialog);
22 dialog_content.append(
22 dialog_content.append(
23 $("<div/>")
23 $("<div/>")
24 .addClass("modal-header")
24 .addClass("modal-header")
25 .append($("<button>")
25 .append($("<button>")
26 .attr("type", "button")
26 .attr("type", "button")
27 .addClass("close")
27 .addClass("close")
28 .attr("data-dismiss", "modal")
28 .attr("data-dismiss", "modal")
29 .attr("aria-hidden", "true")
29 .attr("aria-hidden", "true")
30 .html("&times;")
30 .html("&times;")
31 ).append(
31 ).append(
32 $("<h4/>")
32 $("<h4/>")
33 .addClass('modal-title')
33 .addClass('modal-title')
34 .text(options.title || "")
34 .text(options.title || "")
35 )
35 )
36 ).append(
36 ).append(
37 $("<div/>").addClass("modal-body").append(
37 $("<div/>").addClass("modal-body").append(
38 options.body || $("<p/>")
38 options.body || $("<p/>")
39 )
39 )
40 );
40 );
41
41
42 var footer = $("<div/>").addClass("modal-footer");
42 var footer = $("<div/>").addClass("modal-footer");
43
43
44 for (var label in options.buttons) {
44 for (var label in options.buttons) {
45 var btn_opts = options.buttons[label];
45 var btn_opts = options.buttons[label];
46 var button = $("<button/>")
46 var button = $("<button/>")
47 .addClass("btn btn-default btn-sm")
47 .addClass("btn btn-default btn-sm")
48 .attr("data-dismiss", "modal")
48 .attr("data-dismiss", "modal")
49 .text(label);
49 .text(label);
50 if (btn_opts.click) {
50 if (btn_opts.click) {
51 button.click($.proxy(btn_opts.click, dialog_content));
51 button.click($.proxy(btn_opts.click, dialog_content));
52 }
52 }
53 if (btn_opts.class) {
53 if (btn_opts.class) {
54 button.addClass(btn_opts.class);
54 button.addClass(btn_opts.class);
55 }
55 }
56 footer.append(button);
56 footer.append(button);
57 }
57 }
58 dialog_content.append(footer);
58 dialog_content.append(footer);
59 // hook up on-open event
59 // hook up on-open event
60 modal.on("shown.bs.modal", function() {
60 modal.on("shown.bs.modal", function() {
61 setTimeout(function() {
61 setTimeout(function() {
62 footer.find("button").last().focus();
62 footer.find("button").last().focus();
63 if (options.open) {
63 if (options.open) {
64 $.proxy(options.open, modal)();
64 $.proxy(options.open, modal)();
65 }
65 }
66 }, 0);
66 }, 0);
67 });
67 });
68
68
69 // destroy modal on hide, unless explicitly asked not to
69 // destroy modal on hide, unless explicitly asked not to
70 if (options.destroy === undefined || options.destroy) {
70 if (options.destroy === undefined || options.destroy) {
71 modal.on("hidden.bs.modal", function () {
71 modal.on("hidden.bs.modal", function () {
72 modal.remove();
72 modal.remove();
73 });
73 });
74 }
74 }
75 modal.on("hidden.bs.modal", function () {
75 modal.on("hidden.bs.modal", function () {
76 if (options.notebook) {
76 if (options.notebook) {
77 var cell = options.notebook.get_selected_cell();
77 var cell = options.notebook.get_selected_cell();
78 if (cell) cell.select();
78 if (cell) cell.select();
79 }
79 }
80 if (options.keyboard_manager) {
80 if (options.keyboard_manager) {
81 options.keyboard_manager.enable();
81 options.keyboard_manager.enable();
82 options.keyboard_manager.command_mode();
82 options.keyboard_manager.command_mode();
83 }
83 }
84 });
84 });
85
85
86 if (options.keyboard_manager) {
86 if (options.keyboard_manager) {
87 options.keyboard_manager.disable();
87 options.keyboard_manager.disable();
88 }
88 }
89
89
90 return modal.modal(options);
90 return modal.modal(options);
91 };
91 };
92
92
93 var edit_metadata = function (md, callback, name, keyboard_manager, notebook) {
93 var edit_metadata = function (options) {
94 name = name || "Cell";
94 options.name = options.name || "Cell";
95 var error_div = $('<div/>').css('color', 'red');
95 var error_div = $('<div/>').css('color', 'red');
96 var message =
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 " We recommend putting custom metadata attributes in an appropriately named sub-structure," +
98 " We recommend putting custom metadata attributes in an appropriately named sub-structure," +
99 " so they don't conflict with those of others.";
99 " so they don't conflict with those of others.";
100
100
101 var textarea = $('<textarea/>')
101 var textarea = $('<textarea/>')
102 .attr('rows', '13')
102 .attr('rows', '13')
103 .attr('cols', '80')
103 .attr('cols', '80')
104 .attr('name', 'metadata')
104 .attr('name', 'metadata')
105 .text(JSON.stringify(md || {}, null, 2));
105 .text(JSON.stringify(options.md || {}, null, 2));
106
106
107 var dialogform = $('<div/>').attr('title', 'Edit the metadata')
107 var dialogform = $('<div/>').attr('title', 'Edit the metadata')
108 .append(
108 .append(
109 $('<form/>').append(
109 $('<form/>').append(
110 $('<fieldset/>').append(
110 $('<fieldset/>').append(
111 $('<label/>')
111 $('<label/>')
112 .attr('for','metadata')
112 .attr('for','metadata')
113 .text(message)
113 .text(message)
114 )
114 )
115 .append(error_div)
115 .append(error_div)
116 .append($('<br/>'))
116 .append($('<br/>'))
117 .append(textarea)
117 .append(textarea)
118 )
118 )
119 );
119 );
120 var editor = CodeMirror.fromTextArea(textarea[0], {
120 var editor = CodeMirror.fromTextArea(textarea[0], {
121 lineNumbers: true,
121 lineNumbers: true,
122 matchBrackets: true,
122 matchBrackets: true,
123 indentUnit: 2,
123 indentUnit: 2,
124 autoIndent: true,
124 autoIndent: true,
125 mode: 'application/json',
125 mode: 'application/json',
126 });
126 });
127 var modal = modal({
127 var modal = modal({
128 title: "Edit " + name + " Metadata",
128 title: "Edit " + options.name + " Metadata",
129 body: dialogform,
129 body: dialogform,
130 buttons: {
130 buttons: {
131 OK: { class : "btn-primary",
131 OK: { class : "btn-primary",
132 click: function() {
132 click: function() {
133 // validate json and set it
133 // validate json and set it
134 var new_md;
134 var new_md;
135 try {
135 try {
136 new_md = JSON.parse(editor.getValue());
136 new_md = JSON.parse(editor.getValue());
137 } catch(e) {
137 } catch(e) {
138 console.log(e);
138 console.log(e);
139 error_div.text('WARNING: Could not save invalid JSON.');
139 error_div.text('WARNING: Could not save invalid JSON.');
140 return false;
140 return false;
141 }
141 }
142 callback(new_md);
142 options.callback(new_md);
143 }
143 }
144 },
144 },
145 Cancel: {}
145 Cancel: {}
146 }
146 },
147 }, keyboard_manager, notebook);
147 notebook: options.notebook,
148 keyboard_manager: options.keyboard_manager,
149 });
148
150
149 modal.on('shown.bs.modal', function(){ editor.refresh(); });
151 modal.on('shown.bs.modal', function(){ editor.refresh(); });
150 };
152 };
151
153
152 var dialog = {
154 var dialog = {
153 modal : modal,
155 modal : modal,
154 edit_metadata : edit_metadata,
156 edit_metadata : edit_metadata,
155 };
157 };
156
158
157 // Backwards compatability.
159 // Backwards compatability.
158 IPython.Dialog = dialog;
160 IPython.dialog = dialog;
159
161
160 return dialog;
162 return dialog;
161 });
163 });
@@ -1,254 +1,259 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 define([
4 define([
5 'base/js/namespace',
5 'base/js/namespace',
6 'jquery',
6 'jquery',
7 'base/js/utils',
7 'base/js/utils',
8 ], function(IPython, $, utils) {
8 ], function(IPython, $, utils) {
9 "use strict";
9 "use strict";
10
10
11
11
12 // Setup global keycodes and inverse keycodes.
12 // Setup global keycodes and inverse keycodes.
13
13
14 // See http://unixpapa.com/js/key.html for a complete description. The short of
14 // See http://unixpapa.com/js/key.html for a complete description. The short of
15 // it is that there are different keycode sets. Firefox uses the "Mozilla keycodes"
15 // it is that there are different keycode sets. Firefox uses the "Mozilla keycodes"
16 // and Webkit/IE use the "IE keycodes". These keycode sets are mostly the same
16 // and Webkit/IE use the "IE keycodes". These keycode sets are mostly the same
17 // but have minor differences.
17 // but have minor differences.
18
18
19 // These apply to Firefox, (Webkit and IE)
19 // These apply to Firefox, (Webkit and IE)
20 var _keycodes = {
20 var _keycodes = {
21 'a': 65, 'b': 66, 'c': 67, 'd': 68, 'e': 69, 'f': 70, 'g': 71, 'h': 72, 'i': 73,
21 'a': 65, 'b': 66, 'c': 67, 'd': 68, 'e': 69, 'f': 70, 'g': 71, 'h': 72, 'i': 73,
22 'j': 74, 'k': 75, 'l': 76, 'm': 77, 'n': 78, 'o': 79, 'p': 80, 'q': 81, 'r': 82,
22 'j': 74, 'k': 75, 'l': 76, 'm': 77, 'n': 78, 'o': 79, 'p': 80, 'q': 81, 'r': 82,
23 's': 83, 't': 84, 'u': 85, 'v': 86, 'w': 87, 'x': 88, 'y': 89, 'z': 90,
23 's': 83, 't': 84, 'u': 85, 'v': 86, 'w': 87, 'x': 88, 'y': 89, 'z': 90,
24 '1 !': 49, '2 @': 50, '3 #': 51, '4 $': 52, '5 %': 53, '6 ^': 54,
24 '1 !': 49, '2 @': 50, '3 #': 51, '4 $': 52, '5 %': 53, '6 ^': 54,
25 '7 &': 55, '8 *': 56, '9 (': 57, '0 )': 48,
25 '7 &': 55, '8 *': 56, '9 (': 57, '0 )': 48,
26 '[ {': 219, '] }': 221, '` ~': 192, ', <': 188, '. >': 190, '/ ?': 191,
26 '[ {': 219, '] }': 221, '` ~': 192, ', <': 188, '. >': 190, '/ ?': 191,
27 '\\ |': 220, '\' "': 222,
27 '\\ |': 220, '\' "': 222,
28 'numpad0': 96, 'numpad1': 97, 'numpad2': 98, 'numpad3': 99, 'numpad4': 100,
28 'numpad0': 96, 'numpad1': 97, 'numpad2': 98, 'numpad3': 99, 'numpad4': 100,
29 'numpad5': 101, 'numpad6': 102, 'numpad7': 103, 'numpad8': 104, 'numpad9': 105,
29 'numpad5': 101, 'numpad6': 102, 'numpad7': 103, 'numpad8': 104, 'numpad9': 105,
30 'multiply': 106, 'add': 107, 'subtract': 109, 'decimal': 110, 'divide': 111,
30 'multiply': 106, 'add': 107, 'subtract': 109, 'decimal': 110, 'divide': 111,
31 'f1': 112, 'f2': 113, 'f3': 114, 'f4': 115, 'f5': 116, 'f6': 117, 'f7': 118,
31 'f1': 112, 'f2': 113, 'f3': 114, 'f4': 115, 'f5': 116, 'f6': 117, 'f7': 118,
32 'f8': 119, 'f9': 120, 'f11': 122, 'f12': 123, 'f13': 124, 'f14': 125, 'f15': 126,
32 'f8': 119, 'f9': 120, 'f11': 122, 'f12': 123, 'f13': 124, 'f14': 125, 'f15': 126,
33 'backspace': 8, 'tab': 9, 'enter': 13, 'shift': 16, 'ctrl': 17, 'alt': 18,
33 'backspace': 8, 'tab': 9, 'enter': 13, 'shift': 16, 'ctrl': 17, 'alt': 18,
34 'meta': 91, 'capslock': 20, 'esc': 27, 'space': 32, 'pageup': 33, 'pagedown': 34,
34 'meta': 91, 'capslock': 20, 'esc': 27, 'space': 32, 'pageup': 33, 'pagedown': 34,
35 'end': 35, 'home': 36, 'left': 37, 'up': 38, 'right': 39, 'down': 40,
35 'end': 35, 'home': 36, 'left': 37, 'up': 38, 'right': 39, 'down': 40,
36 'insert': 45, 'delete': 46, 'numlock': 144,
36 'insert': 45, 'delete': 46, 'numlock': 144,
37 };
37 };
38
38
39 // These apply to Firefox and Opera
39 // These apply to Firefox and Opera
40 var _mozilla_keycodes = {
40 var _mozilla_keycodes = {
41 '; :': 59, '= +': 61, '- _': 173, 'meta': 224
41 '; :': 59, '= +': 61, '- _': 173, 'meta': 224
42 };
42 };
43
43
44 // This apply to Webkit and IE
44 // This apply to Webkit and IE
45 var _ie_keycodes = {
45 var _ie_keycodes = {
46 '; :': 186, '= +': 187, '- _': 189
46 '; :': 186, '= +': 187, '- _': 189
47 };
47 };
48
48
49 var browser = utils.browser[0];
49 var browser = utils.browser[0];
50 var platform = utils.platform;
50 var platform = utils.platform;
51
51
52 if (browser === 'Firefox' || browser === 'Opera' || browser === 'Netscape') {
52 if (browser === 'Firefox' || browser === 'Opera' || browser === 'Netscape') {
53 $.extend(_keycodes, _mozilla_keycodes);
53 $.extend(_keycodes, _mozilla_keycodes);
54 } else if (browser === 'Safari' || browser === 'Chrome' || browser === 'MSIE') {
54 } else if (browser === 'Safari' || browser === 'Chrome' || browser === 'MSIE') {
55 $.extend(_keycodes, _ie_keycodes);
55 $.extend(_keycodes, _ie_keycodes);
56 }
56 }
57
57
58 var keycodes = {};
58 var keycodes = {};
59 var inv_keycodes = {};
59 var inv_keycodes = {};
60 for (var name in _keycodes) {
60 for (var name in _keycodes) {
61 var names = name.split(' ');
61 var names = name.split(' ');
62 if (names.length === 1) {
62 if (names.length === 1) {
63 var n = names[0];
63 var n = names[0];
64 keycodes[n] = _keycodes[n];
64 keycodes[n] = _keycodes[n];
65 inv_keycodes[_keycodes[n]] = n;
65 inv_keycodes[_keycodes[n]] = n;
66 } else {
66 } else {
67 var primary = names[0];
67 var primary = names[0];
68 var secondary = names[1];
68 var secondary = names[1];
69 keycodes[primary] = _keycodes[name];
69 keycodes[primary] = _keycodes[name];
70 keycodes[secondary] = _keycodes[name];
70 keycodes[secondary] = _keycodes[name];
71 inv_keycodes[_keycodes[name]] = primary;
71 inv_keycodes[_keycodes[name]] = primary;
72 }
72 }
73 }
73 }
74
74
75 var normalize_key = function (key) {
75 var normalize_key = function (key) {
76 return inv_keycodes[keycodes[key]];
76 return inv_keycodes[keycodes[key]];
77 };
77 };
78
78
79 var normalize_shortcut = function (shortcut) {
79 var normalize_shortcut = function (shortcut) {
80 // Put a shortcut into normalized form:
80 // Put a shortcut into normalized form:
81 // 1. Make lowercase
81 // 1. Make lowercase
82 // 2. Replace cmd by meta
82 // 2. Replace cmd by meta
83 // 3. Sort '-' separated modifiers into the order alt-ctrl-meta-shift
83 // 3. Sort '-' separated modifiers into the order alt-ctrl-meta-shift
84 // 4. Normalize keys
84 // 4. Normalize keys
85 shortcut = shortcut.toLowerCase().replace('cmd', 'meta');
85 shortcut = shortcut.toLowerCase().replace('cmd', 'meta');
86 shortcut = shortcut.replace(/-$/, '_'); // catch shortcuts using '-' key
86 shortcut = shortcut.replace(/-$/, '_'); // catch shortcuts using '-' key
87 var values = shortcut.split("-");
87 var values = shortcut.split("-");
88 if (values.length === 1) {
88 if (values.length === 1) {
89 return normalize_key(values[0]);
89 return normalize_key(values[0]);
90 } else {
90 } else {
91 var modifiers = values.slice(0,-1);
91 var modifiers = values.slice(0,-1);
92 var key = normalize_key(values[values.length-1]);
92 var key = normalize_key(values[values.length-1]);
93 modifiers.sort();
93 modifiers.sort();
94 return modifiers.join('-') + '-' + key;
94 return modifiers.join('-') + '-' + key;
95 }
95 }
96 };
96 };
97
97
98 var shortcut_to_event = function (shortcut, type) {
98 var shortcut_to_event = function (shortcut, type) {
99 // Convert a shortcut (shift-r) to a jQuery Event object
99 // Convert a shortcut (shift-r) to a jQuery Event object
100 type = type || 'keydown';
100 type = type || 'keydown';
101 shortcut = normalize_shortcut(shortcut);
101 shortcut = normalize_shortcut(shortcut);
102 shortcut = shortcut.replace(/-$/, '_'); // catch shortcuts using '-' key
102 shortcut = shortcut.replace(/-$/, '_'); // catch shortcuts using '-' key
103 var values = shortcut.split("-");
103 var values = shortcut.split("-");
104 var modifiers = values.slice(0,-1);
104 var modifiers = values.slice(0,-1);
105 var key = values[values.length-1];
105 var key = values[values.length-1];
106 var opts = {which: keycodes[key]};
106 var opts = {which: keycodes[key]};
107 if (modifiers.indexOf('alt') !== -1) {opts.altKey = true;}
107 if (modifiers.indexOf('alt') !== -1) {opts.altKey = true;}
108 if (modifiers.indexOf('ctrl') !== -1) {opts.ctrlKey = true;}
108 if (modifiers.indexOf('ctrl') !== -1) {opts.ctrlKey = true;}
109 if (modifiers.indexOf('meta') !== -1) {opts.metaKey = true;}
109 if (modifiers.indexOf('meta') !== -1) {opts.metaKey = true;}
110 if (modifiers.indexOf('shift') !== -1) {opts.shiftKey = true;}
110 if (modifiers.indexOf('shift') !== -1) {opts.shiftKey = true;}
111 return $.Event(type, opts);
111 return $.Event(type, opts);
112 };
112 };
113
113
114 var event_to_shortcut = function (event) {
114 var event_to_shortcut = function (event) {
115 // Convert a jQuery Event object to a shortcut (shift-r)
115 // Convert a jQuery Event object to a shortcut (shift-r)
116 var shortcut = '';
116 var shortcut = '';
117 var key = inv_keycodes[event.which];
117 var key = inv_keycodes[event.which];
118 if (event.altKey && key !== 'alt') {shortcut += 'alt-';}
118 if (event.altKey && key !== 'alt') {shortcut += 'alt-';}
119 if (event.ctrlKey && key !== 'ctrl') {shortcut += 'ctrl-';}
119 if (event.ctrlKey && key !== 'ctrl') {shortcut += 'ctrl-';}
120 if (event.metaKey && key !== 'meta') {shortcut += 'meta-';}
120 if (event.metaKey && key !== 'meta') {shortcut += 'meta-';}
121 if (event.shiftKey && key !== 'shift') {shortcut += 'shift-';}
121 if (event.shiftKey && key !== 'shift') {shortcut += 'shift-';}
122 shortcut += key;
122 shortcut += key;
123 return shortcut;
123 return shortcut;
124 };
124 };
125
125
126 // Shortcut manager class
126 // Shortcut manager class
127
127
128 var ShortcutManager = function (delay, events) {
128 var ShortcutManager = function (delay, events) {
129 this._shortcuts = {};
129 this._shortcuts = {};
130 this._counts = {};
130 this._counts = {};
131 this._timers = {};
131 this._timers = {};
132 this.delay = delay || 800; // delay in milliseconds
132 this.delay = delay || 800; // delay in milliseconds
133 this.events = events;
133 this.events = events;
134 };
134 };
135
135
136 ShortcutManager.prototype.help = function () {
136 ShortcutManager.prototype.help = function () {
137 var help = [];
137 var help = [];
138 for (var shortcut in this._shortcuts) {
138 for (var shortcut in this._shortcuts) {
139 var help_string = this._shortcuts[shortcut].help;
139 var help_string = this._shortcuts[shortcut].help;
140 var help_index = this._shortcuts[shortcut].help_index;
140 var help_index = this._shortcuts[shortcut].help_index;
141 if (help_string) {
141 if (help_string) {
142 if (platform === 'MacOS') {
142 if (platform === 'MacOS') {
143 shortcut = shortcut.replace('meta', 'cmd');
143 shortcut = shortcut.replace('meta', 'cmd');
144 }
144 }
145 help.push({
145 help.push({
146 shortcut: shortcut,
146 shortcut: shortcut,
147 help: help_string,
147 help: help_string,
148 help_index: help_index}
148 help_index: help_index}
149 );
149 );
150 }
150 }
151 }
151 }
152 help.sort(function (a, b) {
152 help.sort(function (a, b) {
153 if (a.help_index > b.help_index)
153 if (a.help_index > b.help_index)
154 return 1;
154 return 1;
155 if (a.help_index < b.help_index)
155 if (a.help_index < b.help_index)
156 return -1;
156 return -1;
157 return 0;
157 return 0;
158 });
158 });
159 return help;
159 return help;
160 };
160 };
161
161
162 ShortcutManager.prototype.clear_shortcuts = function () {
162 ShortcutManager.prototype.clear_shortcuts = function () {
163 this._shortcuts = {};
163 this._shortcuts = {};
164 };
164 };
165
165
166 ShortcutManager.prototype.add_shortcut = function (shortcut, data, suppress_help_update) {
166 ShortcutManager.prototype.add_shortcut = function (shortcut, data, suppress_help_update) {
167 if (typeof(data) === 'function') {
167 if (typeof(data) === 'function') {
168 data = {help: '', help_index: '', handler: data};
168 data = {help: '', help_index: '', handler: data};
169 }
169 }
170 data.help_index = data.help_index || '';
170 data.help_index = data.help_index || '';
171 data.help = data.help || '';
171 data.help = data.help || '';
172 data.count = data.count || 1;
172 data.count = data.count || 1;
173 if (data.help_index === '') {
173 if (data.help_index === '') {
174 data.help_index = 'zz';
174 data.help_index = 'zz';
175 }
175 }
176 shortcut = normalize_shortcut(shortcut);
176 shortcut = normalize_shortcut(shortcut);
177 this._counts[shortcut] = 0;
177 this._counts[shortcut] = 0;
178 this._shortcuts[shortcut] = data;
178 this._shortcuts[shortcut] = data;
179 if (!suppress_help_update) {
179 if (!suppress_help_update) {
180 // update the keyboard shortcuts notebook help
180 // update the keyboard shortcuts notebook help
181 this.events.trigger('rebuild.QuickHelp');
181 this.events.trigger('rebuild.QuickHelp');
182 }
182 }
183 };
183 };
184
184
185 ShortcutManager.prototype.add_shortcuts = function (data) {
185 ShortcutManager.prototype.add_shortcuts = function (data) {
186 for (var shortcut in data) {
186 for (var shortcut in data) {
187 this.add_shortcut(shortcut, data[shortcut], true);
187 this.add_shortcut(shortcut, data[shortcut], true);
188 }
188 }
189 // update the keyboard shortcuts notebook help
189 // update the keyboard shortcuts notebook help
190 this.events.trigger('rebuild.QuickHelp');
190 this.events.trigger('rebuild.QuickHelp');
191 };
191 };
192
192
193 ShortcutManager.prototype.remove_shortcut = function (shortcut, suppress_help_update) {
193 ShortcutManager.prototype.remove_shortcut = function (shortcut, suppress_help_update) {
194 shortcut = normalize_shortcut(shortcut);
194 shortcut = normalize_shortcut(shortcut);
195 delete this._counts[shortcut];
195 delete this._counts[shortcut];
196 delete this._shortcuts[shortcut];
196 delete this._shortcuts[shortcut];
197 if (!suppress_help_update) {
197 if (!suppress_help_update) {
198 // update the keyboard shortcuts notebook help
198 // update the keyboard shortcuts notebook help
199 this.events.trigger('rebuild.QuickHelp');
199 this.events.trigger('rebuild.QuickHelp');
200 }
200 }
201 };
201 };
202
202
203 ShortcutManager.prototype.count_handler = function (shortcut, event, data) {
203 ShortcutManager.prototype.count_handler = function (shortcut, event, data) {
204 var that = this;
204 var that = this;
205 var c = this._counts;
205 var c = this._counts;
206 var t = this._timers;
206 var t = this._timers;
207 var timer = null;
207 var timer = null;
208 if (c[shortcut] === data.count-1) {
208 if (c[shortcut] === data.count-1) {
209 c[shortcut] = 0;
209 c[shortcut] = 0;
210 timer = t[shortcut];
210 timer = t[shortcut];
211 if (timer) {clearTimeout(timer); delete t[shortcut];}
211 if (timer) {clearTimeout(timer); delete t[shortcut];}
212 return data.handler(event);
212 return data.handler(event);
213 } else {
213 } else {
214 c[shortcut] = c[shortcut] + 1;
214 c[shortcut] = c[shortcut] + 1;
215 timer = setTimeout(function () {
215 timer = setTimeout(function () {
216 c[shortcut] = 0;
216 c[shortcut] = 0;
217 }, that.delay);
217 }, that.delay);
218 t[shortcut] = timer;
218 t[shortcut] = timer;
219 }
219 }
220 return false;
220 return false;
221 };
221 };
222
222
223 ShortcutManager.prototype.call_handler = function (event) {
223 ShortcutManager.prototype.call_handler = function (event) {
224 var shortcut = event_to_shortcut(event);
224 var shortcut = event_to_shortcut(event);
225 var data = this._shortcuts[shortcut];
225 var data = this._shortcuts[shortcut];
226 if (data) {
226 if (data) {
227 var handler = data.handler;
227 var handler = data.handler;
228 if (handler) {
228 if (handler) {
229 if (data.count === 1) {
229 if (data.count === 1) {
230 return handler(event);
230 return handler(event);
231 } else if (data.count > 1) {
231 } else if (data.count > 1) {
232 return this.count_handler(shortcut, event, data);
232 return this.count_handler(shortcut, event, data);
233 }
233 }
234 }
234 }
235 }
235 }
236 return true;
236 return true;
237 };
237 };
238
238
239 ShortcutManager.prototype.handles = function (event) {
239 ShortcutManager.prototype.handles = function (event) {
240 var shortcut = event_to_shortcut(event);
240 var shortcut = event_to_shortcut(event);
241 var data = this._shortcuts[shortcut];
241 var data = this._shortcuts[shortcut];
242 return !( data === undefined || data.handler === undefined );
242 return !( data === undefined || data.handler === undefined );
243 };
243 };
244
244
245 return {
245 var keyboard = {
246 keycodes : keycodes,
246 keycodes : keycodes,
247 inv_keycodes : inv_keycodes,
247 inv_keycodes : inv_keycodes,
248 ShortcutManager : ShortcutManager,
248 ShortcutManager : ShortcutManager,
249 normalize_key : normalize_key,
249 normalize_key : normalize_key,
250 normalize_shortcut : normalize_shortcut,
250 normalize_shortcut : normalize_shortcut,
251 shortcut_to_event : shortcut_to_event,
251 shortcut_to_event : shortcut_to_event,
252 event_to_shortcut : event_to_shortcut
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 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 define([
4 define([
5 'base/js/namespace',
5 'base/js/namespace',
6 'jquery',
6 'jquery',
7 'components/jquery-ui/ui/minified/jquery-ui.min',
8 'components/bootstrap/js/bootstrap.min',
9 'auth/js/loginwidget'
10 ], function(IPython, $){
7 ], function(IPython, $){
11 "use strict";
8 "use strict";
12
9
13 var Page = function () {
10 var Page = function () {
14 this.style();
11 this.style();
15 this.bind_events();
12 this.bind_events();
16 };
13 };
17
14
18 Page.prototype.style = function () {
15 Page.prototype.style = function () {
19 $('div#header').addClass('border-box-sizing');
16 $('div#header').addClass('border-box-sizing');
20 $('div#site').addClass('border-box-sizing');
17 $('div#site').addClass('border-box-sizing');
21 };
18 };
22
19
23 Page.prototype.bind_events = function () {
20 Page.prototype.bind_events = function () {
24 };
21 };
25
22
26 Page.prototype.show = function () {
23 Page.prototype.show = function () {
27 // The header and site divs start out hidden to prevent FLOUC.
24 // The header and site divs start out hidden to prevent FLOUC.
28 // Main scripts should call this method after styling everything.
25 // Main scripts should call this method after styling everything.
29 this.show_header();
26 this.show_header();
30 this.show_site();
27 this.show_site();
31 };
28 };
32
29
33 Page.prototype.show_header = function () {
30 Page.prototype.show_header = function () {
34 // The header and site divs start out hidden to prevent FLOUC.
31 // The header and site divs start out hidden to prevent FLOUC.
35 // Main scripts should call this method after styling everything.
32 // Main scripts should call this method after styling everything.
36 $('div#header').css('display','block');
33 $('div#header').css('display','block');
37 };
34 };
38
35
39 Page.prototype.show_site = function () {
36 Page.prototype.show_site = function () {
40 // The header and site divs start out hidden to prevent FLOUC.
37 // The header and site divs start out hidden to prevent FLOUC.
41 // Main scripts should call this method after styling everything.
38 // Main scripts should call this method after styling everything.
42 $('div#site').css('display','block');
39 $('div#site').css('display','block');
43 };
40 };
44
41
45 // Register self in the global namespace for convenience.
42 // Register self in the global namespace for convenience.
46 IPython.Page = Page;
43 IPython.Page = Page;
47 return {'Page': Page};
44 return {'Page': Page};
48 });
45 });
@@ -1,10 +1,7 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 var ipython = ipython || {};
5 require(['base/js/page'], function(page) {
4 require(['base/js/page'], function(page) {
6 var page_instance = new page.Page();
5 var page_instance = new page.Page();
7 page_instance.show();
6 page_instance.show();
8
9 ipython.page = page_instance;
10 });
7 });
@@ -1,557 +1,557 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 define([
4 define([
5 'base/js/namespace',
5 'base/js/namespace',
6 'jquery',
6 'jquery',
7 ], function(IPython, $){
7 ], function(IPython, $){
8 "use strict";
8 "use strict";
9
9
10 IPython.load_extensions = function () {
10 IPython.load_extensions = function () {
11 // load one or more IPython notebook extensions with requirejs
11 // load one or more IPython notebook extensions with requirejs
12
12
13 var extensions = [];
13 var extensions = [];
14 var extension_names = arguments;
14 var extension_names = arguments;
15 for (var i = 0; i < extension_names.length; i++) {
15 for (var i = 0; i < extension_names.length; i++) {
16 extensions.push("nbextensions/" + arguments[i]);
16 extensions.push("nbextensions/" + arguments[i]);
17 }
17 }
18
18
19 require(extensions,
19 require(extensions,
20 function () {
20 function () {
21 for (var i = 0; i < arguments.length; i++) {
21 for (var i = 0; i < arguments.length; i++) {
22 var ext = arguments[i];
22 var ext = arguments[i];
23 var ext_name = extension_names[i];
23 var ext_name = extension_names[i];
24 // success callback
24 // success callback
25 console.log("Loaded extension: " + ext_name);
25 console.log("Loaded extension: " + ext_name);
26 if (ext && ext.load_ipython_extension !== undefined) {
26 if (ext && ext.load_ipython_extension !== undefined) {
27 ext.load_ipython_extension();
27 ext.load_ipython_extension();
28 }
28 }
29 }
29 }
30 },
30 },
31 function (err) {
31 function (err) {
32 // failure callback
32 // failure callback
33 console.log("Failed to load extension(s):", err.requireModules, err);
33 console.log("Failed to load extension(s):", err.requireModules, err);
34 }
34 }
35 );
35 );
36 };
36 };
37
37
38 //============================================================================
38 //============================================================================
39 // Cross-browser RegEx Split
39 // Cross-browser RegEx Split
40 //============================================================================
40 //============================================================================
41
41
42 // This code has been MODIFIED from the code licensed below to not replace the
42 // This code has been MODIFIED from the code licensed below to not replace the
43 // default browser split. The license is reproduced here.
43 // default browser split. The license is reproduced here.
44
44
45 // see http://blog.stevenlevithan.com/archives/cross-browser-split for more info:
45 // see http://blog.stevenlevithan.com/archives/cross-browser-split for more info:
46 /*!
46 /*!
47 * Cross-Browser Split 1.1.1
47 * Cross-Browser Split 1.1.1
48 * Copyright 2007-2012 Steven Levithan <stevenlevithan.com>
48 * Copyright 2007-2012 Steven Levithan <stevenlevithan.com>
49 * Available under the MIT License
49 * Available under the MIT License
50 * ECMAScript compliant, uniform cross-browser split method
50 * ECMAScript compliant, uniform cross-browser split method
51 */
51 */
52
52
53 /**
53 /**
54 * Splits a string into an array of strings using a regex or string
54 * Splits a string into an array of strings using a regex or string
55 * separator. Matches of the separator are not included in the result array.
55 * separator. Matches of the separator are not included in the result array.
56 * However, if `separator` is a regex that contains capturing groups,
56 * However, if `separator` is a regex that contains capturing groups,
57 * backreferences are spliced into the result each time `separator` is
57 * backreferences are spliced into the result each time `separator` is
58 * matched. Fixes browser bugs compared to the native
58 * matched. Fixes browser bugs compared to the native
59 * `String.prototype.split` and can be used reliably cross-browser.
59 * `String.prototype.split` and can be used reliably cross-browser.
60 * @param {String} str String to split.
60 * @param {String} str String to split.
61 * @param {RegExp|String} separator Regex or string to use for separating
61 * @param {RegExp|String} separator Regex or string to use for separating
62 * the string.
62 * the string.
63 * @param {Number} [limit] Maximum number of items to include in the result
63 * @param {Number} [limit] Maximum number of items to include in the result
64 * array.
64 * array.
65 * @returns {Array} Array of substrings.
65 * @returns {Array} Array of substrings.
66 * @example
66 * @example
67 *
67 *
68 * // Basic use
68 * // Basic use
69 * regex_split('a b c d', ' ');
69 * regex_split('a b c d', ' ');
70 * // -> ['a', 'b', 'c', 'd']
70 * // -> ['a', 'b', 'c', 'd']
71 *
71 *
72 * // With limit
72 * // With limit
73 * regex_split('a b c d', ' ', 2);
73 * regex_split('a b c d', ' ', 2);
74 * // -> ['a', 'b']
74 * // -> ['a', 'b']
75 *
75 *
76 * // Backreferences in result array
76 * // Backreferences in result array
77 * regex_split('..word1 word2..', /([a-z]+)(\d+)/i);
77 * regex_split('..word1 word2..', /([a-z]+)(\d+)/i);
78 * // -> ['..', 'word', '1', ' ', 'word', '2', '..']
78 * // -> ['..', 'word', '1', ' ', 'word', '2', '..']
79 */
79 */
80 var regex_split = function (str, separator, limit) {
80 var regex_split = function (str, separator, limit) {
81 // If `separator` is not a regex, use `split`
81 // If `separator` is not a regex, use `split`
82 if (Object.prototype.toString.call(separator) !== "[object RegExp]") {
82 if (Object.prototype.toString.call(separator) !== "[object RegExp]") {
83 return split.call(str, separator, limit);
83 return split.call(str, separator, limit);
84 }
84 }
85 var output = [],
85 var output = [],
86 flags = (separator.ignoreCase ? "i" : "") +
86 flags = (separator.ignoreCase ? "i" : "") +
87 (separator.multiline ? "m" : "") +
87 (separator.multiline ? "m" : "") +
88 (separator.extended ? "x" : "") + // Proposed for ES6
88 (separator.extended ? "x" : "") + // Proposed for ES6
89 (separator.sticky ? "y" : ""), // Firefox 3+
89 (separator.sticky ? "y" : ""), // Firefox 3+
90 lastLastIndex = 0,
90 lastLastIndex = 0,
91 // Make `global` and avoid `lastIndex` issues by working with a copy
91 // Make `global` and avoid `lastIndex` issues by working with a copy
92 separator = new RegExp(separator.source, flags + "g"),
92 separator = new RegExp(separator.source, flags + "g"),
93 separator2, match, lastIndex, lastLength;
93 separator2, match, lastIndex, lastLength;
94 str += ""; // Type-convert
94 str += ""; // Type-convert
95
95
96 var compliantExecNpcg = typeof(/()??/.exec("")[1]) === "undefined";
96 var compliantExecNpcg = typeof(/()??/.exec("")[1]) === "undefined";
97 if (!compliantExecNpcg) {
97 if (!compliantExecNpcg) {
98 // Doesn't need flags gy, but they don't hurt
98 // Doesn't need flags gy, but they don't hurt
99 separator2 = new RegExp("^" + separator.source + "$(?!\\s)", flags);
99 separator2 = new RegExp("^" + separator.source + "$(?!\\s)", flags);
100 }
100 }
101 /* Values for `limit`, per the spec:
101 /* Values for `limit`, per the spec:
102 * If undefined: 4294967295 // Math.pow(2, 32) - 1
102 * If undefined: 4294967295 // Math.pow(2, 32) - 1
103 * If 0, Infinity, or NaN: 0
103 * If 0, Infinity, or NaN: 0
104 * If positive number: limit = Math.floor(limit); if (limit > 4294967295) limit -= 4294967296;
104 * If positive number: limit = Math.floor(limit); if (limit > 4294967295) limit -= 4294967296;
105 * If negative number: 4294967296 - Math.floor(Math.abs(limit))
105 * If negative number: 4294967296 - Math.floor(Math.abs(limit))
106 * If other: Type-convert, then use the above rules
106 * If other: Type-convert, then use the above rules
107 */
107 */
108 limit = typeof(limit) === "undefined" ?
108 limit = typeof(limit) === "undefined" ?
109 -1 >>> 0 : // Math.pow(2, 32) - 1
109 -1 >>> 0 : // Math.pow(2, 32) - 1
110 limit >>> 0; // ToUint32(limit)
110 limit >>> 0; // ToUint32(limit)
111 while (match = separator.exec(str)) {
111 while (match = separator.exec(str)) {
112 // `separator.lastIndex` is not reliable cross-browser
112 // `separator.lastIndex` is not reliable cross-browser
113 lastIndex = match.index + match[0].length;
113 lastIndex = match.index + match[0].length;
114 if (lastIndex > lastLastIndex) {
114 if (lastIndex > lastLastIndex) {
115 output.push(str.slice(lastLastIndex, match.index));
115 output.push(str.slice(lastLastIndex, match.index));
116 // Fix browsers whose `exec` methods don't consistently return `undefined` for
116 // Fix browsers whose `exec` methods don't consistently return `undefined` for
117 // nonparticipating capturing groups
117 // nonparticipating capturing groups
118 if (!compliantExecNpcg && match.length > 1) {
118 if (!compliantExecNpcg && match.length > 1) {
119 match[0].replace(separator2, function () {
119 match[0].replace(separator2, function () {
120 for (var i = 1; i < arguments.length - 2; i++) {
120 for (var i = 1; i < arguments.length - 2; i++) {
121 if (typeof(arguments[i]) === "undefined") {
121 if (typeof(arguments[i]) === "undefined") {
122 match[i] = undefined;
122 match[i] = undefined;
123 }
123 }
124 }
124 }
125 });
125 });
126 }
126 }
127 if (match.length > 1 && match.index < str.length) {
127 if (match.length > 1 && match.index < str.length) {
128 Array.prototype.push.apply(output, match.slice(1));
128 Array.prototype.push.apply(output, match.slice(1));
129 }
129 }
130 lastLength = match[0].length;
130 lastLength = match[0].length;
131 lastLastIndex = lastIndex;
131 lastLastIndex = lastIndex;
132 if (output.length >= limit) {
132 if (output.length >= limit) {
133 break;
133 break;
134 }
134 }
135 }
135 }
136 if (separator.lastIndex === match.index) {
136 if (separator.lastIndex === match.index) {
137 separator.lastIndex++; // Avoid an infinite loop
137 separator.lastIndex++; // Avoid an infinite loop
138 }
138 }
139 }
139 }
140 if (lastLastIndex === str.length) {
140 if (lastLastIndex === str.length) {
141 if (lastLength || !separator.test("")) {
141 if (lastLength || !separator.test("")) {
142 output.push("");
142 output.push("");
143 }
143 }
144 } else {
144 } else {
145 output.push(str.slice(lastLastIndex));
145 output.push(str.slice(lastLastIndex));
146 }
146 }
147 return output.length > limit ? output.slice(0, limit) : output;
147 return output.length > limit ? output.slice(0, limit) : output;
148 };
148 };
149
149
150 //============================================================================
150 //============================================================================
151 // End contributed Cross-browser RegEx Split
151 // End contributed Cross-browser RegEx Split
152 //============================================================================
152 //============================================================================
153
153
154
154
155 var uuid = function () {
155 var uuid = function () {
156 // http://www.ietf.org/rfc/rfc4122.txt
156 // http://www.ietf.org/rfc/rfc4122.txt
157 var s = [];
157 var s = [];
158 var hexDigits = "0123456789ABCDEF";
158 var hexDigits = "0123456789ABCDEF";
159 for (var i = 0; i < 32; i++) {
159 for (var i = 0; i < 32; i++) {
160 s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
160 s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
161 }
161 }
162 s[12] = "4"; // bits 12-15 of the time_hi_and_version field to 0010
162 s[12] = "4"; // bits 12-15 of the time_hi_and_version field to 0010
163 s[16] = hexDigits.substr((s[16] & 0x3) | 0x8, 1); // bits 6-7 of the clock_seq_hi_and_reserved to 01
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 var uuid = s.join("");
165 var uuid = s.join("");
166 return uuid;
166 return uuid;
167 };
167 };
168
168
169
169
170 //Fix raw text to parse correctly in crazy XML
170 //Fix raw text to parse correctly in crazy XML
171 function xmlencode(string) {
171 function xmlencode(string) {
172 return string.replace(/\&/g,'&'+'amp;')
172 return string.replace(/\&/g,'&'+'amp;')
173 .replace(/</g,'&'+'lt;')
173 .replace(/</g,'&'+'lt;')
174 .replace(/>/g,'&'+'gt;')
174 .replace(/>/g,'&'+'gt;')
175 .replace(/\'/g,'&'+'apos;')
175 .replace(/\'/g,'&'+'apos;')
176 .replace(/\"/g,'&'+'quot;')
176 .replace(/\"/g,'&'+'quot;')
177 .replace(/`/g,'&'+'#96;');
177 .replace(/`/g,'&'+'#96;');
178 }
178 }
179
179
180
180
181 //Map from terminal commands to CSS classes
181 //Map from terminal commands to CSS classes
182 var ansi_colormap = {
182 var ansi_colormap = {
183 "01":"ansibold",
183 "01":"ansibold",
184
184
185 "30":"ansiblack",
185 "30":"ansiblack",
186 "31":"ansired",
186 "31":"ansired",
187 "32":"ansigreen",
187 "32":"ansigreen",
188 "33":"ansiyellow",
188 "33":"ansiyellow",
189 "34":"ansiblue",
189 "34":"ansiblue",
190 "35":"ansipurple",
190 "35":"ansipurple",
191 "36":"ansicyan",
191 "36":"ansicyan",
192 "37":"ansigray",
192 "37":"ansigray",
193
193
194 "40":"ansibgblack",
194 "40":"ansibgblack",
195 "41":"ansibgred",
195 "41":"ansibgred",
196 "42":"ansibggreen",
196 "42":"ansibggreen",
197 "43":"ansibgyellow",
197 "43":"ansibgyellow",
198 "44":"ansibgblue",
198 "44":"ansibgblue",
199 "45":"ansibgpurple",
199 "45":"ansibgpurple",
200 "46":"ansibgcyan",
200 "46":"ansibgcyan",
201 "47":"ansibggray"
201 "47":"ansibggray"
202 };
202 };
203
203
204 function _process_numbers(attrs, numbers) {
204 function _process_numbers(attrs, numbers) {
205 // process ansi escapes
205 // process ansi escapes
206 var n = numbers.shift();
206 var n = numbers.shift();
207 if (ansi_colormap[n]) {
207 if (ansi_colormap[n]) {
208 if ( ! attrs["class"] ) {
208 if ( ! attrs["class"] ) {
209 attrs["class"] = ansi_colormap[n];
209 attrs["class"] = ansi_colormap[n];
210 } else {
210 } else {
211 attrs["class"] += " " + ansi_colormap[n];
211 attrs["class"] += " " + ansi_colormap[n];
212 }
212 }
213 } else if (n == "38" || n == "48") {
213 } else if (n == "38" || n == "48") {
214 // VT100 256 color or 24 bit RGB
214 // VT100 256 color or 24 bit RGB
215 if (numbers.length < 2) {
215 if (numbers.length < 2) {
216 console.log("Not enough fields for VT100 color", numbers);
216 console.log("Not enough fields for VT100 color", numbers);
217 return;
217 return;
218 }
218 }
219
219
220 var index_or_rgb = numbers.shift();
220 var index_or_rgb = numbers.shift();
221 var r,g,b;
221 var r,g,b;
222 if (index_or_rgb == "5") {
222 if (index_or_rgb == "5") {
223 // 256 color
223 // 256 color
224 var idx = parseInt(numbers.shift());
224 var idx = parseInt(numbers.shift());
225 if (idx < 16) {
225 if (idx < 16) {
226 // indexed ANSI
226 // indexed ANSI
227 // ignore bright / non-bright distinction
227 // ignore bright / non-bright distinction
228 idx = idx % 8;
228 idx = idx % 8;
229 var ansiclass = ansi_colormap[n[0] + (idx % 8).toString()];
229 var ansiclass = ansi_colormap[n[0] + (idx % 8).toString()];
230 if ( ! attrs["class"] ) {
230 if ( ! attrs["class"] ) {
231 attrs["class"] = ansiclass;
231 attrs["class"] = ansiclass;
232 } else {
232 } else {
233 attrs["class"] += " " + ansiclass;
233 attrs["class"] += " " + ansiclass;
234 }
234 }
235 return;
235 return;
236 } else if (idx < 232) {
236 } else if (idx < 232) {
237 // 216 color 6x6x6 RGB
237 // 216 color 6x6x6 RGB
238 idx = idx - 16;
238 idx = idx - 16;
239 b = idx % 6;
239 b = idx % 6;
240 g = Math.floor(idx / 6) % 6;
240 g = Math.floor(idx / 6) % 6;
241 r = Math.floor(idx / 36) % 6;
241 r = Math.floor(idx / 36) % 6;
242 // convert to rgb
242 // convert to rgb
243 r = (r * 51);
243 r = (r * 51);
244 g = (g * 51);
244 g = (g * 51);
245 b = (b * 51);
245 b = (b * 51);
246 } else {
246 } else {
247 // grayscale
247 // grayscale
248 idx = idx - 231;
248 idx = idx - 231;
249 // it's 1-24 and should *not* include black or white,
249 // it's 1-24 and should *not* include black or white,
250 // so a 26 point scale
250 // so a 26 point scale
251 r = g = b = Math.floor(idx * 256 / 26);
251 r = g = b = Math.floor(idx * 256 / 26);
252 }
252 }
253 } else if (index_or_rgb == "2") {
253 } else if (index_or_rgb == "2") {
254 // Simple 24 bit RGB
254 // Simple 24 bit RGB
255 if (numbers.length > 3) {
255 if (numbers.length > 3) {
256 console.log("Not enough fields for RGB", numbers);
256 console.log("Not enough fields for RGB", numbers);
257 return;
257 return;
258 }
258 }
259 r = numbers.shift();
259 r = numbers.shift();
260 g = numbers.shift();
260 g = numbers.shift();
261 b = numbers.shift();
261 b = numbers.shift();
262 } else {
262 } else {
263 console.log("unrecognized control", numbers);
263 console.log("unrecognized control", numbers);
264 return;
264 return;
265 }
265 }
266 if (r !== undefined) {
266 if (r !== undefined) {
267 // apply the rgb color
267 // apply the rgb color
268 var line;
268 var line;
269 if (n == "38") {
269 if (n == "38") {
270 line = "color: ";
270 line = "color: ";
271 } else {
271 } else {
272 line = "background-color: ";
272 line = "background-color: ";
273 }
273 }
274 line = line + "rgb(" + r + "," + g + "," + b + ");"
274 line = line + "rgb(" + r + "," + g + "," + b + ");"
275 if ( !attrs["style"] ) {
275 if ( !attrs["style"] ) {
276 attrs["style"] = line;
276 attrs["style"] = line;
277 } else {
277 } else {
278 attrs["style"] += " " + line;
278 attrs["style"] += " " + line;
279 }
279 }
280 }
280 }
281 }
281 }
282 }
282 }
283
283
284 function ansispan(str) {
284 function ansispan(str) {
285 // ansispan function adapted from github.com/mmalecki/ansispan (MIT License)
285 // ansispan function adapted from github.com/mmalecki/ansispan (MIT License)
286 // regular ansi escapes (using the table above)
286 // regular ansi escapes (using the table above)
287 return str.replace(/\033\[(0?[01]|22|39)?([;\d]+)?m/g, function(match, prefix, pattern) {
287 return str.replace(/\033\[(0?[01]|22|39)?([;\d]+)?m/g, function(match, prefix, pattern) {
288 if (!pattern) {
288 if (!pattern) {
289 // [(01|22|39|)m close spans
289 // [(01|22|39|)m close spans
290 return "</span>";
290 return "</span>";
291 }
291 }
292 // consume sequence of color escapes
292 // consume sequence of color escapes
293 var numbers = pattern.match(/\d+/g);
293 var numbers = pattern.match(/\d+/g);
294 var attrs = {};
294 var attrs = {};
295 while (numbers.length > 0) {
295 while (numbers.length > 0) {
296 _process_numbers(attrs, numbers);
296 _process_numbers(attrs, numbers);
297 }
297 }
298
298
299 var span = "<span ";
299 var span = "<span ";
300 for (var attr in attrs) {
300 for (var attr in attrs) {
301 var value = attrs[attr];
301 var value = attrs[attr];
302 span = span + " " + attr + '="' + attrs[attr] + '"';
302 span = span + " " + attr + '="' + attrs[attr] + '"';
303 }
303 }
304 return span + ">";
304 return span + ">";
305 });
305 });
306 };
306 };
307
307
308 // Transform ANSI color escape codes into HTML <span> tags with css
308 // Transform ANSI color escape codes into HTML <span> tags with css
309 // classes listed in the above ansi_colormap object. The actual color used
309 // classes listed in the above ansi_colormap object. The actual color used
310 // are set in the css file.
310 // are set in the css file.
311 function fixConsole(txt) {
311 function fixConsole(txt) {
312 txt = xmlencode(txt);
312 txt = xmlencode(txt);
313 var re = /\033\[([\dA-Fa-f;]*?)m/;
313 var re = /\033\[([\dA-Fa-f;]*?)m/;
314 var opened = false;
314 var opened = false;
315 var cmds = [];
315 var cmds = [];
316 var opener = "";
316 var opener = "";
317 var closer = "";
317 var closer = "";
318
318
319 // Strip all ANSI codes that are not color related. Matches
319 // Strip all ANSI codes that are not color related. Matches
320 // all ANSI codes that do not end with "m".
320 // all ANSI codes that do not end with "m".
321 var ignored_re = /(?=(\033\[[\d;=]*[a-ln-zA-Z]{1}))\1(?!m)/g;
321 var ignored_re = /(?=(\033\[[\d;=]*[a-ln-zA-Z]{1}))\1(?!m)/g;
322 txt = txt.replace(ignored_re, "");
322 txt = txt.replace(ignored_re, "");
323
323
324 // color ansi codes
324 // color ansi codes
325 txt = ansispan(txt);
325 txt = ansispan(txt);
326 return txt;
326 return txt;
327 }
327 }
328
328
329 // Remove chunks that should be overridden by the effect of
329 // Remove chunks that should be overridden by the effect of
330 // carriage return characters
330 // carriage return characters
331 function fixCarriageReturn(txt) {
331 function fixCarriageReturn(txt) {
332 var tmp = txt;
332 var tmp = txt;
333 do {
333 do {
334 txt = tmp;
334 txt = tmp;
335 tmp = txt.replace(/\r+\n/gm, '\n'); // \r followed by \n --> newline
335 tmp = txt.replace(/\r+\n/gm, '\n'); // \r followed by \n --> newline
336 tmp = tmp.replace(/^.*\r+/gm, ''); // Other \r --> clear line
336 tmp = tmp.replace(/^.*\r+/gm, ''); // Other \r --> clear line
337 } while (tmp.length < txt.length);
337 } while (tmp.length < txt.length);
338 return txt;
338 return txt;
339 }
339 }
340
340
341 // Locate any URLs and convert them to a anchor tag
341 // Locate any URLs and convert them to a anchor tag
342 function autoLinkUrls(txt) {
342 function autoLinkUrls(txt) {
343 return txt.replace(/(^|\s)(https?|ftp)(:[^'">\s]+)/gi,
343 return txt.replace(/(^|\s)(https?|ftp)(:[^'">\s]+)/gi,
344 "$1<a target=\"_blank\" href=\"$2$3\">$2$3</a>");
344 "$1<a target=\"_blank\" href=\"$2$3\">$2$3</a>");
345 }
345 }
346
346
347 var points_to_pixels = function (points) {
347 var points_to_pixels = function (points) {
348 // A reasonably good way of converting between points and pixels.
348 // A reasonably good way of converting between points and pixels.
349 var test = $('<div style="display: none; width: 10000pt; padding:0; border:0;"></div>');
349 var test = $('<div style="display: none; width: 10000pt; padding:0; border:0;"></div>');
350 $(body).append(test);
350 $(body).append(test);
351 var pixel_per_point = test.width()/10000;
351 var pixel_per_point = test.width()/10000;
352 test.remove();
352 test.remove();
353 return Math.floor(points*pixel_per_point);
353 return Math.floor(points*pixel_per_point);
354 };
354 };
355
355
356 var always_new = function (constructor) {
356 var always_new = function (constructor) {
357 // wrapper around contructor to avoid requiring `var a = new constructor()`
357 // wrapper around contructor to avoid requiring `var a = new constructor()`
358 // useful for passing constructors as callbacks,
358 // useful for passing constructors as callbacks,
359 // not for programmer laziness.
359 // not for programmer laziness.
360 // from http://programmers.stackexchange.com/questions/118798
360 // from http://programmers.stackexchange.com/questions/118798
361 return function () {
361 return function () {
362 var obj = Object.create(constructor.prototype);
362 var obj = Object.create(constructor.prototype);
363 constructor.apply(obj, arguments);
363 constructor.apply(obj, arguments);
364 return obj;
364 return obj;
365 };
365 };
366 };
366 };
367
367
368 var url_path_join = function () {
368 var url_path_join = function () {
369 // join a sequence of url components with '/'
369 // join a sequence of url components with '/'
370 var url = '';
370 var url = '';
371 for (var i = 0; i < arguments.length; i++) {
371 for (var i = 0; i < arguments.length; i++) {
372 if (arguments[i] === '') {
372 if (arguments[i] === '') {
373 continue;
373 continue;
374 }
374 }
375 if (url.length > 0 && url[url.length-1] != '/') {
375 if (url.length > 0 && url[url.length-1] != '/') {
376 url = url + '/' + arguments[i];
376 url = url + '/' + arguments[i];
377 } else {
377 } else {
378 url = url + arguments[i];
378 url = url + arguments[i];
379 }
379 }
380 }
380 }
381 url = url.replace(/\/\/+/, '/');
381 url = url.replace(/\/\/+/, '/');
382 return url;
382 return url;
383 };
383 };
384
384
385 var parse_url = function (url) {
385 var parse_url = function (url) {
386 // an `a` element with an href allows attr-access to the parsed segments of a URL
386 // an `a` element with an href allows attr-access to the parsed segments of a URL
387 // a = parse_url("http://localhost:8888/path/name#hash")
387 // a = parse_url("http://localhost:8888/path/name#hash")
388 // a.protocol = "http:"
388 // a.protocol = "http:"
389 // a.host = "localhost:8888"
389 // a.host = "localhost:8888"
390 // a.hostname = "localhost"
390 // a.hostname = "localhost"
391 // a.port = 8888
391 // a.port = 8888
392 // a.pathname = "/path/name"
392 // a.pathname = "/path/name"
393 // a.hash = "#hash"
393 // a.hash = "#hash"
394 var a = document.createElement("a");
394 var a = document.createElement("a");
395 a.href = url;
395 a.href = url;
396 return a;
396 return a;
397 };
397 };
398
398
399 var encode_uri_components = function (uri) {
399 var encode_uri_components = function (uri) {
400 // encode just the components of a multi-segment uri,
400 // encode just the components of a multi-segment uri,
401 // leaving '/' separators
401 // leaving '/' separators
402 return uri.split('/').map(encodeURIComponent).join('/');
402 return uri.split('/').map(encodeURIComponent).join('/');
403 };
403 };
404
404
405 var url_join_encode = function () {
405 var url_join_encode = function () {
406 // join a sequence of url components with '/',
406 // join a sequence of url components with '/',
407 // encoding each component with encodeURIComponent
407 // encoding each component with encodeURIComponent
408 return encode_uri_components(url_path_join.apply(null, arguments));
408 return encode_uri_components(url_path_join.apply(null, arguments));
409 };
409 };
410
410
411
411
412 var splitext = function (filename) {
412 var splitext = function (filename) {
413 // mimic Python os.path.splitext
413 // mimic Python os.path.splitext
414 // Returns ['base', '.ext']
414 // Returns ['base', '.ext']
415 var idx = filename.lastIndexOf('.');
415 var idx = filename.lastIndexOf('.');
416 if (idx > 0) {
416 if (idx > 0) {
417 return [filename.slice(0, idx), filename.slice(idx)];
417 return [filename.slice(0, idx), filename.slice(idx)];
418 } else {
418 } else {
419 return [filename, ''];
419 return [filename, ''];
420 }
420 }
421 };
421 };
422
422
423
423
424 var escape_html = function (text) {
424 var escape_html = function (text) {
425 // escape text to HTML
425 // escape text to HTML
426 return $("<div/>").text(text).html();
426 return $("<div/>").text(text).html();
427 };
427 };
428
428
429
429
430 var get_body_data = function(key) {
430 var get_body_data = function(key) {
431 // get a url-encoded item from body.data and decode it
431 // get a url-encoded item from body.data and decode it
432 // we should never have any encoded URLs anywhere else in code
432 // we should never have any encoded URLs anywhere else in code
433 // until we are building an actual request
433 // until we are building an actual request
434 return decodeURIComponent($('body').data(key));
434 return decodeURIComponent($('body').data(key));
435 };
435 };
436
436
437 var to_absolute_cursor_pos = function (cm, cursor) {
437 var to_absolute_cursor_pos = function (cm, cursor) {
438 // get the absolute cursor position from CodeMirror's col, ch
438 // get the absolute cursor position from CodeMirror's col, ch
439 if (!cursor) {
439 if (!cursor) {
440 cursor = cm.getCursor();
440 cursor = cm.getCursor();
441 }
441 }
442 var cursor_pos = cursor.ch;
442 var cursor_pos = cursor.ch;
443 for (var i = 0; i < cursor.line; i++) {
443 for (var i = 0; i < cursor.line; i++) {
444 cursor_pos += cm.getLine(i).length + 1;
444 cursor_pos += cm.getLine(i).length + 1;
445 }
445 }
446 return cursor_pos;
446 return cursor_pos;
447 };
447 };
448
448
449 var from_absolute_cursor_pos = function (cm, cursor_pos) {
449 var from_absolute_cursor_pos = function (cm, cursor_pos) {
450 // turn absolute cursor postion into CodeMirror col, ch cursor
450 // turn absolute cursor postion into CodeMirror col, ch cursor
451 var i, line;
451 var i, line;
452 var offset = 0;
452 var offset = 0;
453 for (i = 0, line=cm.getLine(i); line !== undefined; i++, line=cm.getLine(i)) {
453 for (i = 0, line=cm.getLine(i); line !== undefined; i++, line=cm.getLine(i)) {
454 if (offset + line.length < cursor_pos) {
454 if (offset + line.length < cursor_pos) {
455 offset += line.length + 1;
455 offset += line.length + 1;
456 } else {
456 } else {
457 return {
457 return {
458 line : i,
458 line : i,
459 ch : cursor_pos - offset,
459 ch : cursor_pos - offset,
460 };
460 };
461 }
461 }
462 }
462 }
463 // reached end, return endpoint
463 // reached end, return endpoint
464 return {
464 return {
465 ch : line.length - 1,
465 ch : line.length - 1,
466 line : i - 1,
466 line : i - 1,
467 };
467 };
468 };
468 };
469
469
470 // http://stackoverflow.com/questions/2400935/browser-detection-in-javascript
470 // http://stackoverflow.com/questions/2400935/browser-detection-in-javascript
471 var browser = (function() {
471 var browser = (function() {
472 if (typeof navigator === 'undefined') {
472 if (typeof navigator === 'undefined') {
473 // navigator undefined in node
473 // navigator undefined in node
474 return 'None';
474 return 'None';
475 }
475 }
476 var N= navigator.appName, ua= navigator.userAgent, tem;
476 var N= navigator.appName, ua= navigator.userAgent, tem;
477 var M= ua.match(/(opera|chrome|safari|firefox|msie)\/?\s*(\.?\d+(\.\d+)*)/i);
477 var M= ua.match(/(opera|chrome|safari|firefox|msie)\/?\s*(\.?\d+(\.\d+)*)/i);
478 if (M && (tem= ua.match(/version\/([\.\d]+)/i)) !== null) M[2]= tem[1];
478 if (M && (tem= ua.match(/version\/([\.\d]+)/i)) !== null) M[2]= tem[1];
479 M= M? [M[1], M[2]]: [N, navigator.appVersion,'-?'];
479 M= M? [M[1], M[2]]: [N, navigator.appVersion,'-?'];
480 return M;
480 return M;
481 })();
481 })();
482
482
483 // http://stackoverflow.com/questions/11219582/how-to-detect-my-browser-version-and-operating-system-using-javascript
483 // http://stackoverflow.com/questions/11219582/how-to-detect-my-browser-version-and-operating-system-using-javascript
484 var platform = (function () {
484 var platform = (function () {
485 if (typeof navigator === 'undefined') {
485 if (typeof navigator === 'undefined') {
486 // navigator undefined in node
486 // navigator undefined in node
487 return 'None';
487 return 'None';
488 }
488 }
489 var OSName="None";
489 var OSName="None";
490 if (navigator.appVersion.indexOf("Win")!=-1) OSName="Windows";
490 if (navigator.appVersion.indexOf("Win")!=-1) OSName="Windows";
491 if (navigator.appVersion.indexOf("Mac")!=-1) OSName="MacOS";
491 if (navigator.appVersion.indexOf("Mac")!=-1) OSName="MacOS";
492 if (navigator.appVersion.indexOf("X11")!=-1) OSName="UNIX";
492 if (navigator.appVersion.indexOf("X11")!=-1) OSName="UNIX";
493 if (navigator.appVersion.indexOf("Linux")!=-1) OSName="Linux";
493 if (navigator.appVersion.indexOf("Linux")!=-1) OSName="Linux";
494 return OSName;
494 return OSName;
495 })();
495 })();
496
496
497 var is_or_has = function (a, b) {
497 var is_or_has = function (a, b) {
498 // Is b a child of a or a itself?
498 // Is b a child of a or a itself?
499 return a.has(b).length !==0 || a.is(b);
499 return a.has(b).length !==0 || a.is(b);
500 };
500 };
501
501
502 var is_focused = function (e) {
502 var is_focused = function (e) {
503 // Is element e, or one of its children focused?
503 // Is element e, or one of its children focused?
504 e = $(e);
504 e = $(e);
505 var target = $(document.activeElement);
505 var target = $(document.activeElement);
506 if (target.length > 0) {
506 if (target.length > 0) {
507 if (is_or_has(e, target)) {
507 if (is_or_has(e, target)) {
508 return true;
508 return true;
509 } else {
509 } else {
510 return false;
510 return false;
511 }
511 }
512 } else {
512 } else {
513 return false;
513 return false;
514 }
514 }
515 };
515 };
516
516
517 var log_ajax_error = function (jqXHR, status, error) {
517 var log_ajax_error = function (jqXHR, status, error) {
518 // log ajax failures with informative messages
518 // log ajax failures with informative messages
519 var msg = "API request failed (" + jqXHR.status + "): ";
519 var msg = "API request failed (" + jqXHR.status + "): ";
520 console.log(jqXHR);
520 console.log(jqXHR);
521 if (jqXHR.responseJSON && jqXHR.responseJSON.message) {
521 if (jqXHR.responseJSON && jqXHR.responseJSON.message) {
522 msg += jqXHR.responseJSON.message;
522 msg += jqXHR.responseJSON.message;
523 } else {
523 } else {
524 msg += jqXHR.statusText;
524 msg += jqXHR.statusText;
525 }
525 }
526 console.log(msg);
526 console.log(msg);
527 };
527 };
528
528
529 var utils = {
529 var utils = {
530 regex_split : regex_split,
530 regex_split : regex_split,
531 uuid : uuid,
531 uuid : uuid,
532 fixConsole : fixConsole,
532 fixConsole : fixConsole,
533 fixCarriageReturn : fixCarriageReturn,
533 fixCarriageReturn : fixCarriageReturn,
534 autoLinkUrls : autoLinkUrls,
534 autoLinkUrls : autoLinkUrls,
535 points_to_pixels : points_to_pixels,
535 points_to_pixels : points_to_pixels,
536 get_body_data : get_body_data,
536 get_body_data : get_body_data,
537 parse_url : parse_url,
537 parse_url : parse_url,
538 url_path_join : url_path_join,
538 url_path_join : url_path_join,
539 url_join_encode : url_join_encode,
539 url_join_encode : url_join_encode,
540 encode_uri_components : encode_uri_components,
540 encode_uri_components : encode_uri_components,
541 splitext : splitext,
541 splitext : splitext,
542 escape_html : escape_html,
542 escape_html : escape_html,
543 always_new : always_new,
543 always_new : always_new,
544 to_absolute_cursor_pos : to_absolute_cursor_pos,
544 to_absolute_cursor_pos : to_absolute_cursor_pos,
545 from_absolute_cursor_pos : from_absolute_cursor_pos,
545 from_absolute_cursor_pos : from_absolute_cursor_pos,
546 browser : browser,
546 browser : browser,
547 platform: platform,
547 platform: platform,
548 is_or_has : is_or_has,
548 is_or_has : is_or_has,
549 is_focused : is_focused,
549 is_focused : is_focused,
550 log_ajax_error : log_ajax_error,
550 log_ajax_error : log_ajax_error,
551 };
551 };
552
552
553 // Backwards compatability.
553 // Backwards compatability.
554 IPython.Utils = utils;
554 IPython.utils = utils;
555
555
556 return utils;
556 return utils;
557 });
557 });
@@ -1,132 +1,125 b''
1 /*
1 /*
2 * Date Format 1.2.3
2 * Date Format 1.2.3
3 * (c) 2007-2009 Steven Levithan <stevenlevithan.com>
3 * (c) 2007-2009 Steven Levithan <stevenlevithan.com>
4 * MIT license
4 * MIT license
5 *
5 *
6 * Includes enhancements by Scott Trenda <scott.trenda.net>
6 * Includes enhancements by Scott Trenda <scott.trenda.net>
7 * and Kris Kowal <cixar.com/~kris.kowal/>
7 * and Kris Kowal <cixar.com/~kris.kowal/>
8 *
8 *
9 * Accepts a date, a mask, or a date and a mask.
9 * Accepts a date, a mask, or a date and a mask.
10 * Returns a formatted version of the given date.
10 * Returns a formatted version of the given date.
11 * The date defaults to the current date/time.
11 * The date defaults to the current date/time.
12 * The mask defaults to dateFormat.masks.default.
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.
15 var dateFormat = function () {
18 define([], function() {
16 var token = /d{1,4}|m{1,4}|yy(?:yy)?|([HhMsTt])\1?|[LloSZ]|"[^"]*"|'[^']*'/g,
19 var dateFormat = function () {
17 timezone = /\b(?:[PMCEA][SDP]T|(?:Pacific|Mountain|Central|Eastern|Atlantic) (?:Standard|Daylight|Prevailing) Time|(?:GMT|UTC)(?:[-+]\d{4})?)\b/g,
20 var token = /d{1,4}|m{1,4}|yy(?:yy)?|([HhMsTt])\1?|[LloSZ]|"[^"]*"|'[^']*'/g,
18 timezoneClip = /[^-+\dA-Z]/g,
21 timezone = /\b(?:[PMCEA][SDP]T|(?:Pacific|Mountain|Central|Eastern|Atlantic) (?:Standard|Daylight|Prevailing) Time|(?:GMT|UTC)(?:[-+]\d{4})?)\b/g,
19 pad = function (val, len) {
22 timezoneClip = /[^-+\dA-Z]/g,
20 val = String(val);
23 pad = function (val, len) {
21 len = len || 2;
24 val = String(val);
22 while (val.length < len) val = "0" + val;
25 len = len || 2;
23 return val;
26 while (val.length < len) val = "0" + val;
24 };
27 return val;
28 };
29
30 // Regexes and supporting functions are cached through closure
31 return function (date, mask, utc) {
32 var dF = dateFormat;
33
25
34 // You can't provide utc if you skip other args (use the "UTC:" mask prefix)
26 // Regexes and supporting functions are cached through closure
35 if (arguments.length == 1 && Object.prototype.toString.call(date) == "[object String]" && !/\d/.test(date)) {
27 return function (date, mask, utc) {
36 mask = date;
28 var dF = dateFormat;
37 date = undefined;
38 }
39
29
40 // Passing date through Date applies Date.parse, if necessary
30 // You can't provide utc if you skip other args (use the "UTC:" mask prefix)
41 date = date ? new Date(date) : new Date;
31 if (arguments.length == 1 && Object.prototype.toString.call(date) == "[object String]" && !/\d/.test(date)) {
42 if (isNaN(date)) throw SyntaxError("invalid 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
40 mask = String(dF.masks[mask] || mask || dF.masks["default"]);
47 if (mask.slice(0, 4) == "UTC:") {
48 mask = mask.slice(4);
49 utc = true;
50 }
51
41
52 var _ = utc ? "getUTC" : "get",
42 // Allow setting the utc argument via the mask
53 d = date[_ + "Date"](),
43 if (mask.slice(0, 4) == "UTC:") {
54 D = date[_ + "Day"](),
44 mask = mask.slice(4);
55 m = date[_ + "Month"](),
45 utc = true;
56 y = date[_ + "FullYear"](),
46 }
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 };
91
47
92 return mask.replace(token, function ($0) {
48 var _ = utc ? "getUTC" : "get",
93 return $0 in flags ? flags[$0] : $0.slice(1, $0.length - 1);
49 d = date[_ + "Date"](),
94 });
50 D = date[_ + "Day"](),
95 };
51 m = date[_ + "Month"](),
96 }();
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
88 return mask.replace(token, function ($0) {
99 dateFormat.masks = {
89 return $0 in flags ? flags[$0] : $0.slice(1, $0.length - 1);
100 "default": "ddd mmm dd yyyy HH:MM:ss",
90 });
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'"
112 };
91 };
92 }();
113
93
114 // Internationalization strings
94 // Some common format strings
115 dateFormat.i18n = {
95 dateFormat.masks = {
116 dayNames: [
96 "default": "ddd mmm dd yyyy HH:MM:ss",
117 "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat",
97 shortDate: "m/d/yy",
118 "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"
98 mediumDate: "mmm d, yyyy",
119 ],
99 longDate: "mmmm d, yyyy",
120 monthNames: [
100 fullDate: "dddd, mmmm d, yyyy",
121 "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
101 shortTime: "h:MM TT",
122 "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"
102 mediumTime: "h:MM:ss TT",
123 ]
103 longTime: "h:MM:ss TT Z",
124 };
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...
110 // Internationalization strings
127 Date.prototype.format = function (mask, utc) {
111 dateFormat.i18n = {
128 return dateFormat(this, mask, utc);
112 dayNames: [
129 };
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};
122 // For convenience...
132 }); No newline at end of file
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 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 define([
4 define([
5 'base/js/namespace',
5 'base/js/namespace',
6 'jquery',
6 'jquery',
7 'notebook/js/textcell',
7 'notebook/js/textcell',
8 ], function(IPython, $, textcell) {
8 ], function(IPython, $, textcell) {
9 "use strict";
9 "use strict";
10
10
11 /**
11 var CellToolbar = function (options) {
12 * @constructor
12 // Constructor
13 * @class CellToolbar
13 //
14 * @param {The cell to attach the metadata UI to} cell
14 // Parameters:
15 */
15 // options: dictionary
16 var CellToolbar = function (cell, events, notebook) {
16 // Dictionary of keyword arguments.
17 // events: $(Events) instance
18 // cell: Cell instance
19 // notebook: Notebook instance
17 CellToolbar._instances.push(this);
20 CellToolbar._instances.push(this);
18 this.notebook = notebook;
21 this.notebook = options.notebook;
19 this.events = events;
22 this.events = options.events;
20 this.cell = cell;
23 this.cell = options.cell;
21 this.create_element();
24 this.create_element();
22 this.rebuild();
25 this.rebuild();
23 return this;
26 return this;
24 };
27 };
25
28
26
29
27 CellToolbar.prototype.create_element = function () {
30 CellToolbar.prototype.create_element = function () {
28 this.inner_element = $('<div/>').addClass('celltoolbar');
31 this.inner_element = $('<div/>').addClass('celltoolbar');
29 this.element = $('<div/>').addClass('ctb_hideshow')
32 this.element = $('<div/>').addClass('ctb_hideshow')
30 .append(this.inner_element);
33 .append(this.inner_element);
31 };
34 };
32
35
33
36
34 // The default css style for the outer celltoolbar div
37 // The default css style for the outer celltoolbar div
35 // (ctb_hideshow) is display: none.
38 // (ctb_hideshow) is display: none.
36 // To show the cell toolbar, *both* of the following conditions must be met:
39 // To show the cell toolbar, *both* of the following conditions must be met:
37 // - A parent container has class `ctb_global_show`
40 // - A parent container has class `ctb_global_show`
38 // - The celltoolbar has the class `ctb_show`
41 // - The celltoolbar has the class `ctb_show`
39 // This allows global show/hide, as well as per-cell show/hide.
42 // This allows global show/hide, as well as per-cell show/hide.
40
43
41 CellToolbar.global_hide = function () {
44 CellToolbar.global_hide = function () {
42 $('body').removeClass('ctb_global_show');
45 $('body').removeClass('ctb_global_show');
43 };
46 };
44
47
45
48
46 CellToolbar.global_show = function () {
49 CellToolbar.global_show = function () {
47 $('body').addClass('ctb_global_show');
50 $('body').addClass('ctb_global_show');
48 };
51 };
49
52
50
53
51 CellToolbar.prototype.hide = function () {
54 CellToolbar.prototype.hide = function () {
52 this.element.removeClass('ctb_show');
55 this.element.removeClass('ctb_show');
53 };
56 };
54
57
55
58
56 CellToolbar.prototype.show = function () {
59 CellToolbar.prototype.show = function () {
57 this.element.addClass('ctb_show');
60 this.element.addClass('ctb_show');
58 };
61 };
59
62
60
63
61 /**
64 /**
62 * Class variable that should contain a dict of all available callback
65 * Class variable that should contain a dict of all available callback
63 * we need to think of wether or not we allow nested namespace
66 * we need to think of wether or not we allow nested namespace
64 * @property _callback_dict
67 * @property _callback_dict
65 * @private
68 * @private
66 * @static
69 * @static
67 * @type Dict
70 * @type Dict
68 */
71 */
69 CellToolbar._callback_dict = {};
72 CellToolbar._callback_dict = {};
70
73
71
74
72 /**
75 /**
73 * Class variable that should contain the reverse order list of the button
76 * Class variable that should contain the reverse order list of the button
74 * to add to the toolbar of each cell
77 * to add to the toolbar of each cell
75 * @property _ui_controls_list
78 * @property _ui_controls_list
76 * @private
79 * @private
77 * @static
80 * @static
78 * @type List
81 * @type List
79 */
82 */
80 CellToolbar._ui_controls_list = [];
83 CellToolbar._ui_controls_list = [];
81
84
82
85
83 /**
86 /**
84 * Class variable that should contain the CellToolbar instances for each
87 * Class variable that should contain the CellToolbar instances for each
85 * cell of the notebook
88 * cell of the notebook
86 *
89 *
87 * @private
90 * @private
88 * @property _instances
91 * @property _instances
89 * @static
92 * @static
90 * @type List
93 * @type List
91 */
94 */
92 CellToolbar._instances = [];
95 CellToolbar._instances = [];
93
96
94
97
95 /**
98 /**
96 * keep a list of all the available presets for the toolbar
99 * keep a list of all the available presets for the toolbar
97 * @private
100 * @private
98 * @property _presets
101 * @property _presets
99 * @static
102 * @static
100 * @type Dict
103 * @type Dict
101 */
104 */
102 CellToolbar._presets = {};
105 CellToolbar._presets = {};
103
106
104
107
105 // this is by design not a prototype.
108 // this is by design not a prototype.
106 /**
109 /**
107 * Register a callback to create an UI element in a cell toolbar.
110 * Register a callback to create an UI element in a cell toolbar.
108 * @method register_callback
111 * @method register_callback
109 * @param name {String} name to use to refer to the callback. It is advised to use a prefix with the name
112 * @param name {String} name to use to refer to the callback. It is advised to use a prefix with the name
110 * for easier sorting and avoid collision
113 * for easier sorting and avoid collision
111 * @param callback {function(div, cell)} callback that will be called to generate the ui element
114 * @param callback {function(div, cell)} callback that will be called to generate the ui element
112 * @param [cell_types] {List of String|undefined} optional list of cell types. If present the UI element
115 * @param [cell_types] {List of String|undefined} optional list of cell types. If present the UI element
113 * will be added only to cells of types in the list.
116 * will be added only to cells of types in the list.
114 *
117 *
115 *
118 *
116 * The callback will receive the following element :
119 * The callback will receive the following element :
117 *
120 *
118 * * a div in which to add element.
121 * * a div in which to add element.
119 * * the cell it is responsible from
122 * * the cell it is responsible from
120 *
123 *
121 * @example
124 * @example
122 *
125 *
123 * Example that create callback for a button that toggle between `true` and `false` label,
126 * Example that create callback for a button that toggle between `true` and `false` label,
124 * with the metadata under the key 'foo' to reflect the status of the button.
127 * with the metadata under the key 'foo' to reflect the status of the button.
125 *
128 *
126 * // first param reference to a DOM div
129 * // first param reference to a DOM div
127 * // second param reference to the cell.
130 * // second param reference to the cell.
128 * var toggle = function(div, cell) {
131 * var toggle = function(div, cell) {
129 * var button_container = $(div)
132 * var button_container = $(div)
130 *
133 *
131 * // let's create a button that show the current value of the metadata
134 * // let's create a button that show the current value of the metadata
132 * var button = $('<div/>').button({label:String(cell.metadata.foo)});
135 * var button = $('<div/>').button({label:String(cell.metadata.foo)});
133 *
136 *
134 * // On click, change the metadata value and update the button label
137 * // On click, change the metadata value and update the button label
135 * button.click(function(){
138 * button.click(function(){
136 * var v = cell.metadata.foo;
139 * var v = cell.metadata.foo;
137 * cell.metadata.foo = !v;
140 * cell.metadata.foo = !v;
138 * button.button("option", "label", String(!v));
141 * button.button("option", "label", String(!v));
139 * })
142 * })
140 *
143 *
141 * // add the button to the DOM div.
144 * // add the button to the DOM div.
142 * button_container.append(button);
145 * button_container.append(button);
143 * }
146 * }
144 *
147 *
145 * // now we register the callback under the name `foo` to give the
148 * // now we register the callback under the name `foo` to give the
146 * // user the ability to use it later
149 * // user the ability to use it later
147 * CellToolbar.register_callback('foo', toggle);
150 * CellToolbar.register_callback('foo', toggle);
148 */
151 */
149 CellToolbar.register_callback = function(name, callback, cell_types) {
152 CellToolbar.register_callback = function(name, callback, cell_types) {
150 // Overwrite if it already exists.
153 // Overwrite if it already exists.
151 CellToolbar._callback_dict[name] = cell_types ? {callback: callback, cell_types: cell_types} : callback;
154 CellToolbar._callback_dict[name] = cell_types ? {callback: callback, cell_types: cell_types} : callback;
152 };
155 };
153
156
154
157
155 /**
158 /**
156 * Register a preset of UI element in a cell toolbar.
159 * Register a preset of UI element in a cell toolbar.
157 * Not supported Yet.
160 * Not supported Yet.
158 * @method register_preset
161 * @method register_preset
159 * @param name {String} name to use to refer to the preset. It is advised to use a prefix with the name
162 * @param name {String} name to use to refer to the preset. It is advised to use a prefix with the name
160 * for easier sorting and avoid collision
163 * for easier sorting and avoid collision
161 * @param preset_list {List of String} reverse order of the button in the toolbar. Each String of the list
164 * @param preset_list {List of String} reverse order of the button in the toolbar. Each String of the list
162 * should correspond to a name of a registerd callback.
165 * should correspond to a name of a registerd callback.
163 *
166 *
164 * @private
167 * @private
165 * @example
168 * @example
166 *
169 *
167 * CellToolbar.register_callback('foo.c1', function(div, cell){...});
170 * CellToolbar.register_callback('foo.c1', function(div, cell){...});
168 * CellToolbar.register_callback('foo.c2', function(div, cell){...});
171 * CellToolbar.register_callback('foo.c2', function(div, cell){...});
169 * CellToolbar.register_callback('foo.c3', function(div, cell){...});
172 * CellToolbar.register_callback('foo.c3', function(div, cell){...});
170 * CellToolbar.register_callback('foo.c4', function(div, cell){...});
173 * CellToolbar.register_callback('foo.c4', function(div, cell){...});
171 * CellToolbar.register_callback('foo.c5', function(div, cell){...});
174 * CellToolbar.register_callback('foo.c5', function(div, cell){...});
172 *
175 *
173 * CellToolbar.register_preset('foo.foo_preset1', ['foo.c1', 'foo.c2', 'foo.c5'])
176 * CellToolbar.register_preset('foo.foo_preset1', ['foo.c1', 'foo.c2', 'foo.c5'])
174 * CellToolbar.register_preset('foo.foo_preset2', ['foo.c4', 'foo.c5'])
177 * CellToolbar.register_preset('foo.foo_preset2', ['foo.c4', 'foo.c5'])
175 */
178 */
176 CellToolbar.register_preset = function(name, preset_list) {
179 CellToolbar.register_preset = function(name, preset_list) {
177 CellToolbar._presets[name] = preset_list;
180 CellToolbar._presets[name] = preset_list;
178 this.events.trigger('preset_added.CellToolbar', {name: name});
181 this.events.trigger('preset_added.CellToolbar', {name: name});
179 // When "register_callback" is called by a custom extension, it may be executed after notebook is loaded.
182 // When "register_callback" is called by a custom extension, it may be executed after notebook is loaded.
180 // In that case, activate the preset if needed.
183 // In that case, activate the preset if needed.
181 if (this.notebook && this.notebook.metadata && this.notebook.metadata.celltoolbar === name)
184 if (this.notebook && this.notebook.metadata && this.notebook.metadata.celltoolbar === name)
182 this.activate_preset(name);
185 this.activate_preset(name);
183 };
186 };
184
187
185
188
186 /**
189 /**
187 * List the names of the presets that are currently registered.
190 * List the names of the presets that are currently registered.
188 *
191 *
189 * @method list_presets
192 * @method list_presets
190 * @static
193 * @static
191 */
194 */
192 CellToolbar.list_presets = function() {
195 CellToolbar.list_presets = function() {
193 var keys = [];
196 var keys = [];
194 for (var k in CellToolbar._presets) {
197 for (var k in CellToolbar._presets) {
195 keys.push(k);
198 keys.push(k);
196 }
199 }
197 return keys;
200 return keys;
198 };
201 };
199
202
200
203
201 /**
204 /**
202 * Activate an UI preset from `register_preset`
205 * Activate an UI preset from `register_preset`
203 *
206 *
204 * This does not update the selection UI.
207 * This does not update the selection UI.
205 *
208 *
206 * @method activate_preset
209 * @method activate_preset
207 * @param preset_name {String} string corresponding to the preset name
210 * @param preset_name {String} string corresponding to the preset name
208 *
211 *
209 * @static
212 * @static
210 * @private
213 * @private
211 * @example
214 * @example
212 *
215 *
213 * CellToolbar.activate_preset('foo.foo_preset1');
216 * CellToolbar.activate_preset('foo.foo_preset1');
214 */
217 */
215 CellToolbar.activate_preset = function(preset_name){
218 CellToolbar.activate_preset = function(preset_name){
216 var preset = CellToolbar._presets[preset_name];
219 var preset = CellToolbar._presets[preset_name];
217
220
218 if(preset !== undefined){
221 if(preset !== undefined){
219 CellToolbar._ui_controls_list = preset;
222 CellToolbar._ui_controls_list = preset;
220 CellToolbar.rebuild_all();
223 CellToolbar.rebuild_all();
221 }
224 }
222
225
223 if (this.events) {
226 if (this.events) {
224 this.events.trigger('preset_activated.CellToolbar', {name: preset_name});
227 this.events.trigger('preset_activated.CellToolbar', {name: preset_name});
225 }
228 }
226 };
229 };
227
230
228
231
229 /**
232 /**
230 * This should be called on the class and not on a instance as it will trigger
233 * This should be called on the class and not on a instance as it will trigger
231 * rebuild of all the instances.
234 * rebuild of all the instances.
232 * @method rebuild_all
235 * @method rebuild_all
233 * @static
236 * @static
234 *
237 *
235 */
238 */
236 CellToolbar.rebuild_all = function(){
239 CellToolbar.rebuild_all = function(){
237 for(var i=0; i < CellToolbar._instances.length; i++){
240 for(var i=0; i < CellToolbar._instances.length; i++){
238 CellToolbar._instances[i].rebuild();
241 CellToolbar._instances[i].rebuild();
239 }
242 }
240 };
243 };
241
244
242 /**
245 /**
243 * Rebuild all the button on the toolbar to update its state.
246 * Rebuild all the button on the toolbar to update its state.
244 * @method rebuild
247 * @method rebuild
245 */
248 */
246 CellToolbar.prototype.rebuild = function(){
249 CellToolbar.prototype.rebuild = function(){
247 // strip evrything from the div
250 // strip evrything from the div
248 // which is probably inner_element
251 // which is probably inner_element
249 // or this.element.
252 // or this.element.
250 this.inner_element.empty();
253 this.inner_element.empty();
251 this.ui_controls_list = [];
254 this.ui_controls_list = [];
252
255
253 var callbacks = CellToolbar._callback_dict;
256 var callbacks = CellToolbar._callback_dict;
254 var preset = CellToolbar._ui_controls_list;
257 var preset = CellToolbar._ui_controls_list;
255 // Yes we iterate on the class variable, not the instance one.
258 // Yes we iterate on the class variable, not the instance one.
256 for (var i=0; i < preset.length; i++) {
259 for (var i=0; i < preset.length; i++) {
257 var key = preset[i];
260 var key = preset[i];
258 var callback = callbacks[key];
261 var callback = callbacks[key];
259 if (!callback) continue;
262 if (!callback) continue;
260
263
261 if (typeof callback === 'object') {
264 if (typeof callback === 'object') {
262 if (callback.cell_types.indexOf(this.cell.cell_type) === -1) continue;
265 if (callback.cell_types.indexOf(this.cell.cell_type) === -1) continue;
263 callback = callback.callback;
266 callback = callback.callback;
264 }
267 }
265
268
266 var local_div = $('<div/>').addClass('button_container');
269 var local_div = $('<div/>').addClass('button_container');
267 try {
270 try {
268 callback(local_div, this.cell, this);
271 callback(local_div, this.cell, this);
269 this.ui_controls_list.push(key);
272 this.ui_controls_list.push(key);
270 } catch (e) {
273 } catch (e) {
271 console.log("Error in cell toolbar callback " + key, e);
274 console.log("Error in cell toolbar callback " + key, e);
272 continue;
275 continue;
273 }
276 }
274 // only append if callback succeeded.
277 // only append if callback succeeded.
275 this.inner_element.append(local_div);
278 this.inner_element.append(local_div);
276 }
279 }
277
280
278 // If there are no controls or the cell is a rendered TextCell hide the toolbar.
281 // If there are no controls or the cell is a rendered TextCell hide the toolbar.
279 if (!this.ui_controls_list.length || (this.cell instanceof textcell.TextCell && this.cell.rendered)) {
282 if (!this.ui_controls_list.length || (this.cell instanceof textcell.TextCell && this.cell.rendered)) {
280 this.hide();
283 this.hide();
281 } else {
284 } else {
282 this.show();
285 this.show();
283 }
286 }
284 };
287 };
285
288
286
289
287 /**
290 /**
288 */
291 */
289 CellToolbar.utils = {};
292 CellToolbar.utils = {};
290
293
291
294
292 /**
295 /**
293 * A utility function to generate bindings between a checkbox and cell/metadata
296 * A utility function to generate bindings between a checkbox and cell/metadata
294 * @method utils.checkbox_ui_generator
297 * @method utils.checkbox_ui_generator
295 * @static
298 * @static
296 *
299 *
297 * @param name {string} Label in front of the checkbox
300 * @param name {string} Label in front of the checkbox
298 * @param setter {function( cell, newValue )}
301 * @param setter {function( cell, newValue )}
299 * A setter method to set the newValue
302 * A setter method to set the newValue
300 * @param getter {function( cell )}
303 * @param getter {function( cell )}
301 * A getter methods which return the current value.
304 * A getter methods which return the current value.
302 *
305 *
303 * @return callback {function( div, cell )} Callback to be passed to `register_callback`
306 * @return callback {function( div, cell )} Callback to be passed to `register_callback`
304 *
307 *
305 * @example
308 * @example
306 *
309 *
307 * An exmple that bind the subkey `slideshow.isSectionStart` to a checkbox with a `New Slide` label
310 * An exmple that bind the subkey `slideshow.isSectionStart` to a checkbox with a `New Slide` label
308 *
311 *
309 * var newSlide = CellToolbar.utils.checkbox_ui_generator('New Slide',
312 * var newSlide = CellToolbar.utils.checkbox_ui_generator('New Slide',
310 * // setter
313 * // setter
311 * function(cell, value){
314 * function(cell, value){
312 * // we check that the slideshow namespace exist and create it if needed
315 * // we check that the slideshow namespace exist and create it if needed
313 * if (cell.metadata.slideshow == undefined){cell.metadata.slideshow = {}}
316 * if (cell.metadata.slideshow == undefined){cell.metadata.slideshow = {}}
314 * // set the value
317 * // set the value
315 * cell.metadata.slideshow.isSectionStart = value
318 * cell.metadata.slideshow.isSectionStart = value
316 * },
319 * },
317 * //geter
320 * //geter
318 * function(cell){ var ns = cell.metadata.slideshow;
321 * function(cell){ var ns = cell.metadata.slideshow;
319 * // if the slideshow namespace does not exist return `undefined`
322 * // if the slideshow namespace does not exist return `undefined`
320 * // (will be interpreted as `false` by checkbox) otherwise
323 * // (will be interpreted as `false` by checkbox) otherwise
321 * // return the value
324 * // return the value
322 * return (ns == undefined)? undefined: ns.isSectionStart
325 * return (ns == undefined)? undefined: ns.isSectionStart
323 * }
326 * }
324 * );
327 * );
325 *
328 *
326 * CellToolbar.register_callback('newSlide', newSlide);
329 * CellToolbar.register_callback('newSlide', newSlide);
327 *
330 *
328 */
331 */
329 CellToolbar.utils.checkbox_ui_generator = function(name, setter, getter){
332 CellToolbar.utils.checkbox_ui_generator = function(name, setter, getter){
330 return function(div, cell, celltoolbar) {
333 return function(div, cell, celltoolbar) {
331 var button_container = $(div);
334 var button_container = $(div);
332
335
333 var chkb = $('<input/>').attr('type', 'checkbox');
336 var chkb = $('<input/>').attr('type', 'checkbox');
334 var lbl = $('<label/>').append($('<span/>').text(name));
337 var lbl = $('<label/>').append($('<span/>').text(name));
335 lbl.append(chkb);
338 lbl.append(chkb);
336 chkb.attr("checked", getter(cell));
339 chkb.attr("checked", getter(cell));
337
340
338 chkb.click(function(){
341 chkb.click(function(){
339 var v = getter(cell);
342 var v = getter(cell);
340 setter(cell, !v);
343 setter(cell, !v);
341 chkb.attr("checked", !v);
344 chkb.attr("checked", !v);
342 });
345 });
343 button_container.append($('<div/>').append(lbl));
346 button_container.append($('<div/>').append(lbl));
344 };
347 };
345 };
348 };
346
349
347
350
348 /**
351 /**
349 * A utility function to generate bindings between a dropdown list cell
352 * A utility function to generate bindings between a dropdown list cell
350 * @method utils.select_ui_generator
353 * @method utils.select_ui_generator
351 * @static
354 * @static
352 *
355 *
353 * @param list_list {list of sublist} List of sublist of metadata value and name in the dropdown list.
356 * @param list_list {list of sublist} List of sublist of metadata value and name in the dropdown list.
354 * subslit shoud contain 2 element each, first a string that woul be displayed in the dropdown list,
357 * subslit shoud contain 2 element each, first a string that woul be displayed in the dropdown list,
355 * and second the corresponding value to be passed to setter/return by getter. the corresponding value
358 * and second the corresponding value to be passed to setter/return by getter. the corresponding value
356 * should not be "undefined" or behavior can be unexpected.
359 * should not be "undefined" or behavior can be unexpected.
357 * @param setter {function( cell, newValue )}
360 * @param setter {function( cell, newValue )}
358 * A setter method to set the newValue
361 * A setter method to set the newValue
359 * @param getter {function( cell )}
362 * @param getter {function( cell )}
360 * A getter methods which return the current value of the metadata.
363 * A getter methods which return the current value of the metadata.
361 * @param [label=""] {String} optionnal label for the dropdown menu
364 * @param [label=""] {String} optionnal label for the dropdown menu
362 *
365 *
363 * @return callback {function( div, cell )} Callback to be passed to `register_callback`
366 * @return callback {function( div, cell )} Callback to be passed to `register_callback`
364 *
367 *
365 * @example
368 * @example
366 *
369 *
367 * var select_type = CellToolbar.utils.select_ui_generator([
370 * var select_type = CellToolbar.utils.select_ui_generator([
368 * ["<None>" , "None" ],
371 * ["<None>" , "None" ],
369 * ["Header Slide" , "header_slide" ],
372 * ["Header Slide" , "header_slide" ],
370 * ["Slide" , "slide" ],
373 * ["Slide" , "slide" ],
371 * ["Fragment" , "fragment" ],
374 * ["Fragment" , "fragment" ],
372 * ["Skip" , "skip" ],
375 * ["Skip" , "skip" ],
373 * ],
376 * ],
374 * // setter
377 * // setter
375 * function(cell, value){
378 * function(cell, value){
376 * // we check that the slideshow namespace exist and create it if needed
379 * // we check that the slideshow namespace exist and create it if needed
377 * if (cell.metadata.slideshow == undefined){cell.metadata.slideshow = {}}
380 * if (cell.metadata.slideshow == undefined){cell.metadata.slideshow = {}}
378 * // set the value
381 * // set the value
379 * cell.metadata.slideshow.slide_type = value
382 * cell.metadata.slideshow.slide_type = value
380 * },
383 * },
381 * //geter
384 * //geter
382 * function(cell){ var ns = cell.metadata.slideshow;
385 * function(cell){ var ns = cell.metadata.slideshow;
383 * // if the slideshow namespace does not exist return `undefined`
386 * // if the slideshow namespace does not exist return `undefined`
384 * // (will be interpreted as `false` by checkbox) otherwise
387 * // (will be interpreted as `false` by checkbox) otherwise
385 * // return the value
388 * // return the value
386 * return (ns == undefined)? undefined: ns.slide_type
389 * return (ns == undefined)? undefined: ns.slide_type
387 * }
390 * }
388 * CellToolbar.register_callback('slideshow.select', select_type);
391 * CellToolbar.register_callback('slideshow.select', select_type);
389 *
392 *
390 */
393 */
391 CellToolbar.utils.select_ui_generator = function(list_list, setter, getter, label) {
394 CellToolbar.utils.select_ui_generator = function(list_list, setter, getter, label) {
392 label = label || "";
395 label = label || "";
393 return function(div, cell, celltoolbar) {
396 return function(div, cell, celltoolbar) {
394 var button_container = $(div);
397 var button_container = $(div);
395 var lbl = $("<label/>").append($('<span/>').text(label));
398 var lbl = $("<label/>").append($('<span/>').text(label));
396 var select = $('<select/>').addClass('ui-widget ui-widget-content');
399 var select = $('<select/>').addClass('ui-widget ui-widget-content');
397 for(var i=0; i < list_list.length; i++){
400 for(var i=0; i < list_list.length; i++){
398 var opt = $('<option/>')
401 var opt = $('<option/>')
399 .attr('value', list_list[i][1])
402 .attr('value', list_list[i][1])
400 .text(list_list[i][0]);
403 .text(list_list[i][0]);
401 select.append(opt);
404 select.append(opt);
402 }
405 }
403 select.val(getter(cell));
406 select.val(getter(cell));
404 select.change(function(){
407 select.change(function(){
405 setter(cell, select.val());
408 setter(cell, select.val());
406 });
409 });
407 button_container.append($('<div/>').append(lbl).append(select));
410 button_container.append($('<div/>').append(lbl).append(select));
408 };
411 };
409 };
412 };
410
413
411 // Backwards compatability.
414 // Backwards compatability.
412 IPython.CellToolbar = CellToolbar;
415 IPython.CellToolbar = CellToolbar;
413
416
414 return {'CellToolbar': CellToolbar};
417 return {'CellToolbar': CellToolbar};
415 });
418 });
@@ -1,519 +1,526 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 define([
4 define([
5 'base/js/namespace',
5 'base/js/namespace',
6 'jquery',
6 'jquery',
7 'base/js/utils',
7 'base/js/utils',
8 'notebook/js/tooltip',
8 'notebook/js/tooltip',
9 'base/js/keyboard',
9 'base/js/keyboard',
10 'notebook/js/cell',
10 'notebook/js/cell',
11 'notebook/js/outputarea',
11 'notebook/js/outputarea',
12 'notebook/js/completer',
12 'notebook/js/completer',
13 'notebook/js/celltoolbar',
13 'notebook/js/celltoolbar',
14 ], function(IPython, $, utils, tooltip, keyboard, cell, outputarea, completer, celltoolbar) {
14 ], function(IPython, $, utils, tooltip, keyboard, cell, outputarea, completer, celltoolbar) {
15 "use strict";
15 "use strict";
16 var Cell = cell.Cell;
16 var Cell = cell.Cell;
17
17
18 /* local util for codemirror */
18 /* local util for codemirror */
19 var posEq = function(a, b) {return a.line == b.line && a.ch == b.ch;};
19 var posEq = function(a, b) {return a.line == b.line && a.ch == b.ch;};
20
20
21 /**
21 /**
22 *
22 *
23 * function to delete until previous non blanking space character
23 * function to delete until previous non blanking space character
24 * or first multiple of 4 tabstop.
24 * or first multiple of 4 tabstop.
25 * @private
25 * @private
26 */
26 */
27 CodeMirror.commands.delSpaceToPrevTabStop = function(cm){
27 CodeMirror.commands.delSpaceToPrevTabStop = function(cm){
28 var from = cm.getCursor(true), to = cm.getCursor(false), sel = !posEq(from, to);
28 var from = cm.getCursor(true), to = cm.getCursor(false), sel = !posEq(from, to);
29 if (!posEq(from, to)) { cm.replaceRange("", from, to); return; }
29 if (!posEq(from, to)) { cm.replaceRange("", from, to); return; }
30 var cur = cm.getCursor(), line = cm.getLine(cur.line);
30 var cur = cm.getCursor(), line = cm.getLine(cur.line);
31 var tabsize = cm.getOption('tabSize');
31 var tabsize = cm.getOption('tabSize');
32 var chToPrevTabStop = cur.ch-(Math.ceil(cur.ch/tabsize)-1)*tabsize;
32 var chToPrevTabStop = cur.ch-(Math.ceil(cur.ch/tabsize)-1)*tabsize;
33 from = {ch:cur.ch-chToPrevTabStop,line:cur.line};
33 from = {ch:cur.ch-chToPrevTabStop,line:cur.line};
34 var select = cm.getRange(from,cur);
34 var select = cm.getRange(from,cur);
35 if( select.match(/^\ +$/) !== null){
35 if( select.match(/^\ +$/) !== null){
36 cm.replaceRange("",from,cur);
36 cm.replaceRange("",from,cur);
37 } else {
37 } else {
38 cm.deleteH(-1,"char");
38 cm.deleteH(-1,"char");
39 }
39 }
40 };
40 };
41
41
42 var keycodes = keyboard.keycodes;
42 var keycodes = keyboard.keycodes;
43
43
44 var CodeCell = function (kernel, options) {
44 var CodeCell = function (kernel, options) {
45 // Constructor
45 // Constructor
46 //
46 //
47 // A Cell conceived to write code.
47 // A Cell conceived to write code.
48 //
48 //
49 // Parameters:
49 // Parameters:
50 // kernel: Kernel instance
50 // kernel: Kernel instance
51 // The kernel doesn't have to be set at creation time, in that case
51 // The kernel doesn't have to be set at creation time, in that case
52 // it will be null and set_kernel has to be called later.
52 // it will be null and set_kernel has to be called later.
53 // options: dictionary
53 // options: dictionary
54 // Dictionary of keyword arguments.
54 // Dictionary of keyword arguments.
55 // events: $(Events) instance
55 // events: $(Events) instance
56 // config: dictionary
56 // config: dictionary
57 // keyboard_manager: KeyboardManager instance
57 // keyboard_manager: KeyboardManager instance
58 // notebook: Notebook instance
58 // notebook: Notebook instance
59 this.kernel = kernel || null;
59 this.kernel = kernel || null;
60 this.notebook = options.notebook;
60 this.notebook = options.notebook;
61 this.collapsed = false;
61 this.collapsed = false;
62 this.events = options.events;
62 this.events = options.events;
63 this.tooltip = new tooltip.Tooltip(this.events);
63 this.tooltip = new tooltip.Tooltip(this.events);
64 this.config = options.config;
64 this.config = options.config;
65
65
66 // create all attributed in constructor function
66 // create all attributed in constructor function
67 // even if null for V8 VM optimisation
67 // even if null for V8 VM optimisation
68 this.input_prompt_number = null;
68 this.input_prompt_number = null;
69 this.celltoolbar = null;
69 this.celltoolbar = null;
70 this.output_area = null;
70 this.output_area = null;
71 this.last_msg_id = null;
71 this.last_msg_id = null;
72 this.completer = null;
72 this.completer = null;
73
73
74
74
75 var cm_overwrite_options = {
75 var cm_overwrite_options = {
76 onKeyEvent: $.proxy(this.handle_keyevent,this)
76 onKeyEvent: $.proxy(this.handle_keyevent,this)
77 };
77 };
78
78
79 var config = this.mergeopt(CodeCell, this.config, {cm_config: cm_overwrite_options});
79 var config = this.mergeopt(CodeCell, this.config, {cm_config: cm_overwrite_options});
80 Cell.apply(this,[{
80 Cell.apply(this,[{
81 config: config,
81 config: config,
82 keyboard_manager: options.keyboard_manager,
82 keyboard_manager: options.keyboard_manager,
83 events: this.events}]);
83 events: this.events}]);
84
84
85 // Attributes we want to override in this subclass.
85 // Attributes we want to override in this subclass.
86 this.cell_type = "code";
86 this.cell_type = "code";
87
87
88 var that = this;
88 var that = this;
89 this.element.focusout(
89 this.element.focusout(
90 function() { that.auto_highlight(); }
90 function() { that.auto_highlight(); }
91 );
91 );
92 };
92 };
93
93
94 CodeCell.options_default = {
94 CodeCell.options_default = {
95 cm_config : {
95 cm_config : {
96 extraKeys: {
96 extraKeys: {
97 "Tab" : "indentMore",
97 "Tab" : "indentMore",
98 "Shift-Tab" : "indentLess",
98 "Shift-Tab" : "indentLess",
99 "Backspace" : "delSpaceToPrevTabStop",
99 "Backspace" : "delSpaceToPrevTabStop",
100 "Cmd-/" : "toggleComment",
100 "Cmd-/" : "toggleComment",
101 "Ctrl-/" : "toggleComment"
101 "Ctrl-/" : "toggleComment"
102 },
102 },
103 mode: 'ipython',
103 mode: 'ipython',
104 theme: 'ipython',
104 theme: 'ipython',
105 matchBrackets: true,
105 matchBrackets: true,
106 // don't auto-close strings because of CodeMirror #2385
106 // don't auto-close strings because of CodeMirror #2385
107 autoCloseBrackets: "()[]{}"
107 autoCloseBrackets: "()[]{}"
108 }
108 }
109 };
109 };
110
110
111 CodeCell.msg_cells = {};
111 CodeCell.msg_cells = {};
112
112
113 CodeCell.prototype = new Cell();
113 CodeCell.prototype = new Cell();
114
114
115 /**
115 /**
116 * @method auto_highlight
116 * @method auto_highlight
117 */
117 */
118 CodeCell.prototype.auto_highlight = function () {
118 CodeCell.prototype.auto_highlight = function () {
119 this._auto_highlight(this.config.cell_magic_highlight);
119 this._auto_highlight(this.config.cell_magic_highlight);
120 };
120 };
121
121
122 /** @method create_element */
122 /** @method create_element */
123 CodeCell.prototype.create_element = function () {
123 CodeCell.prototype.create_element = function () {
124 Cell.prototype.create_element.apply(this, arguments);
124 Cell.prototype.create_element.apply(this, arguments);
125
125
126 var cell = $('<div></div>').addClass('cell border-box-sizing code_cell');
126 var cell = $('<div></div>').addClass('cell border-box-sizing code_cell');
127 cell.attr('tabindex','2');
127 cell.attr('tabindex','2');
128
128
129 var input = $('<div></div>').addClass('input');
129 var input = $('<div></div>').addClass('input');
130 var prompt = $('<div/>').addClass('prompt input_prompt');
130 var prompt = $('<div/>').addClass('prompt input_prompt');
131 var inner_cell = $('<div/>').addClass('inner_cell');
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 inner_cell.append(this.celltoolbar.element);
136 inner_cell.append(this.celltoolbar.element);
134 var input_area = $('<div/>').addClass('input_area');
137 var input_area = $('<div/>').addClass('input_area');
135 this.code_mirror = CodeMirror(input_area.get(0), this.cm_config);
138 this.code_mirror = CodeMirror(input_area.get(0), this.cm_config);
136 $(this.code_mirror.getInputField()).attr("spellcheck", "false");
139 $(this.code_mirror.getInputField()).attr("spellcheck", "false");
137 inner_cell.append(input_area);
140 inner_cell.append(input_area);
138 input.append(prompt).append(inner_cell);
141 input.append(prompt).append(inner_cell);
139
142
140 var widget_area = $('<div/>')
143 var widget_area = $('<div/>')
141 .addClass('widget-area')
144 .addClass('widget-area')
142 .hide();
145 .hide();
143 this.widget_area = widget_area;
146 this.widget_area = widget_area;
144 var widget_prompt = $('<div/>')
147 var widget_prompt = $('<div/>')
145 .addClass('prompt')
148 .addClass('prompt')
146 .appendTo(widget_area);
149 .appendTo(widget_area);
147 var widget_subarea = $('<div/>')
150 var widget_subarea = $('<div/>')
148 .addClass('widget-subarea')
151 .addClass('widget-subarea')
149 .appendTo(widget_area);
152 .appendTo(widget_area);
150 this.widget_subarea = widget_subarea;
153 this.widget_subarea = widget_subarea;
151 var widget_clear_buton = $('<button />')
154 var widget_clear_buton = $('<button />')
152 .addClass('close')
155 .addClass('close')
153 .html('&times;')
156 .html('&times;')
154 .click(function() {
157 .click(function() {
155 widget_area.slideUp('', function(){ widget_subarea.html(''); });
158 widget_area.slideUp('', function(){ widget_subarea.html(''); });
156 })
159 })
157 .appendTo(widget_prompt);
160 .appendTo(widget_prompt);
158
161
159 var output = $('<div></div>');
162 var output = $('<div></div>');
160 cell.append(input).append(widget_area).append(output);
163 cell.append(input).append(widget_area).append(output);
161 this.element = cell;
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 this.completer = new completer.Completer(this, this.events);
170 this.completer = new completer.Completer(this, this.events);
164 };
171 };
165
172
166 /** @method bind_events */
173 /** @method bind_events */
167 CodeCell.prototype.bind_events = function () {
174 CodeCell.prototype.bind_events = function () {
168 Cell.prototype.bind_events.apply(this);
175 Cell.prototype.bind_events.apply(this);
169 var that = this;
176 var that = this;
170
177
171 this.element.focusout(
178 this.element.focusout(
172 function() { that.auto_highlight(); }
179 function() { that.auto_highlight(); }
173 );
180 );
174 };
181 };
175
182
176
183
177 /**
184 /**
178 * This method gets called in CodeMirror's onKeyDown/onKeyPress
185 * This method gets called in CodeMirror's onKeyDown/onKeyPress
179 * handlers and is used to provide custom key handling. Its return
186 * handlers and is used to provide custom key handling. Its return
180 * value is used to determine if CodeMirror should ignore the event:
187 * value is used to determine if CodeMirror should ignore the event:
181 * true = ignore, false = don't ignore.
188 * true = ignore, false = don't ignore.
182 * @method handle_codemirror_keyevent
189 * @method handle_codemirror_keyevent
183 */
190 */
184 CodeCell.prototype.handle_codemirror_keyevent = function (editor, event) {
191 CodeCell.prototype.handle_codemirror_keyevent = function (editor, event) {
185
192
186 var that = this;
193 var that = this;
187 // whatever key is pressed, first, cancel the tooltip request before
194 // whatever key is pressed, first, cancel the tooltip request before
188 // they are sent, and remove tooltip if any, except for tab again
195 // they are sent, and remove tooltip if any, except for tab again
189 var tooltip_closed = null;
196 var tooltip_closed = null;
190 if (event.type === 'keydown' && event.which != keycodes.tab ) {
197 if (event.type === 'keydown' && event.which != keycodes.tab ) {
191 tooltip_closed = this.tooltip.remove_and_cancel_tooltip();
198 tooltip_closed = this.tooltip.remove_and_cancel_tooltip();
192 }
199 }
193
200
194 var cur = editor.getCursor();
201 var cur = editor.getCursor();
195 if (event.keyCode === keycodes.enter){
202 if (event.keyCode === keycodes.enter){
196 this.auto_highlight();
203 this.auto_highlight();
197 }
204 }
198
205
199 if (event.which === keycodes.down && event.type === 'keypress' && this.tooltip.time_before_tooltip >= 0) {
206 if (event.which === keycodes.down && event.type === 'keypress' && this.tooltip.time_before_tooltip >= 0) {
200 // triger on keypress (!) otherwise inconsistent event.which depending on plateform
207 // triger on keypress (!) otherwise inconsistent event.which depending on plateform
201 // browser and keyboard layout !
208 // browser and keyboard layout !
202 // Pressing '(' , request tooltip, don't forget to reappend it
209 // Pressing '(' , request tooltip, don't forget to reappend it
203 // The second argument says to hide the tooltip if the docstring
210 // The second argument says to hide the tooltip if the docstring
204 // is actually empty
211 // is actually empty
205 this.tooltip.pending(that, true);
212 this.tooltip.pending(that, true);
206 } else if ( tooltip_closed && event.which === keycodes.esc && event.type === 'keydown') {
213 } else if ( tooltip_closed && event.which === keycodes.esc && event.type === 'keydown') {
207 // If tooltip is active, cancel it. The call to
214 // If tooltip is active, cancel it. The call to
208 // remove_and_cancel_tooltip above doesn't pass, force=true.
215 // remove_and_cancel_tooltip above doesn't pass, force=true.
209 // Because of this it won't actually close the tooltip
216 // Because of this it won't actually close the tooltip
210 // if it is in sticky mode. Thus, we have to check again if it is open
217 // if it is in sticky mode. Thus, we have to check again if it is open
211 // and close it with force=true.
218 // and close it with force=true.
212 if (!this.tooltip._hidden) {
219 if (!this.tooltip._hidden) {
213 this.tooltip.remove_and_cancel_tooltip(true);
220 this.tooltip.remove_and_cancel_tooltip(true);
214 }
221 }
215 // If we closed the tooltip, don't let CM or the global handlers
222 // If we closed the tooltip, don't let CM or the global handlers
216 // handle this event.
223 // handle this event.
217 event.stop();
224 event.stop();
218 return true;
225 return true;
219 } else if (event.keyCode === keycodes.tab && event.type === 'keydown' && event.shiftKey) {
226 } else if (event.keyCode === keycodes.tab && event.type === 'keydown' && event.shiftKey) {
220 if (editor.somethingSelected()){
227 if (editor.somethingSelected()){
221 var anchor = editor.getCursor("anchor");
228 var anchor = editor.getCursor("anchor");
222 var head = editor.getCursor("head");
229 var head = editor.getCursor("head");
223 if( anchor.line != head.line){
230 if( anchor.line != head.line){
224 return false;
231 return false;
225 }
232 }
226 }
233 }
227 this.tooltip.request(that);
234 this.tooltip.request(that);
228 event.stop();
235 event.stop();
229 return true;
236 return true;
230 } else if (event.keyCode === keycodes.tab && event.type == 'keydown') {
237 } else if (event.keyCode === keycodes.tab && event.type == 'keydown') {
231 // Tab completion.
238 // Tab completion.
232 this.tooltip.remove_and_cancel_tooltip();
239 this.tooltip.remove_and_cancel_tooltip();
233 if (editor.somethingSelected()) {
240 if (editor.somethingSelected()) {
234 return false;
241 return false;
235 }
242 }
236 var pre_cursor = editor.getRange({line:cur.line,ch:0},cur);
243 var pre_cursor = editor.getRange({line:cur.line,ch:0},cur);
237 if (pre_cursor.trim() === "") {
244 if (pre_cursor.trim() === "") {
238 // Don't autocomplete if the part of the line before the cursor
245 // Don't autocomplete if the part of the line before the cursor
239 // is empty. In this case, let CodeMirror handle indentation.
246 // is empty. In this case, let CodeMirror handle indentation.
240 return false;
247 return false;
241 } else {
248 } else {
242 event.stop();
249 event.stop();
243 this.completer.startCompletion();
250 this.completer.startCompletion();
244 return true;
251 return true;
245 }
252 }
246 }
253 }
247
254
248 // keyboard event wasn't one of those unique to code cells, let's see
255 // keyboard event wasn't one of those unique to code cells, let's see
249 // if it's one of the generic ones (i.e. check edit mode shortcuts)
256 // if it's one of the generic ones (i.e. check edit mode shortcuts)
250 return Cell.prototype.handle_codemirror_keyevent.apply(this, [editor, event]);
257 return Cell.prototype.handle_codemirror_keyevent.apply(this, [editor, event]);
251 };
258 };
252
259
253 // Kernel related calls.
260 // Kernel related calls.
254
261
255 CodeCell.prototype.set_kernel = function (kernel) {
262 CodeCell.prototype.set_kernel = function (kernel) {
256 this.kernel = kernel;
263 this.kernel = kernel;
257 };
264 };
258
265
259 /**
266 /**
260 * Execute current code cell to the kernel
267 * Execute current code cell to the kernel
261 * @method execute
268 * @method execute
262 */
269 */
263 CodeCell.prototype.execute = function () {
270 CodeCell.prototype.execute = function () {
264 this.output_area.clear_output();
271 this.output_area.clear_output();
265
272
266 // Clear widget area
273 // Clear widget area
267 this.widget_subarea.html('');
274 this.widget_subarea.html('');
268 this.widget_subarea.height('');
275 this.widget_subarea.height('');
269 this.widget_area.height('');
276 this.widget_area.height('');
270 this.widget_area.hide();
277 this.widget_area.hide();
271
278
272 this.set_input_prompt('*');
279 this.set_input_prompt('*');
273 this.element.addClass("running");
280 this.element.addClass("running");
274 if (this.last_msg_id) {
281 if (this.last_msg_id) {
275 this.kernel.clear_callbacks_for_msg(this.last_msg_id);
282 this.kernel.clear_callbacks_for_msg(this.last_msg_id);
276 }
283 }
277 var callbacks = this.get_callbacks();
284 var callbacks = this.get_callbacks();
278
285
279 var old_msg_id = this.last_msg_id;
286 var old_msg_id = this.last_msg_id;
280 this.last_msg_id = this.kernel.execute(this.get_text(), callbacks, {silent: false, store_history: true});
287 this.last_msg_id = this.kernel.execute(this.get_text(), callbacks, {silent: false, store_history: true});
281 if (old_msg_id) {
288 if (old_msg_id) {
282 delete CodeCell.msg_cells[old_msg_id];
289 delete CodeCell.msg_cells[old_msg_id];
283 }
290 }
284 CodeCell.msg_cells[this.last_msg_id] = this;
291 CodeCell.msg_cells[this.last_msg_id] = this;
285 };
292 };
286
293
287 /**
294 /**
288 * Construct the default callbacks for
295 * Construct the default callbacks for
289 * @method get_callbacks
296 * @method get_callbacks
290 */
297 */
291 CodeCell.prototype.get_callbacks = function () {
298 CodeCell.prototype.get_callbacks = function () {
292 return {
299 return {
293 shell : {
300 shell : {
294 reply : $.proxy(this._handle_execute_reply, this),
301 reply : $.proxy(this._handle_execute_reply, this),
295 payload : {
302 payload : {
296 set_next_input : $.proxy(this._handle_set_next_input, this),
303 set_next_input : $.proxy(this._handle_set_next_input, this),
297 page : $.proxy(this._open_with_pager, this)
304 page : $.proxy(this._open_with_pager, this)
298 }
305 }
299 },
306 },
300 iopub : {
307 iopub : {
301 output : $.proxy(this.output_area.handle_output, this.output_area),
308 output : $.proxy(this.output_area.handle_output, this.output_area),
302 clear_output : $.proxy(this.output_area.handle_clear_output, this.output_area),
309 clear_output : $.proxy(this.output_area.handle_clear_output, this.output_area),
303 },
310 },
304 input : $.proxy(this._handle_input_request, this)
311 input : $.proxy(this._handle_input_request, this)
305 };
312 };
306 };
313 };
307
314
308 CodeCell.prototype._open_with_pager = function (payload) {
315 CodeCell.prototype._open_with_pager = function (payload) {
309 this.events.trigger('open_with_text.Pager', payload);
316 this.events.trigger('open_with_text.Pager', payload);
310 };
317 };
311
318
312 /**
319 /**
313 * @method _handle_execute_reply
320 * @method _handle_execute_reply
314 * @private
321 * @private
315 */
322 */
316 CodeCell.prototype._handle_execute_reply = function (msg) {
323 CodeCell.prototype._handle_execute_reply = function (msg) {
317 this.set_input_prompt(msg.content.execution_count);
324 this.set_input_prompt(msg.content.execution_count);
318 this.element.removeClass("running");
325 this.element.removeClass("running");
319 this.events.trigger('set_dirty.Notebook', {value: true});
326 this.events.trigger('set_dirty.Notebook', {value: true});
320 };
327 };
321
328
322 /**
329 /**
323 * @method _handle_set_next_input
330 * @method _handle_set_next_input
324 * @private
331 * @private
325 */
332 */
326 CodeCell.prototype._handle_set_next_input = function (payload) {
333 CodeCell.prototype._handle_set_next_input = function (payload) {
327 var data = {'cell': this, 'text': payload.text};
334 var data = {'cell': this, 'text': payload.text};
328 this.events.trigger('set_next_input.Notebook', data);
335 this.events.trigger('set_next_input.Notebook', data);
329 };
336 };
330
337
331 /**
338 /**
332 * @method _handle_input_request
339 * @method _handle_input_request
333 * @private
340 * @private
334 */
341 */
335 CodeCell.prototype._handle_input_request = function (msg) {
342 CodeCell.prototype._handle_input_request = function (msg) {
336 this.output_area.append_raw_input(msg);
343 this.output_area.append_raw_input(msg);
337 };
344 };
338
345
339
346
340 // Basic cell manipulation.
347 // Basic cell manipulation.
341
348
342 CodeCell.prototype.select = function () {
349 CodeCell.prototype.select = function () {
343 var cont = Cell.prototype.select.apply(this);
350 var cont = Cell.prototype.select.apply(this);
344 if (cont) {
351 if (cont) {
345 this.code_mirror.refresh();
352 this.code_mirror.refresh();
346 this.auto_highlight();
353 this.auto_highlight();
347 }
354 }
348 return cont;
355 return cont;
349 };
356 };
350
357
351 CodeCell.prototype.render = function () {
358 CodeCell.prototype.render = function () {
352 var cont = Cell.prototype.render.apply(this);
359 var cont = Cell.prototype.render.apply(this);
353 // Always execute, even if we are already in the rendered state
360 // Always execute, even if we are already in the rendered state
354 return cont;
361 return cont;
355 };
362 };
356
363
357 CodeCell.prototype.unrender = function () {
364 CodeCell.prototype.unrender = function () {
358 // CodeCell is always rendered
365 // CodeCell is always rendered
359 return false;
366 return false;
360 };
367 };
361
368
362 CodeCell.prototype.select_all = function () {
369 CodeCell.prototype.select_all = function () {
363 var start = {line: 0, ch: 0};
370 var start = {line: 0, ch: 0};
364 var nlines = this.code_mirror.lineCount();
371 var nlines = this.code_mirror.lineCount();
365 var last_line = this.code_mirror.getLine(nlines-1);
372 var last_line = this.code_mirror.getLine(nlines-1);
366 var end = {line: nlines-1, ch: last_line.length};
373 var end = {line: nlines-1, ch: last_line.length};
367 this.code_mirror.setSelection(start, end);
374 this.code_mirror.setSelection(start, end);
368 };
375 };
369
376
370
377
371 CodeCell.prototype.collapse_output = function () {
378 CodeCell.prototype.collapse_output = function () {
372 this.collapsed = true;
379 this.collapsed = true;
373 this.output_area.collapse();
380 this.output_area.collapse();
374 };
381 };
375
382
376
383
377 CodeCell.prototype.expand_output = function () {
384 CodeCell.prototype.expand_output = function () {
378 this.collapsed = false;
385 this.collapsed = false;
379 this.output_area.expand();
386 this.output_area.expand();
380 this.output_area.unscroll_area();
387 this.output_area.unscroll_area();
381 };
388 };
382
389
383 CodeCell.prototype.scroll_output = function () {
390 CodeCell.prototype.scroll_output = function () {
384 this.output_area.expand();
391 this.output_area.expand();
385 this.output_area.scroll_if_long();
392 this.output_area.scroll_if_long();
386 };
393 };
387
394
388 CodeCell.prototype.toggle_output = function () {
395 CodeCell.prototype.toggle_output = function () {
389 this.collapsed = Boolean(1 - this.collapsed);
396 this.collapsed = Boolean(1 - this.collapsed);
390 this.output_area.toggle_output();
397 this.output_area.toggle_output();
391 };
398 };
392
399
393 CodeCell.prototype.toggle_output_scroll = function () {
400 CodeCell.prototype.toggle_output_scroll = function () {
394 this.output_area.toggle_scroll();
401 this.output_area.toggle_scroll();
395 };
402 };
396
403
397
404
398 CodeCell.input_prompt_classical = function (prompt_value, lines_number) {
405 CodeCell.input_prompt_classical = function (prompt_value, lines_number) {
399 var ns;
406 var ns;
400 if (prompt_value === undefined) {
407 if (prompt_value === undefined) {
401 ns = "&nbsp;";
408 ns = "&nbsp;";
402 } else {
409 } else {
403 ns = encodeURIComponent(prompt_value);
410 ns = encodeURIComponent(prompt_value);
404 }
411 }
405 return 'In&nbsp;[' + ns + ']:';
412 return 'In&nbsp;[' + ns + ']:';
406 };
413 };
407
414
408 CodeCell.input_prompt_continuation = function (prompt_value, lines_number) {
415 CodeCell.input_prompt_continuation = function (prompt_value, lines_number) {
409 var html = [CodeCell.input_prompt_classical(prompt_value, lines_number)];
416 var html = [CodeCell.input_prompt_classical(prompt_value, lines_number)];
410 for(var i=1; i < lines_number; i++) {
417 for(var i=1; i < lines_number; i++) {
411 html.push(['...:']);
418 html.push(['...:']);
412 }
419 }
413 return html.join('<br/>');
420 return html.join('<br/>');
414 };
421 };
415
422
416 CodeCell.input_prompt_function = CodeCell.input_prompt_classical;
423 CodeCell.input_prompt_function = CodeCell.input_prompt_classical;
417
424
418
425
419 CodeCell.prototype.set_input_prompt = function (number) {
426 CodeCell.prototype.set_input_prompt = function (number) {
420 var nline = 1;
427 var nline = 1;
421 if (this.code_mirror !== undefined) {
428 if (this.code_mirror !== undefined) {
422 nline = this.code_mirror.lineCount();
429 nline = this.code_mirror.lineCount();
423 }
430 }
424 this.input_prompt_number = number;
431 this.input_prompt_number = number;
425 var prompt_html = CodeCell.input_prompt_function(this.input_prompt_number, nline);
432 var prompt_html = CodeCell.input_prompt_function(this.input_prompt_number, nline);
426 // This HTML call is okay because the user contents are escaped.
433 // This HTML call is okay because the user contents are escaped.
427 this.element.find('div.input_prompt').html(prompt_html);
434 this.element.find('div.input_prompt').html(prompt_html);
428 };
435 };
429
436
430
437
431 CodeCell.prototype.clear_input = function () {
438 CodeCell.prototype.clear_input = function () {
432 this.code_mirror.setValue('');
439 this.code_mirror.setValue('');
433 };
440 };
434
441
435
442
436 CodeCell.prototype.get_text = function () {
443 CodeCell.prototype.get_text = function () {
437 return this.code_mirror.getValue();
444 return this.code_mirror.getValue();
438 };
445 };
439
446
440
447
441 CodeCell.prototype.set_text = function (code) {
448 CodeCell.prototype.set_text = function (code) {
442 return this.code_mirror.setValue(code);
449 return this.code_mirror.setValue(code);
443 };
450 };
444
451
445
452
446 CodeCell.prototype.clear_output = function (wait) {
453 CodeCell.prototype.clear_output = function (wait) {
447 this.output_area.clear_output(wait);
454 this.output_area.clear_output(wait);
448 this.set_input_prompt();
455 this.set_input_prompt();
449 };
456 };
450
457
451
458
452 // JSON serialization
459 // JSON serialization
453
460
454 CodeCell.prototype.fromJSON = function (data) {
461 CodeCell.prototype.fromJSON = function (data) {
455 Cell.prototype.fromJSON.apply(this, arguments);
462 Cell.prototype.fromJSON.apply(this, arguments);
456 if (data.cell_type === 'code') {
463 if (data.cell_type === 'code') {
457 if (data.input !== undefined) {
464 if (data.input !== undefined) {
458 this.set_text(data.input);
465 this.set_text(data.input);
459 // make this value the starting point, so that we can only undo
466 // make this value the starting point, so that we can only undo
460 // to this state, instead of a blank cell
467 // to this state, instead of a blank cell
461 this.code_mirror.clearHistory();
468 this.code_mirror.clearHistory();
462 this.auto_highlight();
469 this.auto_highlight();
463 }
470 }
464 if (data.prompt_number !== undefined) {
471 if (data.prompt_number !== undefined) {
465 this.set_input_prompt(data.prompt_number);
472 this.set_input_prompt(data.prompt_number);
466 } else {
473 } else {
467 this.set_input_prompt();
474 this.set_input_prompt();
468 }
475 }
469 this.output_area.trusted = data.trusted || false;
476 this.output_area.trusted = data.trusted || false;
470 this.output_area.fromJSON(data.outputs);
477 this.output_area.fromJSON(data.outputs);
471 if (data.collapsed !== undefined) {
478 if (data.collapsed !== undefined) {
472 if (data.collapsed) {
479 if (data.collapsed) {
473 this.collapse_output();
480 this.collapse_output();
474 } else {
481 } else {
475 this.expand_output();
482 this.expand_output();
476 }
483 }
477 }
484 }
478 }
485 }
479 };
486 };
480
487
481
488
482 CodeCell.prototype.toJSON = function () {
489 CodeCell.prototype.toJSON = function () {
483 var data = Cell.prototype.toJSON.apply(this);
490 var data = Cell.prototype.toJSON.apply(this);
484 data.input = this.get_text();
491 data.input = this.get_text();
485 // is finite protect against undefined and '*' value
492 // is finite protect against undefined and '*' value
486 if (isFinite(this.input_prompt_number)) {
493 if (isFinite(this.input_prompt_number)) {
487 data.prompt_number = this.input_prompt_number;
494 data.prompt_number = this.input_prompt_number;
488 }
495 }
489 var outputs = this.output_area.toJSON();
496 var outputs = this.output_area.toJSON();
490 data.outputs = outputs;
497 data.outputs = outputs;
491 data.language = 'python';
498 data.language = 'python';
492 data.trusted = this.output_area.trusted;
499 data.trusted = this.output_area.trusted;
493 data.collapsed = this.collapsed;
500 data.collapsed = this.collapsed;
494 return data;
501 return data;
495 };
502 };
496
503
497 /**
504 /**
498 * handle cell level logic when a cell is unselected
505 * handle cell level logic when a cell is unselected
499 * @method unselect
506 * @method unselect
500 * @return is the action being taken
507 * @return is the action being taken
501 */
508 */
502 CodeCell.prototype.unselect = function () {
509 CodeCell.prototype.unselect = function () {
503 var cont = Cell.prototype.unselect.apply(this);
510 var cont = Cell.prototype.unselect.apply(this);
504 if (cont) {
511 if (cont) {
505 // When a code cell is usnelected, make sure that the corresponding
512 // When a code cell is usnelected, make sure that the corresponding
506 // tooltip and completer to that cell is closed.
513 // tooltip and completer to that cell is closed.
507 this.tooltip.remove_and_cancel_tooltip(true);
514 this.tooltip.remove_and_cancel_tooltip(true);
508 if (this.completer !== null) {
515 if (this.completer !== null) {
509 this.completer.close();
516 this.completer.close();
510 }
517 }
511 }
518 }
512 return cont;
519 return cont;
513 };
520 };
514
521
515 // Backwards compatability.
522 // Backwards compatability.
516 IPython.CodeCell = CodeCell;
523 IPython.CodeCell = CodeCell;
517
524
518 return {'CodeCell': CodeCell};
525 return {'CodeCell': CodeCell};
519 });
526 });
@@ -1,138 +1,135 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 require([
4 require([
5 'base/js/namespace',
5 'base/js/namespace',
6 'jquery',
6 'jquery',
7 'notebook/js/notebook',
7 'notebook/js/notebook',
8 'base/js/utils',
8 'base/js/utils',
9 'base/js/keyboard',
10 'base/js/page',
9 'base/js/page',
11 'notebook/js/layoutmanager',
10 'notebook/js/layoutmanager',
12 'base/js/events',
11 'base/js/events',
13 'auth/js/loginwidget',
12 'auth/js/loginwidget',
14 'notebook/js/maintoolbar',
13 'notebook/js/maintoolbar',
15 'notebook/js/pager',
14 'notebook/js/pager',
16 'notebook/js/quickhelp',
15 'notebook/js/quickhelp',
17 'notebook/js/menubar',
16 'notebook/js/menubar',
18 'notebook/js/notificationarea',
17 'notebook/js/notificationarea',
19 'notebook/js/savewidget',
18 'notebook/js/savewidget',
20 'notebook/js/keyboardmanager',
19 'notebook/js/keyboardmanager',
21 'notebook/js/config',
20 'notebook/js/config',
22 ], function(
21 ], function(
23 IPython,
22 IPython,
24 $,
23 $,
25 notebook,
24 notebook,
26 utils,
25 utils,
27 keyboard,
28 page,
26 page,
29 layoutmanager,
27 layoutmanager,
30 events,
28 events,
31 loginwidget,
29 loginwidget,
32 maintoolbar,
30 maintoolbar,
33 pager,
31 pager,
34 quickhelp,
32 quickhelp,
35 menubar,
33 menubar,
36 notificationarea,
34 notificationarea,
37 savewidget,
35 savewidget,
38 keyboardmanager,
36 keyboardmanager,
39 config
37 config
40 ) {
38 ) {
41 "use strict";
39 "use strict";
42
40
43 $('#ipython-main-app').addClass('border-box-sizing');
41 $('#ipython-main-app').addClass('border-box-sizing');
44 $('div#notebook_panel').addClass('border-box-sizing');
42 $('div#notebook_panel').addClass('border-box-sizing');
45
43
46 var common_options = {
44 var common_options = {
47 base_url : utils.get_body_data("baseUrl"),
45 base_url : utils.get_body_data("baseUrl"),
48 notebook_path : utils.get_body_data("notebookPath"),
46 notebook_path : utils.get_body_data("notebookPath"),
49 notebook_name : utils.get_body_data('notebookName')
47 notebook_name : utils.get_body_data('notebookName')
50 };
48 };
51
49
52 var user_config = $.extend({}, config.default_config);
50 var user_config = $.extend({}, config.default_config);
53 var page = new page.Page();
51 var page = new page.Page();
54 var layout_manager = new layoutmanager.LayoutManager();
52 var layout_manager = new layoutmanager.LayoutManager();
55 var events = $([new events.Events()]);
53 var events = $([new events.Events()]);
56 var pager = new pager.Pager('div#pager', 'div#pager_splitter', {
54 var pager = new pager.Pager('div#pager', 'div#pager_splitter', {
57 layout_manager: layout_manager,
55 layout_manager: layout_manager,
58 events: events});
56 events: events});
59 var keyboard_manager = new keyboardmanager.KeyboardManager({
57 var keyboard_manager = new keyboardmanager.KeyboardManager({
60 pager: pager,
58 pager: pager,
61 events: events});
59 events: events});
62 var save_widget = new savewidget.SaveWidget('span#save_widget', {
60 var save_widget = new savewidget.SaveWidget('span#save_widget', {
63 events: events,
61 events: events,
64 keyboard_manager: keyboard_manager});
62 keyboard_manager: keyboard_manager});
65 var notebook = new notebook.Notebook('div#notebook', $.extend({
63 var notebook = new notebook.Notebook('div#notebook', $.extend({
66 events: events,
64 events: events,
67 keyboard_manager: keyboard_manager,
65 keyboard_manager: keyboard_manager,
68 save_widget: save_widget,
66 save_widget: save_widget,
69 config: user_config},
67 config: user_config},
70 common_options));
68 common_options));
71 var login_widget = new loginwidget.LoginWidget('span#login_widget', common_options);
69 var login_widget = new loginwidget.LoginWidget('span#login_widget', common_options);
72 var toolbar = new maintoolbar.MainToolBar('#maintoolbar-container', {
70 var toolbar = new maintoolbar.MainToolBar('#maintoolbar-container', {
73 notebook: notebook,
71 notebook: notebook,
74 events: events});
72 events: events});
75 var quick_help = new quickhelp.QuickHelp({
73 var quick_help = new quickhelp.QuickHelp({
76 keyboard_manager: keyboard_manager,
74 keyboard_manager: keyboard_manager,
77 events: events,
75 events: events,
78 notebook: notebook});
76 notebook: notebook});
79 var menubar = new menubar.MenuBar('#menubar', $.extend({
77 var menubar = new menubar.MenuBar('#menubar', $.extend({
80 notebook: notebook,
78 notebook: notebook,
81 layout_manager: layout_manager,
79 layout_manager: layout_manager,
82 events: events,
80 events: events,
83 save_widget: save_widget,
81 save_widget: save_widget,
84 quick_help: quick_help},
82 quick_help: quick_help},
85 common_options));
83 common_options));
86 var notification_area = new notificationarea.NotificationArea(
84 var notification_area = new notificationarea.NotificationArea(
87 '#notification_area', {
85 '#notification_area', {
88 events: events,
86 events: events,
89 save_widget: save_widget,
87 save_widget: save_widget,
90 notebook: notebook,
88 notebook: notebook,
91 keyboard_manager: keyboard_manager});
89 keyboard_manager: keyboard_manager});
92 notification_area.init_notification_widgets();
90 notification_area.init_notification_widgets();
93
91
94 $('body').append('<div id="fonttest"><pre><span id="test1">x</span>'+
92 $('body').append('<div id="fonttest"><pre><span id="test1">x</span>'+
95 '<span id="test2" style="font-weight: bold;">x</span>'+
93 '<span id="test2" style="font-weight: bold;">x</span>'+
96 '<span id="test3" style="font-style: italic;">x</span></pre></div>');
94 '<span id="test3" style="font-style: italic;">x</span></pre></div>');
97 var nh = $('#test1').innerHeight();
95 var nh = $('#test1').innerHeight();
98 var bh = $('#test2').innerHeight();
96 var bh = $('#test2').innerHeight();
99 var ih = $('#test3').innerHeight();
97 var ih = $('#test3').innerHeight();
100 if(nh != bh || nh != ih) {
98 if(nh != bh || nh != ih) {
101 $('head').append('<style>.CodeMirror span { vertical-align: bottom; }</style>');
99 $('head').append('<style>.CodeMirror span { vertical-align: bottom; }</style>');
102 }
100 }
103 $('#fonttest').remove();
101 $('#fonttest').remove();
104
102
105 page.show();
103 page.show();
106
104
107 layout_manager.do_resize();
105 layout_manager.do_resize();
108 var first_load = function () {
106 var first_load = function () {
109 layout_manager.do_resize();
107 layout_manager.do_resize();
110 var hash = document.location.hash;
108 var hash = document.location.hash;
111 if (hash) {
109 if (hash) {
112 document.location.hash = '';
110 document.location.hash = '';
113 document.location.hash = hash;
111 document.location.hash = hash;
114 }
112 }
115 notebook.set_autosave_interval(notebook.minimum_autosave_interval);
113 notebook.set_autosave_interval(notebook.minimum_autosave_interval);
116 // only do this once
114 // only do this once
117 events.off('notebook_loaded.Notebook', first_load);
115 events.off('notebook_loaded.Notebook', first_load);
118 };
116 };
119
117
120 events.on('notebook_loaded.Notebook', first_load);
118 events.on('notebook_loaded.Notebook', first_load);
121 events.trigger('app_initialized.NotebookApp');
119 events.trigger('app_initialized.NotebookApp');
122 notebook.load_notebook(common_options.notebook_name, common_options.notebook_path);
120 notebook.load_notebook(common_options.notebook_name, common_options.notebook_path);
123
121
124 IPython.page = page;
122 IPython.page = page;
125 IPython.layout_manager = layout_manager;
123 IPython.layout_manager = layout_manager;
126 IPython.notebook = notebook;
124 IPython.notebook = notebook;
127 IPython.pager = pager;
125 IPython.pager = pager;
128 IPython.quick_help = quick_help;
126 IPython.quick_help = quick_help;
129 IPython.login_widget = login_widget;
127 IPython.login_widget = login_widget;
130 IPython.menubar = menubar;
128 IPython.menubar = menubar;
131 IPython.toolbar = toolbar;
129 IPython.toolbar = toolbar;
132 IPython.notification_area = notification_area;
130 IPython.notification_area = notification_area;
133 IPython.events = events;
131 IPython.events = events;
134 IPython.keyboard_manager = keyboard_manager;
132 IPython.keyboard_manager = keyboard_manager;
135 IPython.save_widget = save_widget;
133 IPython.save_widget = save_widget;
136 IPython.config = user_config;
134 IPython.config = user_config;
137 IPython.keyboard = keyboard;
138 });
135 });
@@ -1,348 +1,350 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 define([
4 define([
5 'base/js/namespace',
5 'base/js/namespace',
6 'jquery',
6 'jquery',
7 'base/js/utils',
7 'base/js/utils',
8 'notebook/js/tour',
8 'notebook/js/tour',
9 ], function(IPython, $, utils, tour) {
9 ], function(IPython, $, utils, tour) {
10 "use strict";
10 "use strict";
11
11
12 var MenuBar = function (selector, options) {
12 var MenuBar = function (selector, options) {
13 // Constructor
13 // Constructor
14 //
14 //
15 // A MenuBar Class to generate the menubar of IPython notebook
15 // A MenuBar Class to generate the menubar of IPython notebook
16 //
16 //
17 // Parameters:
17 // Parameters:
18 // selector: string
18 // selector: string
19 // options: dictionary
19 // options: dictionary
20 // Dictionary of keyword arguments.
20 // Dictionary of keyword arguments.
21 // notebook: Notebook instance
21 // notebook: Notebook instance
22 // layout_manager: LayoutManager instance
22 // layout_manager: LayoutManager instance
23 // events: $(Events) instance
23 // events: $(Events) instance
24 // save_widget: SaveWidget instance
24 // save_widget: SaveWidget instance
25 // quick_help: QuickHelp instance
25 // quick_help: QuickHelp instance
26 // base_url : string
26 // base_url : string
27 // notebook_path : string
27 // notebook_path : string
28 // notebook_name : string
28 // notebook_name : string
29 options = options || {};
29 options = options || {};
30 this.base_url = options.base_url || utils.get_body_data("baseUrl");
30 this.base_url = options.base_url || utils.get_body_data("baseUrl");
31 this.selector = selector;
31 this.selector = selector;
32 this.notebook = options.notebook;
32 this.notebook = options.notebook;
33 this.layout_manager = options.layout_manager;
33 this.layout_manager = options.layout_manager;
34 this.events = options.events;
34 this.events = options.events;
35 this.save_widget = options.save_widget;
35 this.save_widget = options.save_widget;
36 this.quick_help = options.quick_help;
36 this.quick_help = options.quick_help;
37
37
38 try {
38 try {
39 this.tour = new tour.Tour(this.notebook, this.events);
39 this.tour = new tour.Tour(this.notebook, this.events);
40 } catch (e) {
40 } catch (e) {
41 this.tour = undefined;
41 this.tour = undefined;
42 console.log("Failed to instantiate Notebook Tour", e);
42 console.log("Failed to instantiate Notebook Tour", e);
43 }
43 }
44
44
45 if (this.selector !== undefined) {
45 if (this.selector !== undefined) {
46 this.element = $(selector);
46 this.element = $(selector);
47 this.style();
47 this.style();
48 this.bind_events();
48 this.bind_events();
49 }
49 }
50 };
50 };
51
51
52 MenuBar.prototype.style = function () {
52 MenuBar.prototype.style = function () {
53 var that = this;
53 var that = this;
54 this.element.addClass('border-box-sizing');
54 this.element.addClass('border-box-sizing');
55 this.element.find("li").click(function (event, ui) {
55 this.element.find("li").click(function (event, ui) {
56 // The selected cell loses focus when the menu is entered, so we
56 // The selected cell loses focus when the menu is entered, so we
57 // re-select it upon selection.
57 // re-select it upon selection.
58 var i = that.notebook.get_selected_index();
58 var i = that.notebook.get_selected_index();
59 that.notebook.select(i);
59 that.notebook.select(i);
60 }
60 }
61 );
61 );
62 };
62 };
63
63
64 MenuBar.prototype._nbconvert = function (format, download) {
64 MenuBar.prototype._nbconvert = function (format, download) {
65 download = download || false;
65 download = download || false;
66 var notebook_path = this.notebook.notebook_path;
66 var notebook_path = this.notebook.notebook_path;
67 var notebook_name = this.notebook.notebook_name;
67 var notebook_name = this.notebook.notebook_name;
68 if (this.notebook.dirty) {
68 if (this.notebook.dirty) {
69 this.notebook.save_notebook({async : false});
69 this.notebook.save_notebook({async : false});
70 }
70 }
71 var url = utils.url_join_encode(
71 var url = utils.url_join_encode(
72 this.base_url,
72 this.base_url,
73 'nbconvert',
73 'nbconvert',
74 format,
74 format,
75 notebook_path,
75 notebook_path,
76 notebook_name
76 notebook_name
77 ) + "?download=" + download.toString();
77 ) + "?download=" + download.toString();
78
78
79 window.open(url);
79 window.open(url);
80 };
80 };
81
81
82 MenuBar.prototype.bind_events = function () {
82 MenuBar.prototype.bind_events = function () {
83 // File
83 // File
84 var that = this;
84 var that = this;
85 this.element.find('#new_notebook').click(function () {
85 this.element.find('#new_notebook').click(function () {
86 that.notebook.new_notebook();
86 that.notebook.new_notebook();
87 });
87 });
88 this.element.find('#open_notebook').click(function () {
88 this.element.find('#open_notebook').click(function () {
89 window.open(utils.url_join_encode(
89 window.open(utils.url_join_encode(
90 that.notebook.base_url,
90 that.notebook.base_url,
91 'tree',
91 'tree',
92 that.notebook.notebook_path
92 that.notebook.notebook_path
93 ));
93 ));
94 });
94 });
95 this.element.find('#copy_notebook').click(function () {
95 this.element.find('#copy_notebook').click(function () {
96 that.notebook.copy_notebook();
96 that.notebook.copy_notebook();
97 return false;
97 return false;
98 });
98 });
99 this.element.find('#download_ipynb').click(function () {
99 this.element.find('#download_ipynb').click(function () {
100 var base_url = that.notebook.base_url;
100 var base_url = that.notebook.base_url;
101 var notebook_path = that.notebook.notebook_path;
101 var notebook_path = that.notebook.notebook_path;
102 var notebook_name = that.notebook.notebook_name;
102 var notebook_name = that.notebook.notebook_name;
103 if (that.notebook.dirty) {
103 if (that.notebook.dirty) {
104 that.notebook.save_notebook({async : false});
104 that.notebook.save_notebook({async : false});
105 }
105 }
106
106
107 var url = utils.url_join_encode(
107 var url = utils.url_join_encode(
108 base_url,
108 base_url,
109 'files',
109 'files',
110 notebook_path,
110 notebook_path,
111 notebook_name
111 notebook_name
112 );
112 );
113 window.location.assign(url);
113 window.location.assign(url);
114 });
114 });
115
115
116 this.element.find('#print_preview').click(function () {
116 this.element.find('#print_preview').click(function () {
117 that._nbconvert('html', false);
117 that._nbconvert('html', false);
118 });
118 });
119
119
120 this.element.find('#download_py').click(function () {
120 this.element.find('#download_py').click(function () {
121 that._nbconvert('python', true);
121 that._nbconvert('python', true);
122 });
122 });
123
123
124 this.element.find('#download_html').click(function () {
124 this.element.find('#download_html').click(function () {
125 that._nbconvert('html', true);
125 that._nbconvert('html', true);
126 });
126 });
127
127
128 this.element.find('#download_rst').click(function () {
128 this.element.find('#download_rst').click(function () {
129 that._nbconvert('rst', true);
129 that._nbconvert('rst', true);
130 });
130 });
131
131
132 this.element.find('#download_pdf').click(function () {
132 this.element.find('#download_pdf').click(function () {
133 that._nbconvert('pdf', true);
133 that._nbconvert('pdf', true);
134 });
134 });
135
135
136 this.element.find('#rename_notebook').click(function () {
136 this.element.find('#rename_notebook').click(function () {
137 that.save_widget.rename_notebook({notebook: that.notebook});
137 that.save_widget.rename_notebook({notebook: that.notebook});
138 });
138 });
139 this.element.find('#save_checkpoint').click(function () {
139 this.element.find('#save_checkpoint').click(function () {
140 that.notebook.save_checkpoint();
140 that.notebook.save_checkpoint();
141 });
141 });
142 this.element.find('#restore_checkpoint').click(function () {
142 this.element.find('#restore_checkpoint').click(function () {
143 });
143 });
144 this.element.find('#trust_notebook').click(function () {
144 this.element.find('#trust_notebook').click(function () {
145 that.notebook.trust_notebook();
145 that.notebook.trust_notebook();
146 });
146 });
147 this.events.on('trust_changed.Notebook', function (event, trusted) {
147 this.events.on('trust_changed.Notebook', function (event, trusted) {
148 if (trusted) {
148 if (trusted) {
149 that.element.find('#trust_notebook')
149 that.element.find('#trust_notebook')
150 .addClass("disabled")
150 .addClass("disabled")
151 .find("a").text("Trusted Notebook");
151 .find("a").text("Trusted Notebook");
152 } else {
152 } else {
153 that.element.find('#trust_notebook')
153 that.element.find('#trust_notebook')
154 .removeClass("disabled")
154 .removeClass("disabled")
155 .find("a").text("Trust Notebook");
155 .find("a").text("Trust Notebook");
156 }
156 }
157 });
157 });
158 this.element.find('#kill_and_exit').click(function () {
158 this.element.find('#kill_and_exit').click(function () {
159 that.notebook.session.delete();
159 that.notebook.session.delete();
160 setTimeout(function(){
160 setTimeout(function(){
161 // allow closing of new tabs in Chromium, impossible in FF
161 // allow closing of new tabs in Chromium, impossible in FF
162 window.open('', '_self', '');
162 window.open('', '_self', '');
163 window.close();
163 window.close();
164 }, 500);
164 }, 500);
165 });
165 });
166 // Edit
166 // Edit
167 this.element.find('#cut_cell').click(function () {
167 this.element.find('#cut_cell').click(function () {
168 that.notebook.cut_cell();
168 that.notebook.cut_cell();
169 });
169 });
170 this.element.find('#copy_cell').click(function () {
170 this.element.find('#copy_cell').click(function () {
171 that.notebook.copy_cell();
171 that.notebook.copy_cell();
172 });
172 });
173 this.element.find('#delete_cell').click(function () {
173 this.element.find('#delete_cell').click(function () {
174 that.notebook.delete_cell();
174 that.notebook.delete_cell();
175 });
175 });
176 this.element.find('#undelete_cell').click(function () {
176 this.element.find('#undelete_cell').click(function () {
177 that.notebook.undelete_cell();
177 that.notebook.undelete_cell();
178 });
178 });
179 this.element.find('#split_cell').click(function () {
179 this.element.find('#split_cell').click(function () {
180 that.notebook.split_cell();
180 that.notebook.split_cell();
181 });
181 });
182 this.element.find('#merge_cell_above').click(function () {
182 this.element.find('#merge_cell_above').click(function () {
183 that.notebook.merge_cell_above();
183 that.notebook.merge_cell_above();
184 });
184 });
185 this.element.find('#merge_cell_below').click(function () {
185 this.element.find('#merge_cell_below').click(function () {
186 that.notebook.merge_cell_below();
186 that.notebook.merge_cell_below();
187 });
187 });
188 this.element.find('#move_cell_up').click(function () {
188 this.element.find('#move_cell_up').click(function () {
189 that.notebook.move_cell_up();
189 that.notebook.move_cell_up();
190 });
190 });
191 this.element.find('#move_cell_down').click(function () {
191 this.element.find('#move_cell_down').click(function () {
192 that.notebook.move_cell_down();
192 that.notebook.move_cell_down();
193 });
193 });
194 this.element.find('#edit_nb_metadata').click(function () {
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 // View
200 // View
199 this.element.find('#toggle_header').click(function () {
201 this.element.find('#toggle_header').click(function () {
200 $('div#header').toggle();
202 $('div#header').toggle();
201 that.layout_manager.do_resize();
203 that.layout_manager.do_resize();
202 });
204 });
203 this.element.find('#toggle_toolbar').click(function () {
205 this.element.find('#toggle_toolbar').click(function () {
204 $('div#maintoolbar').toggle();
206 $('div#maintoolbar').toggle();
205 that.layout_manager.do_resize();
207 that.layout_manager.do_resize();
206 });
208 });
207 // Insert
209 // Insert
208 this.element.find('#insert_cell_above').click(function () {
210 this.element.find('#insert_cell_above').click(function () {
209 that.notebook.insert_cell_above('code');
211 that.notebook.insert_cell_above('code');
210 that.notebook.select_prev();
212 that.notebook.select_prev();
211 });
213 });
212 this.element.find('#insert_cell_below').click(function () {
214 this.element.find('#insert_cell_below').click(function () {
213 that.notebook.insert_cell_below('code');
215 that.notebook.insert_cell_below('code');
214 that.notebook.select_next();
216 that.notebook.select_next();
215 });
217 });
216 // Cell
218 // Cell
217 this.element.find('#run_cell').click(function () {
219 this.element.find('#run_cell').click(function () {
218 that.notebook.execute_cell();
220 that.notebook.execute_cell();
219 });
221 });
220 this.element.find('#run_cell_select_below').click(function () {
222 this.element.find('#run_cell_select_below').click(function () {
221 that.notebook.execute_cell_and_select_below();
223 that.notebook.execute_cell_and_select_below();
222 });
224 });
223 this.element.find('#run_cell_insert_below').click(function () {
225 this.element.find('#run_cell_insert_below').click(function () {
224 that.notebook.execute_cell_and_insert_below();
226 that.notebook.execute_cell_and_insert_below();
225 });
227 });
226 this.element.find('#run_all_cells').click(function () {
228 this.element.find('#run_all_cells').click(function () {
227 that.notebook.execute_all_cells();
229 that.notebook.execute_all_cells();
228 });
230 });
229 this.element.find('#run_all_cells_above').click(function () {
231 this.element.find('#run_all_cells_above').click(function () {
230 that.notebook.execute_cells_above();
232 that.notebook.execute_cells_above();
231 });
233 });
232 this.element.find('#run_all_cells_below').click(function () {
234 this.element.find('#run_all_cells_below').click(function () {
233 that.notebook.execute_cells_below();
235 that.notebook.execute_cells_below();
234 });
236 });
235 this.element.find('#to_code').click(function () {
237 this.element.find('#to_code').click(function () {
236 that.notebook.to_code();
238 that.notebook.to_code();
237 });
239 });
238 this.element.find('#to_markdown').click(function () {
240 this.element.find('#to_markdown').click(function () {
239 that.notebook.to_markdown();
241 that.notebook.to_markdown();
240 });
242 });
241 this.element.find('#to_raw').click(function () {
243 this.element.find('#to_raw').click(function () {
242 that.notebook.to_raw();
244 that.notebook.to_raw();
243 });
245 });
244 this.element.find('#to_heading1').click(function () {
246 this.element.find('#to_heading1').click(function () {
245 that.notebook.to_heading(undefined, 1);
247 that.notebook.to_heading(undefined, 1);
246 });
248 });
247 this.element.find('#to_heading2').click(function () {
249 this.element.find('#to_heading2').click(function () {
248 that.notebook.to_heading(undefined, 2);
250 that.notebook.to_heading(undefined, 2);
249 });
251 });
250 this.element.find('#to_heading3').click(function () {
252 this.element.find('#to_heading3').click(function () {
251 that.notebook.to_heading(undefined, 3);
253 that.notebook.to_heading(undefined, 3);
252 });
254 });
253 this.element.find('#to_heading4').click(function () {
255 this.element.find('#to_heading4').click(function () {
254 that.notebook.to_heading(undefined, 4);
256 that.notebook.to_heading(undefined, 4);
255 });
257 });
256 this.element.find('#to_heading5').click(function () {
258 this.element.find('#to_heading5').click(function () {
257 that.notebook.to_heading(undefined, 5);
259 that.notebook.to_heading(undefined, 5);
258 });
260 });
259 this.element.find('#to_heading6').click(function () {
261 this.element.find('#to_heading6').click(function () {
260 that.notebook.to_heading(undefined, 6);
262 that.notebook.to_heading(undefined, 6);
261 });
263 });
262
264
263 this.element.find('#toggle_current_output').click(function () {
265 this.element.find('#toggle_current_output').click(function () {
264 that.notebook.toggle_output();
266 that.notebook.toggle_output();
265 });
267 });
266 this.element.find('#toggle_current_output_scroll').click(function () {
268 this.element.find('#toggle_current_output_scroll').click(function () {
267 that.notebook.toggle_output_scroll();
269 that.notebook.toggle_output_scroll();
268 });
270 });
269 this.element.find('#clear_current_output').click(function () {
271 this.element.find('#clear_current_output').click(function () {
270 that.notebook.clear_output();
272 that.notebook.clear_output();
271 });
273 });
272
274
273 this.element.find('#toggle_all_output').click(function () {
275 this.element.find('#toggle_all_output').click(function () {
274 that.notebook.toggle_all_output();
276 that.notebook.toggle_all_output();
275 });
277 });
276 this.element.find('#toggle_all_output_scroll').click(function () {
278 this.element.find('#toggle_all_output_scroll').click(function () {
277 that.notebook.toggle_all_output_scroll();
279 that.notebook.toggle_all_output_scroll();
278 });
280 });
279 this.element.find('#clear_all_output').click(function () {
281 this.element.find('#clear_all_output').click(function () {
280 that.notebook.clear_all_output();
282 that.notebook.clear_all_output();
281 });
283 });
282
284
283 // Kernel
285 // Kernel
284 this.element.find('#int_kernel').click(function () {
286 this.element.find('#int_kernel').click(function () {
285 that.notebook.session.interrupt_kernel();
287 that.notebook.session.interrupt_kernel();
286 });
288 });
287 this.element.find('#restart_kernel').click(function () {
289 this.element.find('#restart_kernel').click(function () {
288 that.notebook.restart_kernel();
290 that.notebook.restart_kernel();
289 });
291 });
290 // Help
292 // Help
291 if (this.tour) {
293 if (this.tour) {
292 this.element.find('#notebook_tour').click(function () {
294 this.element.find('#notebook_tour').click(function () {
293 that.tour.start();
295 that.tour.start();
294 });
296 });
295 } else {
297 } else {
296 this.element.find('#notebook_tour').addClass("disabled");
298 this.element.find('#notebook_tour').addClass("disabled");
297 }
299 }
298 this.element.find('#keyboard_shortcuts').click(function () {
300 this.element.find('#keyboard_shortcuts').click(function () {
299 that.quick_help.show_keyboard_shortcuts();
301 that.quick_help.show_keyboard_shortcuts();
300 });
302 });
301
303
302 this.update_restore_checkpoint(null);
304 this.update_restore_checkpoint(null);
303
305
304 this.events.on('checkpoints_listed.Notebook', function (event, data) {
306 this.events.on('checkpoints_listed.Notebook', function (event, data) {
305 that.update_restore_checkpoint(that.notebook.checkpoints);
307 that.update_restore_checkpoint(that.notebook.checkpoints);
306 });
308 });
307
309
308 this.events.on('checkpoint_created.Notebook', function (event, data) {
310 this.events.on('checkpoint_created.Notebook', function (event, data) {
309 that.update_restore_checkpoint(that.notebook.checkpoints);
311 that.update_restore_checkpoint(that.notebook.checkpoints);
310 });
312 });
311 };
313 };
312
314
313 MenuBar.prototype.update_restore_checkpoint = function(checkpoints) {
315 MenuBar.prototype.update_restore_checkpoint = function(checkpoints) {
314 var ul = this.element.find("#restore_checkpoint").find("ul");
316 var ul = this.element.find("#restore_checkpoint").find("ul");
315 ul.empty();
317 ul.empty();
316 if (!checkpoints || checkpoints.length === 0) {
318 if (!checkpoints || checkpoints.length === 0) {
317 ul.append(
319 ul.append(
318 $("<li/>")
320 $("<li/>")
319 .addClass("disabled")
321 .addClass("disabled")
320 .append(
322 .append(
321 $("<a/>")
323 $("<a/>")
322 .text("No checkpoints")
324 .text("No checkpoints")
323 )
325 )
324 );
326 );
325 return;
327 return;
326 }
328 }
327
329
328 var that = this;
330 var that = this;
329 checkpoints.map(function (checkpoint) {
331 checkpoints.map(function (checkpoint) {
330 var d = new Date(checkpoint.last_modified);
332 var d = new Date(checkpoint.last_modified);
331 ul.append(
333 ul.append(
332 $("<li/>").append(
334 $("<li/>").append(
333 $("<a/>")
335 $("<a/>")
334 .attr("href", "#")
336 .attr("href", "#")
335 .text(d.format("mmm dd HH:MM:ss"))
337 .text(d.format("mmm dd HH:MM:ss"))
336 .click(function () {
338 .click(function () {
337 that.notebook.restore_checkpoint_dialog(checkpoint);
339 that.notebook.restore_checkpoint_dialog(checkpoint);
338 })
340 })
339 )
341 )
340 );
342 );
341 });
343 });
342 };
344 };
343
345
344 // Backwards compatability.
346 // Backwards compatability.
345 IPython.MenuBar = MenuBar;
347 IPython.MenuBar = MenuBar;
346
348
347 return {'MenuBar': MenuBar};
349 return {'MenuBar': MenuBar};
348 });
350 });
@@ -1,2514 +1,2512 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 define([
4 define([
5 'base/js/namespace',
5 'base/js/namespace',
6 'jquery',
6 'jquery',
7 'base/js/utils',
7 'base/js/utils',
8 'base/js/dialog',
8 'base/js/dialog',
9 'notebook/js/textcell',
9 'notebook/js/textcell',
10 'notebook/js/codecell',
10 'notebook/js/codecell',
11 'services/sessions/js/session',
11 'services/sessions/js/session',
12 'notebook/js/celltoolbar',
12 'notebook/js/celltoolbar',
13 'components/marked/lib/marked',
13 'components/marked/lib/marked',
14 'notebook/js/mathjaxutils',
14 'notebook/js/mathjaxutils',
15 'base/js/keyboard',
15 'base/js/keyboard',
16 'components/jquery-ui/ui/minified/jquery-ui.min',
17 'components/bootstrap/js/bootstrap.min',
18 ], function (
16 ], function (
19 IPython,
17 IPython,
20 $,
18 $,
21 utils,
19 utils,
22 dialog,
20 dialog,
23 textcell,
21 textcell,
24 codecell,
22 codecell,
25 session,
23 session,
26 celltoolbar,
24 celltoolbar,
27 marked,
25 marked,
28 mathjaxutils,
26 mathjaxutils,
29 keyboard
27 keyboard
30 ) {
28 ) {
31
29
32 var Notebook = function (selector, options) {
30 var Notebook = function (selector, options) {
33 // Constructor
31 // Constructor
34 //
32 //
35 // A notebook contains and manages cells.
33 // A notebook contains and manages cells.
36 //
34 //
37 // Parameters:
35 // Parameters:
38 // selector: string
36 // selector: string
39 // options: dictionary
37 // options: dictionary
40 // Dictionary of keyword arguments.
38 // Dictionary of keyword arguments.
41 // events: $(Events) instance
39 // events: $(Events) instance
42 // keyboard_manager: KeyboardManager instance
40 // keyboard_manager: KeyboardManager instance
43 // save_widget: SaveWidget instance
41 // save_widget: SaveWidget instance
44 // config: dictionary
42 // config: dictionary
45 // base_url : string
43 // base_url : string
46 // notebook_path : string
44 // notebook_path : string
47 // notebook_name : string
45 // notebook_name : string
48 this.config = options.config || {};
46 this.config = options.config || {};
49 this.base_url = options.base_url;
47 this.base_url = options.base_url;
50 this.notebook_path = options.notebook_path;
48 this.notebook_path = options.notebook_path;
51 this.notebook_name = options.notebook_name;
49 this.notebook_name = options.notebook_name;
52 this.events = options.events;
50 this.events = options.events;
53 this.keyboard_manager = options.keyboard_manager;
51 this.keyboard_manager = options.keyboard_manager;
54 this.save_widget = options.save_widget;
52 this.save_widget = options.save_widget;
55 // TODO: This code smells (and the other `= this` line a couple lines down)
53 // TODO: This code smells (and the other `= this` line a couple lines down)
56 // We need a better way to deal with circular instance references.
54 // We need a better way to deal with circular instance references.
57 this.keyboard_manager.notebook = this;
55 this.keyboard_manager.notebook = this;
58 this.save_widget.notebook = this;
56 this.save_widget.notebook = this;
59
57
60 mathjaxutils.init();
58 mathjaxutils.init();
61
59
62 if (marked) {
60 if (marked) {
63 marked.setOptions({
61 marked.setOptions({
64 gfm : true,
62 gfm : true,
65 tables: true,
63 tables: true,
66 langPrefix: "language-",
64 langPrefix: "language-",
67 highlight: function(code, lang) {
65 highlight: function(code, lang) {
68 if (!lang) {
66 if (!lang) {
69 // no language, no highlight
67 // no language, no highlight
70 return code;
68 return code;
71 }
69 }
72 var highlighted;
70 var highlighted;
73 try {
71 try {
74 highlighted = hljs.highlight(lang, code, false);
72 highlighted = hljs.highlight(lang, code, false);
75 } catch(err) {
73 } catch(err) {
76 highlighted = hljs.highlightAuto(code);
74 highlighted = hljs.highlightAuto(code);
77 }
75 }
78 return highlighted.value;
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 this.element = $(selector);
81 this.element = $(selector);
89 this.element.scroll();
82 this.element.scroll();
90 this.element.data("notebook", this);
83 this.element.data("notebook", this);
91 this.next_prompt_number = 1;
84 this.next_prompt_number = 1;
92 this.session = null;
85 this.session = null;
93 this.kernel = null;
86 this.kernel = null;
94 this.clipboard = null;
87 this.clipboard = null;
95 this.undelete_backup = null;
88 this.undelete_backup = null;
96 this.undelete_index = null;
89 this.undelete_index = null;
97 this.undelete_below = false;
90 this.undelete_below = false;
98 this.paste_enabled = false;
91 this.paste_enabled = false;
99 // It is important to start out in command mode to match the intial mode
92 // It is important to start out in command mode to match the intial mode
100 // of the KeyboardManager.
93 // of the KeyboardManager.
101 this.mode = 'command';
94 this.mode = 'command';
102 this.set_dirty(false);
95 this.set_dirty(false);
103 this.metadata = {};
96 this.metadata = {};
104 this._checkpoint_after_save = false;
97 this._checkpoint_after_save = false;
105 this.last_checkpoint = null;
98 this.last_checkpoint = null;
106 this.checkpoints = [];
99 this.checkpoints = [];
107 this.autosave_interval = 0;
100 this.autosave_interval = 0;
108 this.autosave_timer = null;
101 this.autosave_timer = null;
109 // autosave *at most* every two minutes
102 // autosave *at most* every two minutes
110 this.minimum_autosave_interval = 120000;
103 this.minimum_autosave_interval = 120000;
111 // single worksheet for now
104 // single worksheet for now
112 this.worksheet_metadata = {};
105 this.worksheet_metadata = {};
113 this.notebook_name_blacklist_re = /[\/\\:]/;
106 this.notebook_name_blacklist_re = /[\/\\:]/;
114 this.nbformat = 3; // Increment this when changing the nbformat
107 this.nbformat = 3; // Increment this when changing the nbformat
115 this.nbformat_minor = 0; // Increment this when changing the nbformat
108 this.nbformat_minor = 0; // Increment this when changing the nbformat
116 this.style();
109 this.style();
117 this.create_elements();
110 this.create_elements();
118 this.bind_events();
111 this.bind_events();
119 this.save_notebook = function() { // don't allow save until notebook_loaded
112 this.save_notebook = function() { // don't allow save until notebook_loaded
120 this.save_notebook_error(null, null, "Load failed, save is disabled");
113 this.save_notebook_error(null, null, "Load failed, save is disabled");
121 };
114 };
122 };
115 };
123
116
124 /**
117 /**
125 * Tweak the notebook's CSS style.
118 * Tweak the notebook's CSS style.
126 *
119 *
127 * @method style
120 * @method style
128 */
121 */
129 Notebook.prototype.style = function () {
122 Notebook.prototype.style = function () {
130 $('div#notebook').addClass('border-box-sizing');
123 $('div#notebook').addClass('border-box-sizing');
131 };
124 };
132
125
133 /**
126 /**
134 * Create an HTML and CSS representation of the notebook.
127 * Create an HTML and CSS representation of the notebook.
135 *
128 *
136 * @method create_elements
129 * @method create_elements
137 */
130 */
138 Notebook.prototype.create_elements = function () {
131 Notebook.prototype.create_elements = function () {
139 var that = this;
132 var that = this;
140 this.element.attr('tabindex','-1');
133 this.element.attr('tabindex','-1');
141 this.container = $("<div/>").addClass("container").attr("id", "notebook-container");
134 this.container = $("<div/>").addClass("container").attr("id", "notebook-container");
142 // We add this end_space div to the end of the notebook div to:
135 // We add this end_space div to the end of the notebook div to:
143 // i) provide a margin between the last cell and the end of the notebook
136 // i) provide a margin between the last cell and the end of the notebook
144 // ii) to prevent the div from scrolling up when the last cell is being
137 // ii) to prevent the div from scrolling up when the last cell is being
145 // edited, but is too low on the page, which browsers will do automatically.
138 // edited, but is too low on the page, which browsers will do automatically.
146 var end_space = $('<div/>').addClass('end_space');
139 var end_space = $('<div/>').addClass('end_space');
147 end_space.dblclick(function (e) {
140 end_space.dblclick(function (e) {
148 var ncells = that.ncells();
141 var ncells = that.ncells();
149 that.insert_cell_below('code',ncells-1);
142 that.insert_cell_below('code',ncells-1);
150 });
143 });
151 this.element.append(this.container);
144 this.element.append(this.container);
152 this.container.append(end_space);
145 this.container.append(end_space);
153 };
146 };
154
147
155 /**
148 /**
156 * Bind JavaScript events: key presses and custom IPython events.
149 * Bind JavaScript events: key presses and custom IPython events.
157 *
150 *
158 * @method bind_events
151 * @method bind_events
159 */
152 */
160 Notebook.prototype.bind_events = function () {
153 Notebook.prototype.bind_events = function () {
161 var that = this;
154 var that = this;
162
155
163 this.events.on('set_next_input.Notebook', function (event, data) {
156 this.events.on('set_next_input.Notebook', function (event, data) {
164 var index = that.find_cell_index(data.cell);
157 var index = that.find_cell_index(data.cell);
165 var new_cell = that.insert_cell_below('code',index);
158 var new_cell = that.insert_cell_below('code',index);
166 new_cell.set_text(data.text);
159 new_cell.set_text(data.text);
167 that.dirty = true;
160 that.dirty = true;
168 });
161 });
169
162
170 this.events.on('set_dirty.Notebook', function (event, data) {
163 this.events.on('set_dirty.Notebook', function (event, data) {
171 that.dirty = data.value;
164 that.dirty = data.value;
172 });
165 });
173
166
174 this.events.on('trust_changed.Notebook', function (event, data) {
167 this.events.on('trust_changed.Notebook', function (event, data) {
175 that.trusted = data.value;
168 that.trusted = data.value;
176 });
169 });
177
170
178 this.events.on('select.Cell', function (event, data) {
171 this.events.on('select.Cell', function (event, data) {
179 var index = that.find_cell_index(data.cell);
172 var index = that.find_cell_index(data.cell);
180 that.select(index);
173 that.select(index);
181 });
174 });
182
175
183 this.events.on('edit_mode.Cell', function (event, data) {
176 this.events.on('edit_mode.Cell', function (event, data) {
184 that.handle_edit_mode(data.cell);
177 that.handle_edit_mode(data.cell);
185 });
178 });
186
179
187 this.events.on('command_mode.Cell', function (event, data) {
180 this.events.on('command_mode.Cell', function (event, data) {
188 that.handle_command_mode(data.cell);
181 that.handle_command_mode(data.cell);
189 });
182 });
190
183
191 this.events.on('status_autorestarting.Kernel', function () {
184 this.events.on('status_autorestarting.Kernel', function () {
192 dialog.modal({
185 dialog.modal({
193 notebook: that,
186 notebook: that,
194 keyboard_manager: that.keyboard_manager,
187 keyboard_manager: that.keyboard_manager,
195 title: "Kernel Restarting",
188 title: "Kernel Restarting",
196 body: "The kernel appears to have died. It will restart automatically.",
189 body: "The kernel appears to have died. It will restart automatically.",
197 buttons: {
190 buttons: {
198 OK : {
191 OK : {
199 class : "btn-primary"
192 class : "btn-primary"
200 }
193 }
201 }
194 }
202 });
195 });
203 });
196 });
204
197
205 var collapse_time = function (time) {
198 var collapse_time = function (time) {
206 var app_height = $('#ipython-main-app').height(); // content height
199 var app_height = $('#ipython-main-app').height(); // content height
207 var splitter_height = $('div#pager_splitter').outerHeight(true);
200 var splitter_height = $('div#pager_splitter').outerHeight(true);
208 var new_height = app_height - splitter_height;
201 var new_height = app_height - splitter_height;
209 that.element.animate({height : new_height + 'px'}, time);
202 that.element.animate({height : new_height + 'px'}, time);
210 };
203 };
211
204
212 this.element.bind('collapse_pager', function (event, extrap) {
205 this.element.bind('collapse_pager', function (event, extrap) {
213 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
206 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
214 collapse_time(time);
207 collapse_time(time);
215 });
208 });
216
209
217 var expand_time = function (time) {
210 var expand_time = function (time) {
218 var app_height = $('#ipython-main-app').height(); // content height
211 var app_height = $('#ipython-main-app').height(); // content height
219 var splitter_height = $('div#pager_splitter').outerHeight(true);
212 var splitter_height = $('div#pager_splitter').outerHeight(true);
220 var pager_height = $('div#pager').outerHeight(true);
213 var pager_height = $('div#pager').outerHeight(true);
221 var new_height = app_height - pager_height - splitter_height;
214 var new_height = app_height - pager_height - splitter_height;
222 that.element.animate({height : new_height + 'px'}, time);
215 that.element.animate({height : new_height + 'px'}, time);
223 };
216 };
224
217
225 this.element.bind('expand_pager', function (event, extrap) {
218 this.element.bind('expand_pager', function (event, extrap) {
226 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
219 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
227 expand_time(time);
220 expand_time(time);
228 });
221 });
229
222
230 // Firefox 22 broke $(window).on("beforeunload")
223 // Firefox 22 broke $(window).on("beforeunload")
231 // I'm not sure why or how.
224 // I'm not sure why or how.
232 window.onbeforeunload = function (e) {
225 window.onbeforeunload = function (e) {
233 // TODO: Make killing the kernel configurable.
226 // TODO: Make killing the kernel configurable.
234 var kill_kernel = false;
227 var kill_kernel = false;
235 if (kill_kernel) {
228 if (kill_kernel) {
236 that.session.kill_kernel();
229 that.session.kill_kernel();
237 }
230 }
238 // if we are autosaving, trigger an autosave on nav-away.
231 // if we are autosaving, trigger an autosave on nav-away.
239 // still warn, because if we don't the autosave may fail.
232 // still warn, because if we don't the autosave may fail.
240 if (that.dirty) {
233 if (that.dirty) {
241 if ( that.autosave_interval ) {
234 if ( that.autosave_interval ) {
242 // schedule autosave in a timeout
235 // schedule autosave in a timeout
243 // this gives you a chance to forcefully discard changes
236 // this gives you a chance to forcefully discard changes
244 // by reloading the page if you *really* want to.
237 // by reloading the page if you *really* want to.
245 // the timer doesn't start until you *dismiss* the dialog.
238 // the timer doesn't start until you *dismiss* the dialog.
246 setTimeout(function () {
239 setTimeout(function () {
247 if (that.dirty) {
240 if (that.dirty) {
248 that.save_notebook();
241 that.save_notebook();
249 }
242 }
250 }, 1000);
243 }, 1000);
251 return "Autosave in progress, latest changes may be lost.";
244 return "Autosave in progress, latest changes may be lost.";
252 } else {
245 } else {
253 return "Unsaved changes will be lost.";
246 return "Unsaved changes will be lost.";
254 }
247 }
255 }
248 }
256 // Null is the *only* return value that will make the browser not
249 // Null is the *only* return value that will make the browser not
257 // pop up the "don't leave" dialog.
250 // pop up the "don't leave" dialog.
258 return null;
251 return null;
259 };
252 };
260 };
253 };
261
254
262 /**
255 /**
263 * Set the dirty flag, and trigger the set_dirty.Notebook event
256 * Set the dirty flag, and trigger the set_dirty.Notebook event
264 *
257 *
265 * @method set_dirty
258 * @method set_dirty
266 */
259 */
267 Notebook.prototype.set_dirty = function (value) {
260 Notebook.prototype.set_dirty = function (value) {
268 if (value === undefined) {
261 if (value === undefined) {
269 value = true;
262 value = true;
270 }
263 }
271 if (this.dirty == value) {
264 if (this.dirty == value) {
272 return;
265 return;
273 }
266 }
274 this.events.trigger('set_dirty.Notebook', {value: value});
267 this.events.trigger('set_dirty.Notebook', {value: value});
275 };
268 };
276
269
277 /**
270 /**
278 * Scroll the top of the page to a given cell.
271 * Scroll the top of the page to a given cell.
279 *
272 *
280 * @method scroll_to_cell
273 * @method scroll_to_cell
281 * @param {Number} cell_number An index of the cell to view
274 * @param {Number} cell_number An index of the cell to view
282 * @param {Number} time Animation time in milliseconds
275 * @param {Number} time Animation time in milliseconds
283 * @return {Number} Pixel offset from the top of the container
276 * @return {Number} Pixel offset from the top of the container
284 */
277 */
285 Notebook.prototype.scroll_to_cell = function (cell_number, time) {
278 Notebook.prototype.scroll_to_cell = function (cell_number, time) {
286 var cells = this.get_cells();
279 var cells = this.get_cells();
287 time = time || 0;
280 time = time || 0;
288 cell_number = Math.min(cells.length-1,cell_number);
281 cell_number = Math.min(cells.length-1,cell_number);
289 cell_number = Math.max(0 ,cell_number);
282 cell_number = Math.max(0 ,cell_number);
290 var scroll_value = cells[cell_number].element.position().top-cells[0].element.position().top ;
283 var scroll_value = cells[cell_number].element.position().top-cells[0].element.position().top ;
291 this.element.animate({scrollTop:scroll_value}, time);
284 this.element.animate({scrollTop:scroll_value}, time);
292 return scroll_value;
285 return scroll_value;
293 };
286 };
294
287
295 /**
288 /**
296 * Scroll to the bottom of the page.
289 * Scroll to the bottom of the page.
297 *
290 *
298 * @method scroll_to_bottom
291 * @method scroll_to_bottom
299 */
292 */
300 Notebook.prototype.scroll_to_bottom = function () {
293 Notebook.prototype.scroll_to_bottom = function () {
301 this.element.animate({scrollTop:this.element.get(0).scrollHeight}, 0);
294 this.element.animate({scrollTop:this.element.get(0).scrollHeight}, 0);
302 };
295 };
303
296
304 /**
297 /**
305 * Scroll to the top of the page.
298 * Scroll to the top of the page.
306 *
299 *
307 * @method scroll_to_top
300 * @method scroll_to_top
308 */
301 */
309 Notebook.prototype.scroll_to_top = function () {
302 Notebook.prototype.scroll_to_top = function () {
310 this.element.animate({scrollTop:0}, 0);
303 this.element.animate({scrollTop:0}, 0);
311 };
304 };
312
305
313 // Edit Notebook metadata
306 // Edit Notebook metadata
314
307
315 Notebook.prototype.edit_metadata = function () {
308 Notebook.prototype.edit_metadata = function () {
316 var that = this;
309 var that = this;
317 dialog.edit_metadata(this.metadata, function (md) {
310 dialog.edit_metadata({
318 that.metadata = md;
311 md: this.metadata,
319 }, 'Notebook');
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 // Cell indexing, retrieval, etc.
320 // Cell indexing, retrieval, etc.
323
321
324 /**
322 /**
325 * Get all cell elements in the notebook.
323 * Get all cell elements in the notebook.
326 *
324 *
327 * @method get_cell_elements
325 * @method get_cell_elements
328 * @return {jQuery} A selector of all cell elements
326 * @return {jQuery} A selector of all cell elements
329 */
327 */
330 Notebook.prototype.get_cell_elements = function () {
328 Notebook.prototype.get_cell_elements = function () {
331 return this.container.children("div.cell");
329 return this.container.children("div.cell");
332 };
330 };
333
331
334 /**
332 /**
335 * Get a particular cell element.
333 * Get a particular cell element.
336 *
334 *
337 * @method get_cell_element
335 * @method get_cell_element
338 * @param {Number} index An index of a cell to select
336 * @param {Number} index An index of a cell to select
339 * @return {jQuery} A selector of the given cell.
337 * @return {jQuery} A selector of the given cell.
340 */
338 */
341 Notebook.prototype.get_cell_element = function (index) {
339 Notebook.prototype.get_cell_element = function (index) {
342 var result = null;
340 var result = null;
343 var e = this.get_cell_elements().eq(index);
341 var e = this.get_cell_elements().eq(index);
344 if (e.length !== 0) {
342 if (e.length !== 0) {
345 result = e;
343 result = e;
346 }
344 }
347 return result;
345 return result;
348 };
346 };
349
347
350 /**
348 /**
351 * Try to get a particular cell by msg_id.
349 * Try to get a particular cell by msg_id.
352 *
350 *
353 * @method get_msg_cell
351 * @method get_msg_cell
354 * @param {String} msg_id A message UUID
352 * @param {String} msg_id A message UUID
355 * @return {Cell} Cell or null if no cell was found.
353 * @return {Cell} Cell or null if no cell was found.
356 */
354 */
357 Notebook.prototype.get_msg_cell = function (msg_id) {
355 Notebook.prototype.get_msg_cell = function (msg_id) {
358 return codecell.CodeCell.msg_cells[msg_id] || null;
356 return codecell.CodeCell.msg_cells[msg_id] || null;
359 };
357 };
360
358
361 /**
359 /**
362 * Count the cells in this notebook.
360 * Count the cells in this notebook.
363 *
361 *
364 * @method ncells
362 * @method ncells
365 * @return {Number} The number of cells in this notebook
363 * @return {Number} The number of cells in this notebook
366 */
364 */
367 Notebook.prototype.ncells = function () {
365 Notebook.prototype.ncells = function () {
368 return this.get_cell_elements().length;
366 return this.get_cell_elements().length;
369 };
367 };
370
368
371 /**
369 /**
372 * Get all Cell objects in this notebook.
370 * Get all Cell objects in this notebook.
373 *
371 *
374 * @method get_cells
372 * @method get_cells
375 * @return {Array} This notebook's Cell objects
373 * @return {Array} This notebook's Cell objects
376 */
374 */
377 // TODO: we are often calling cells as cells()[i], which we should optimize
375 // TODO: we are often calling cells as cells()[i], which we should optimize
378 // to cells(i) or a new method.
376 // to cells(i) or a new method.
379 Notebook.prototype.get_cells = function () {
377 Notebook.prototype.get_cells = function () {
380 return this.get_cell_elements().toArray().map(function (e) {
378 return this.get_cell_elements().toArray().map(function (e) {
381 return $(e).data("cell");
379 return $(e).data("cell");
382 });
380 });
383 };
381 };
384
382
385 /**
383 /**
386 * Get a Cell object from this notebook.
384 * Get a Cell object from this notebook.
387 *
385 *
388 * @method get_cell
386 * @method get_cell
389 * @param {Number} index An index of a cell to retrieve
387 * @param {Number} index An index of a cell to retrieve
390 * @return {Cell} A particular cell
388 * @return {Cell} A particular cell
391 */
389 */
392 Notebook.prototype.get_cell = function (index) {
390 Notebook.prototype.get_cell = function (index) {
393 var result = null;
391 var result = null;
394 var ce = this.get_cell_element(index);
392 var ce = this.get_cell_element(index);
395 if (ce !== null) {
393 if (ce !== null) {
396 result = ce.data('cell');
394 result = ce.data('cell');
397 }
395 }
398 return result;
396 return result;
399 };
397 };
400
398
401 /**
399 /**
402 * Get the cell below a given cell.
400 * Get the cell below a given cell.
403 *
401 *
404 * @method get_next_cell
402 * @method get_next_cell
405 * @param {Cell} cell The provided cell
403 * @param {Cell} cell The provided cell
406 * @return {Cell} The next cell
404 * @return {Cell} The next cell
407 */
405 */
408 Notebook.prototype.get_next_cell = function (cell) {
406 Notebook.prototype.get_next_cell = function (cell) {
409 var result = null;
407 var result = null;
410 var index = this.find_cell_index(cell);
408 var index = this.find_cell_index(cell);
411 if (this.is_valid_cell_index(index+1)) {
409 if (this.is_valid_cell_index(index+1)) {
412 result = this.get_cell(index+1);
410 result = this.get_cell(index+1);
413 }
411 }
414 return result;
412 return result;
415 };
413 };
416
414
417 /**
415 /**
418 * Get the cell above a given cell.
416 * Get the cell above a given cell.
419 *
417 *
420 * @method get_prev_cell
418 * @method get_prev_cell
421 * @param {Cell} cell The provided cell
419 * @param {Cell} cell The provided cell
422 * @return {Cell} The previous cell
420 * @return {Cell} The previous cell
423 */
421 */
424 Notebook.prototype.get_prev_cell = function (cell) {
422 Notebook.prototype.get_prev_cell = function (cell) {
425 // TODO: off-by-one
423 // TODO: off-by-one
426 // nb.get_prev_cell(nb.get_cell(1)) is null
424 // nb.get_prev_cell(nb.get_cell(1)) is null
427 var result = null;
425 var result = null;
428 var index = this.find_cell_index(cell);
426 var index = this.find_cell_index(cell);
429 if (index !== null && index > 1) {
427 if (index !== null && index > 1) {
430 result = this.get_cell(index-1);
428 result = this.get_cell(index-1);
431 }
429 }
432 return result;
430 return result;
433 };
431 };
434
432
435 /**
433 /**
436 * Get the numeric index of a given cell.
434 * Get the numeric index of a given cell.
437 *
435 *
438 * @method find_cell_index
436 * @method find_cell_index
439 * @param {Cell} cell The provided cell
437 * @param {Cell} cell The provided cell
440 * @return {Number} The cell's numeric index
438 * @return {Number} The cell's numeric index
441 */
439 */
442 Notebook.prototype.find_cell_index = function (cell) {
440 Notebook.prototype.find_cell_index = function (cell) {
443 var result = null;
441 var result = null;
444 this.get_cell_elements().filter(function (index) {
442 this.get_cell_elements().filter(function (index) {
445 if ($(this).data("cell") === cell) {
443 if ($(this).data("cell") === cell) {
446 result = index;
444 result = index;
447 }
445 }
448 });
446 });
449 return result;
447 return result;
450 };
448 };
451
449
452 /**
450 /**
453 * Get a given index , or the selected index if none is provided.
451 * Get a given index , or the selected index if none is provided.
454 *
452 *
455 * @method index_or_selected
453 * @method index_or_selected
456 * @param {Number} index A cell's index
454 * @param {Number} index A cell's index
457 * @return {Number} The given index, or selected index if none is provided.
455 * @return {Number} The given index, or selected index if none is provided.
458 */
456 */
459 Notebook.prototype.index_or_selected = function (index) {
457 Notebook.prototype.index_or_selected = function (index) {
460 var i;
458 var i;
461 if (index === undefined || index === null) {
459 if (index === undefined || index === null) {
462 i = this.get_selected_index();
460 i = this.get_selected_index();
463 if (i === null) {
461 if (i === null) {
464 i = 0;
462 i = 0;
465 }
463 }
466 } else {
464 } else {
467 i = index;
465 i = index;
468 }
466 }
469 return i;
467 return i;
470 };
468 };
471
469
472 /**
470 /**
473 * Get the currently selected cell.
471 * Get the currently selected cell.
474 * @method get_selected_cell
472 * @method get_selected_cell
475 * @return {Cell} The selected cell
473 * @return {Cell} The selected cell
476 */
474 */
477 Notebook.prototype.get_selected_cell = function () {
475 Notebook.prototype.get_selected_cell = function () {
478 var index = this.get_selected_index();
476 var index = this.get_selected_index();
479 return this.get_cell(index);
477 return this.get_cell(index);
480 };
478 };
481
479
482 /**
480 /**
483 * Check whether a cell index is valid.
481 * Check whether a cell index is valid.
484 *
482 *
485 * @method is_valid_cell_index
483 * @method is_valid_cell_index
486 * @param {Number} index A cell index
484 * @param {Number} index A cell index
487 * @return True if the index is valid, false otherwise
485 * @return True if the index is valid, false otherwise
488 */
486 */
489 Notebook.prototype.is_valid_cell_index = function (index) {
487 Notebook.prototype.is_valid_cell_index = function (index) {
490 if (index !== null && index >= 0 && index < this.ncells()) {
488 if (index !== null && index >= 0 && index < this.ncells()) {
491 return true;
489 return true;
492 } else {
490 } else {
493 return false;
491 return false;
494 }
492 }
495 };
493 };
496
494
497 /**
495 /**
498 * Get the index of the currently selected cell.
496 * Get the index of the currently selected cell.
499
497
500 * @method get_selected_index
498 * @method get_selected_index
501 * @return {Number} The selected cell's numeric index
499 * @return {Number} The selected cell's numeric index
502 */
500 */
503 Notebook.prototype.get_selected_index = function () {
501 Notebook.prototype.get_selected_index = function () {
504 var result = null;
502 var result = null;
505 this.get_cell_elements().filter(function (index) {
503 this.get_cell_elements().filter(function (index) {
506 if ($(this).data("cell").selected === true) {
504 if ($(this).data("cell").selected === true) {
507 result = index;
505 result = index;
508 }
506 }
509 });
507 });
510 return result;
508 return result;
511 };
509 };
512
510
513
511
514 // Cell selection.
512 // Cell selection.
515
513
516 /**
514 /**
517 * Programmatically select a cell.
515 * Programmatically select a cell.
518 *
516 *
519 * @method select
517 * @method select
520 * @param {Number} index A cell's index
518 * @param {Number} index A cell's index
521 * @return {Notebook} This notebook
519 * @return {Notebook} This notebook
522 */
520 */
523 Notebook.prototype.select = function (index) {
521 Notebook.prototype.select = function (index) {
524 if (this.is_valid_cell_index(index)) {
522 if (this.is_valid_cell_index(index)) {
525 var sindex = this.get_selected_index();
523 var sindex = this.get_selected_index();
526 if (sindex !== null && index !== sindex) {
524 if (sindex !== null && index !== sindex) {
527 // If we are about to select a different cell, make sure we are
525 // If we are about to select a different cell, make sure we are
528 // first in command mode.
526 // first in command mode.
529 if (this.mode !== 'command') {
527 if (this.mode !== 'command') {
530 this.command_mode();
528 this.command_mode();
531 }
529 }
532 this.get_cell(sindex).unselect();
530 this.get_cell(sindex).unselect();
533 }
531 }
534 var cell = this.get_cell(index);
532 var cell = this.get_cell(index);
535 cell.select();
533 cell.select();
536 if (cell.cell_type === 'heading') {
534 if (cell.cell_type === 'heading') {
537 this.events.trigger('selected_cell_type_changed.Notebook',
535 this.events.trigger('selected_cell_type_changed.Notebook',
538 {'cell_type':cell.cell_type,level:cell.level}
536 {'cell_type':cell.cell_type,level:cell.level}
539 );
537 );
540 } else {
538 } else {
541 this.events.trigger('selected_cell_type_changed.Notebook',
539 this.events.trigger('selected_cell_type_changed.Notebook',
542 {'cell_type':cell.cell_type}
540 {'cell_type':cell.cell_type}
543 );
541 );
544 }
542 }
545 }
543 }
546 return this;
544 return this;
547 };
545 };
548
546
549 /**
547 /**
550 * Programmatically select the next cell.
548 * Programmatically select the next cell.
551 *
549 *
552 * @method select_next
550 * @method select_next
553 * @return {Notebook} This notebook
551 * @return {Notebook} This notebook
554 */
552 */
555 Notebook.prototype.select_next = function () {
553 Notebook.prototype.select_next = function () {
556 var index = this.get_selected_index();
554 var index = this.get_selected_index();
557 this.select(index+1);
555 this.select(index+1);
558 return this;
556 return this;
559 };
557 };
560
558
561 /**
559 /**
562 * Programmatically select the previous cell.
560 * Programmatically select the previous cell.
563 *
561 *
564 * @method select_prev
562 * @method select_prev
565 * @return {Notebook} This notebook
563 * @return {Notebook} This notebook
566 */
564 */
567 Notebook.prototype.select_prev = function () {
565 Notebook.prototype.select_prev = function () {
568 var index = this.get_selected_index();
566 var index = this.get_selected_index();
569 this.select(index-1);
567 this.select(index-1);
570 return this;
568 return this;
571 };
569 };
572
570
573
571
574 // Edit/Command mode
572 // Edit/Command mode
575
573
576 /**
574 /**
577 * Gets the index of the cell that is in edit mode.
575 * Gets the index of the cell that is in edit mode.
578 *
576 *
579 * @method get_edit_index
577 * @method get_edit_index
580 *
578 *
581 * @return index {int}
579 * @return index {int}
582 **/
580 **/
583 Notebook.prototype.get_edit_index = function () {
581 Notebook.prototype.get_edit_index = function () {
584 var result = null;
582 var result = null;
585 this.get_cell_elements().filter(function (index) {
583 this.get_cell_elements().filter(function (index) {
586 if ($(this).data("cell").mode === 'edit') {
584 if ($(this).data("cell").mode === 'edit') {
587 result = index;
585 result = index;
588 }
586 }
589 });
587 });
590 return result;
588 return result;
591 };
589 };
592
590
593 /**
591 /**
594 * Handle when a a cell blurs and the notebook should enter command mode.
592 * Handle when a a cell blurs and the notebook should enter command mode.
595 *
593 *
596 * @method handle_command_mode
594 * @method handle_command_mode
597 * @param [cell] {Cell} Cell to enter command mode on.
595 * @param [cell] {Cell} Cell to enter command mode on.
598 **/
596 **/
599 Notebook.prototype.handle_command_mode = function (cell) {
597 Notebook.prototype.handle_command_mode = function (cell) {
600 if (this.mode !== 'command') {
598 if (this.mode !== 'command') {
601 cell.command_mode();
599 cell.command_mode();
602 this.mode = 'command';
600 this.mode = 'command';
603 this.events.trigger('command_mode.Notebook');
601 this.events.trigger('command_mode.Notebook');
604 this.keyboard_manager.command_mode();
602 this.keyboard_manager.command_mode();
605 }
603 }
606 };
604 };
607
605
608 /**
606 /**
609 * Make the notebook enter command mode.
607 * Make the notebook enter command mode.
610 *
608 *
611 * @method command_mode
609 * @method command_mode
612 **/
610 **/
613 Notebook.prototype.command_mode = function () {
611 Notebook.prototype.command_mode = function () {
614 var cell = this.get_cell(this.get_edit_index());
612 var cell = this.get_cell(this.get_edit_index());
615 if (cell && this.mode !== 'command') {
613 if (cell && this.mode !== 'command') {
616 // We don't call cell.command_mode, but rather call cell.focus_cell()
614 // We don't call cell.command_mode, but rather call cell.focus_cell()
617 // which will blur and CM editor and trigger the call to
615 // which will blur and CM editor and trigger the call to
618 // handle_command_mode.
616 // handle_command_mode.
619 cell.focus_cell();
617 cell.focus_cell();
620 }
618 }
621 };
619 };
622
620
623 /**
621 /**
624 * Handle when a cell fires it's edit_mode event.
622 * Handle when a cell fires it's edit_mode event.
625 *
623 *
626 * @method handle_edit_mode
624 * @method handle_edit_mode
627 * @param [cell] {Cell} Cell to enter edit mode on.
625 * @param [cell] {Cell} Cell to enter edit mode on.
628 **/
626 **/
629 Notebook.prototype.handle_edit_mode = function (cell) {
627 Notebook.prototype.handle_edit_mode = function (cell) {
630 if (cell && this.mode !== 'edit') {
628 if (cell && this.mode !== 'edit') {
631 cell.edit_mode();
629 cell.edit_mode();
632 this.mode = 'edit';
630 this.mode = 'edit';
633 this.events.trigger('edit_mode.Notebook');
631 this.events.trigger('edit_mode.Notebook');
634 this.keyboard_manager.edit_mode();
632 this.keyboard_manager.edit_mode();
635 }
633 }
636 };
634 };
637
635
638 /**
636 /**
639 * Make a cell enter edit mode.
637 * Make a cell enter edit mode.
640 *
638 *
641 * @method edit_mode
639 * @method edit_mode
642 **/
640 **/
643 Notebook.prototype.edit_mode = function () {
641 Notebook.prototype.edit_mode = function () {
644 var cell = this.get_selected_cell();
642 var cell = this.get_selected_cell();
645 if (cell && this.mode !== 'edit') {
643 if (cell && this.mode !== 'edit') {
646 cell.unrender();
644 cell.unrender();
647 cell.focus_editor();
645 cell.focus_editor();
648 }
646 }
649 };
647 };
650
648
651 /**
649 /**
652 * Focus the currently selected cell.
650 * Focus the currently selected cell.
653 *
651 *
654 * @method focus_cell
652 * @method focus_cell
655 **/
653 **/
656 Notebook.prototype.focus_cell = function () {
654 Notebook.prototype.focus_cell = function () {
657 var cell = this.get_selected_cell();
655 var cell = this.get_selected_cell();
658 if (cell === null) {return;} // No cell is selected
656 if (cell === null) {return;} // No cell is selected
659 cell.focus_cell();
657 cell.focus_cell();
660 };
658 };
661
659
662 // Cell movement
660 // Cell movement
663
661
664 /**
662 /**
665 * Move given (or selected) cell up and select it.
663 * Move given (or selected) cell up and select it.
666 *
664 *
667 * @method move_cell_up
665 * @method move_cell_up
668 * @param [index] {integer} cell index
666 * @param [index] {integer} cell index
669 * @return {Notebook} This notebook
667 * @return {Notebook} This notebook
670 **/
668 **/
671 Notebook.prototype.move_cell_up = function (index) {
669 Notebook.prototype.move_cell_up = function (index) {
672 var i = this.index_or_selected(index);
670 var i = this.index_or_selected(index);
673 if (this.is_valid_cell_index(i) && i > 0) {
671 if (this.is_valid_cell_index(i) && i > 0) {
674 var pivot = this.get_cell_element(i-1);
672 var pivot = this.get_cell_element(i-1);
675 var tomove = this.get_cell_element(i);
673 var tomove = this.get_cell_element(i);
676 if (pivot !== null && tomove !== null) {
674 if (pivot !== null && tomove !== null) {
677 tomove.detach();
675 tomove.detach();
678 pivot.before(tomove);
676 pivot.before(tomove);
679 this.select(i-1);
677 this.select(i-1);
680 var cell = this.get_selected_cell();
678 var cell = this.get_selected_cell();
681 cell.focus_cell();
679 cell.focus_cell();
682 }
680 }
683 this.set_dirty(true);
681 this.set_dirty(true);
684 }
682 }
685 return this;
683 return this;
686 };
684 };
687
685
688
686
689 /**
687 /**
690 * Move given (or selected) cell down and select it
688 * Move given (or selected) cell down and select it
691 *
689 *
692 * @method move_cell_down
690 * @method move_cell_down
693 * @param [index] {integer} cell index
691 * @param [index] {integer} cell index
694 * @return {Notebook} This notebook
692 * @return {Notebook} This notebook
695 **/
693 **/
696 Notebook.prototype.move_cell_down = function (index) {
694 Notebook.prototype.move_cell_down = function (index) {
697 var i = this.index_or_selected(index);
695 var i = this.index_or_selected(index);
698 if (this.is_valid_cell_index(i) && this.is_valid_cell_index(i+1)) {
696 if (this.is_valid_cell_index(i) && this.is_valid_cell_index(i+1)) {
699 var pivot = this.get_cell_element(i+1);
697 var pivot = this.get_cell_element(i+1);
700 var tomove = this.get_cell_element(i);
698 var tomove = this.get_cell_element(i);
701 if (pivot !== null && tomove !== null) {
699 if (pivot !== null && tomove !== null) {
702 tomove.detach();
700 tomove.detach();
703 pivot.after(tomove);
701 pivot.after(tomove);
704 this.select(i+1);
702 this.select(i+1);
705 var cell = this.get_selected_cell();
703 var cell = this.get_selected_cell();
706 cell.focus_cell();
704 cell.focus_cell();
707 }
705 }
708 }
706 }
709 this.set_dirty();
707 this.set_dirty();
710 return this;
708 return this;
711 };
709 };
712
710
713
711
714 // Insertion, deletion.
712 // Insertion, deletion.
715
713
716 /**
714 /**
717 * Delete a cell from the notebook.
715 * Delete a cell from the notebook.
718 *
716 *
719 * @method delete_cell
717 * @method delete_cell
720 * @param [index] A cell's numeric index
718 * @param [index] A cell's numeric index
721 * @return {Notebook} This notebook
719 * @return {Notebook} This notebook
722 */
720 */
723 Notebook.prototype.delete_cell = function (index) {
721 Notebook.prototype.delete_cell = function (index) {
724 var i = this.index_or_selected(index);
722 var i = this.index_or_selected(index);
725 var cell = this.get_selected_cell();
723 var cell = this.get_selected_cell();
726 this.undelete_backup = cell.toJSON();
724 this.undelete_backup = cell.toJSON();
727 $('#undelete_cell').removeClass('disabled');
725 $('#undelete_cell').removeClass('disabled');
728 if (this.is_valid_cell_index(i)) {
726 if (this.is_valid_cell_index(i)) {
729 var old_ncells = this.ncells();
727 var old_ncells = this.ncells();
730 var ce = this.get_cell_element(i);
728 var ce = this.get_cell_element(i);
731 ce.remove();
729 ce.remove();
732 if (i === 0) {
730 if (i === 0) {
733 // Always make sure we have at least one cell.
731 // Always make sure we have at least one cell.
734 if (old_ncells === 1) {
732 if (old_ncells === 1) {
735 this.insert_cell_below('code');
733 this.insert_cell_below('code');
736 }
734 }
737 this.select(0);
735 this.select(0);
738 this.undelete_index = 0;
736 this.undelete_index = 0;
739 this.undelete_below = false;
737 this.undelete_below = false;
740 } else if (i === old_ncells-1 && i !== 0) {
738 } else if (i === old_ncells-1 && i !== 0) {
741 this.select(i-1);
739 this.select(i-1);
742 this.undelete_index = i - 1;
740 this.undelete_index = i - 1;
743 this.undelete_below = true;
741 this.undelete_below = true;
744 } else {
742 } else {
745 this.select(i);
743 this.select(i);
746 this.undelete_index = i;
744 this.undelete_index = i;
747 this.undelete_below = false;
745 this.undelete_below = false;
748 }
746 }
749 this.events.trigger('delete.Cell', {'cell': cell, 'index': i});
747 this.events.trigger('delete.Cell', {'cell': cell, 'index': i});
750 this.set_dirty(true);
748 this.set_dirty(true);
751 }
749 }
752 return this;
750 return this;
753 };
751 };
754
752
755 /**
753 /**
756 * Restore the most recently deleted cell.
754 * Restore the most recently deleted cell.
757 *
755 *
758 * @method undelete
756 * @method undelete
759 */
757 */
760 Notebook.prototype.undelete_cell = function() {
758 Notebook.prototype.undelete_cell = function() {
761 if (this.undelete_backup !== null && this.undelete_index !== null) {
759 if (this.undelete_backup !== null && this.undelete_index !== null) {
762 var current_index = this.get_selected_index();
760 var current_index = this.get_selected_index();
763 if (this.undelete_index < current_index) {
761 if (this.undelete_index < current_index) {
764 current_index = current_index + 1;
762 current_index = current_index + 1;
765 }
763 }
766 if (this.undelete_index >= this.ncells()) {
764 if (this.undelete_index >= this.ncells()) {
767 this.select(this.ncells() - 1);
765 this.select(this.ncells() - 1);
768 }
766 }
769 else {
767 else {
770 this.select(this.undelete_index);
768 this.select(this.undelete_index);
771 }
769 }
772 var cell_data = this.undelete_backup;
770 var cell_data = this.undelete_backup;
773 var new_cell = null;
771 var new_cell = null;
774 if (this.undelete_below) {
772 if (this.undelete_below) {
775 new_cell = this.insert_cell_below(cell_data.cell_type);
773 new_cell = this.insert_cell_below(cell_data.cell_type);
776 } else {
774 } else {
777 new_cell = this.insert_cell_above(cell_data.cell_type);
775 new_cell = this.insert_cell_above(cell_data.cell_type);
778 }
776 }
779 new_cell.fromJSON(cell_data);
777 new_cell.fromJSON(cell_data);
780 if (this.undelete_below) {
778 if (this.undelete_below) {
781 this.select(current_index+1);
779 this.select(current_index+1);
782 } else {
780 } else {
783 this.select(current_index);
781 this.select(current_index);
784 }
782 }
785 this.undelete_backup = null;
783 this.undelete_backup = null;
786 this.undelete_index = null;
784 this.undelete_index = null;
787 }
785 }
788 $('#undelete_cell').addClass('disabled');
786 $('#undelete_cell').addClass('disabled');
789 };
787 };
790
788
791 /**
789 /**
792 * Insert a cell so that after insertion the cell is at given index.
790 * Insert a cell so that after insertion the cell is at given index.
793 *
791 *
794 * If cell type is not provided, it will default to the type of the
792 * If cell type is not provided, it will default to the type of the
795 * currently active cell.
793 * currently active cell.
796 *
794 *
797 * Similar to insert_above, but index parameter is mandatory
795 * Similar to insert_above, but index parameter is mandatory
798 *
796 *
799 * Index will be brought back into the accessible range [0,n]
797 * Index will be brought back into the accessible range [0,n]
800 *
798 *
801 * @method insert_cell_at_index
799 * @method insert_cell_at_index
802 * @param [type] {string} in ['code','markdown','heading'], defaults to 'code'
800 * @param [type] {string} in ['code','markdown','heading'], defaults to 'code'
803 * @param [index] {int} a valid index where to insert cell
801 * @param [index] {int} a valid index where to insert cell
804 *
802 *
805 * @return cell {cell|null} created cell or null
803 * @return cell {cell|null} created cell or null
806 **/
804 **/
807 Notebook.prototype.insert_cell_at_index = function(type, index){
805 Notebook.prototype.insert_cell_at_index = function(type, index){
808
806
809 var ncells = this.ncells();
807 var ncells = this.ncells();
810 index = Math.min(index,ncells);
808 index = Math.min(index,ncells);
811 index = Math.max(index,0);
809 index = Math.max(index,0);
812 var cell = null;
810 var cell = null;
813 type = type || this.get_selected_cell().cell_type;
811 type = type || this.get_selected_cell().cell_type;
814
812
815 if (ncells === 0 || this.is_valid_cell_index(index) || index === ncells) {
813 if (ncells === 0 || this.is_valid_cell_index(index) || index === ncells) {
816 var cell_options = {
814 var cell_options = {
817 events: this.events,
815 events: this.events,
818 config: this.config,
816 config: this.config,
819 keyboard_manager: this.keyboard_manager,
817 keyboard_manager: this.keyboard_manager,
820 notebook: this
818 notebook: this
821 };
819 };
822 if (type === 'code') {
820 if (type === 'code') {
823 cell = new codecell.CodeCell(this.kernel, cell_options);
821 cell = new codecell.CodeCell(this.kernel, cell_options);
824 cell.set_input_prompt();
822 cell.set_input_prompt();
825 } else if (type === 'markdown') {
823 } else if (type === 'markdown') {
826 cell = new textcell.MarkdownCell(cell_options);
824 cell = new textcell.MarkdownCell(cell_options);
827 } else if (type === 'raw') {
825 } else if (type === 'raw') {
828 cell = new textcell.RawCell(cell_options);
826 cell = new textcell.RawCell(cell_options);
829 } else if (type === 'heading') {
827 } else if (type === 'heading') {
830 cell = new textcell.HeadingCell(cell_options);
828 cell = new textcell.HeadingCell(cell_options);
831 }
829 }
832
830
833 if(this._insert_element_at_index(cell.element,index)) {
831 if(this._insert_element_at_index(cell.element,index)) {
834 cell.render();
832 cell.render();
835 this.events.trigger('create.Cell', {'cell': cell, 'index': index});
833 this.events.trigger('create.Cell', {'cell': cell, 'index': index});
836 cell.refresh();
834 cell.refresh();
837 // We used to select the cell after we refresh it, but there
835 // We used to select the cell after we refresh it, but there
838 // are now cases were this method is called where select is
836 // are now cases were this method is called where select is
839 // not appropriate. The selection logic should be handled by the
837 // not appropriate. The selection logic should be handled by the
840 // caller of the the top level insert_cell methods.
838 // caller of the the top level insert_cell methods.
841 this.set_dirty(true);
839 this.set_dirty(true);
842 }
840 }
843 }
841 }
844 return cell;
842 return cell;
845
843
846 };
844 };
847
845
848 /**
846 /**
849 * Insert an element at given cell index.
847 * Insert an element at given cell index.
850 *
848 *
851 * @method _insert_element_at_index
849 * @method _insert_element_at_index
852 * @param element {dom element} a cell element
850 * @param element {dom element} a cell element
853 * @param [index] {int} a valid index where to inser cell
851 * @param [index] {int} a valid index where to inser cell
854 * @private
852 * @private
855 *
853 *
856 * return true if everything whent fine.
854 * return true if everything whent fine.
857 **/
855 **/
858 Notebook.prototype._insert_element_at_index = function(element, index){
856 Notebook.prototype._insert_element_at_index = function(element, index){
859 if (element === undefined){
857 if (element === undefined){
860 return false;
858 return false;
861 }
859 }
862
860
863 var ncells = this.ncells();
861 var ncells = this.ncells();
864
862
865 if (ncells === 0) {
863 if (ncells === 0) {
866 // special case append if empty
864 // special case append if empty
867 this.element.find('div.end_space').before(element);
865 this.element.find('div.end_space').before(element);
868 } else if ( ncells === index ) {
866 } else if ( ncells === index ) {
869 // special case append it the end, but not empty
867 // special case append it the end, but not empty
870 this.get_cell_element(index-1).after(element);
868 this.get_cell_element(index-1).after(element);
871 } else if (this.is_valid_cell_index(index)) {
869 } else if (this.is_valid_cell_index(index)) {
872 // otherwise always somewhere to append to
870 // otherwise always somewhere to append to
873 this.get_cell_element(index).before(element);
871 this.get_cell_element(index).before(element);
874 } else {
872 } else {
875 return false;
873 return false;
876 }
874 }
877
875
878 if (this.undelete_index !== null && index <= this.undelete_index) {
876 if (this.undelete_index !== null && index <= this.undelete_index) {
879 this.undelete_index = this.undelete_index + 1;
877 this.undelete_index = this.undelete_index + 1;
880 this.set_dirty(true);
878 this.set_dirty(true);
881 }
879 }
882 return true;
880 return true;
883 };
881 };
884
882
885 /**
883 /**
886 * Insert a cell of given type above given index, or at top
884 * Insert a cell of given type above given index, or at top
887 * of notebook if index smaller than 0.
885 * of notebook if index smaller than 0.
888 *
886 *
889 * default index value is the one of currently selected cell
887 * default index value is the one of currently selected cell
890 *
888 *
891 * @method insert_cell_above
889 * @method insert_cell_above
892 * @param [type] {string} cell type
890 * @param [type] {string} cell type
893 * @param [index] {integer}
891 * @param [index] {integer}
894 *
892 *
895 * @return handle to created cell or null
893 * @return handle to created cell or null
896 **/
894 **/
897 Notebook.prototype.insert_cell_above = function (type, index) {
895 Notebook.prototype.insert_cell_above = function (type, index) {
898 index = this.index_or_selected(index);
896 index = this.index_or_selected(index);
899 return this.insert_cell_at_index(type, index);
897 return this.insert_cell_at_index(type, index);
900 };
898 };
901
899
902 /**
900 /**
903 * Insert a cell of given type below given index, or at bottom
901 * Insert a cell of given type below given index, or at bottom
904 * of notebook if index greater than number of cells
902 * of notebook if index greater than number of cells
905 *
903 *
906 * default index value is the one of currently selected cell
904 * default index value is the one of currently selected cell
907 *
905 *
908 * @method insert_cell_below
906 * @method insert_cell_below
909 * @param [type] {string} cell type
907 * @param [type] {string} cell type
910 * @param [index] {integer}
908 * @param [index] {integer}
911 *
909 *
912 * @return handle to created cell or null
910 * @return handle to created cell or null
913 *
911 *
914 **/
912 **/
915 Notebook.prototype.insert_cell_below = function (type, index) {
913 Notebook.prototype.insert_cell_below = function (type, index) {
916 index = this.index_or_selected(index);
914 index = this.index_or_selected(index);
917 return this.insert_cell_at_index(type, index+1);
915 return this.insert_cell_at_index(type, index+1);
918 };
916 };
919
917
920
918
921 /**
919 /**
922 * Insert cell at end of notebook
920 * Insert cell at end of notebook
923 *
921 *
924 * @method insert_cell_at_bottom
922 * @method insert_cell_at_bottom
925 * @param {String} type cell type
923 * @param {String} type cell type
926 *
924 *
927 * @return the added cell; or null
925 * @return the added cell; or null
928 **/
926 **/
929 Notebook.prototype.insert_cell_at_bottom = function (type){
927 Notebook.prototype.insert_cell_at_bottom = function (type){
930 var len = this.ncells();
928 var len = this.ncells();
931 return this.insert_cell_below(type,len-1);
929 return this.insert_cell_below(type,len-1);
932 };
930 };
933
931
934 /**
932 /**
935 * Turn a cell into a code cell.
933 * Turn a cell into a code cell.
936 *
934 *
937 * @method to_code
935 * @method to_code
938 * @param {Number} [index] A cell's index
936 * @param {Number} [index] A cell's index
939 */
937 */
940 Notebook.prototype.to_code = function (index) {
938 Notebook.prototype.to_code = function (index) {
941 var i = this.index_or_selected(index);
939 var i = this.index_or_selected(index);
942 if (this.is_valid_cell_index(i)) {
940 if (this.is_valid_cell_index(i)) {
943 var source_element = this.get_cell_element(i);
941 var source_element = this.get_cell_element(i);
944 var source_cell = source_element.data("cell");
942 var source_cell = source_element.data("cell");
945 if (!(source_cell instanceof codecell.CodeCell)) {
943 if (!(source_cell instanceof codecell.CodeCell)) {
946 var target_cell = this.insert_cell_below('code',i);
944 var target_cell = this.insert_cell_below('code',i);
947 var text = source_cell.get_text();
945 var text = source_cell.get_text();
948 if (text === source_cell.placeholder) {
946 if (text === source_cell.placeholder) {
949 text = '';
947 text = '';
950 }
948 }
951 target_cell.set_text(text);
949 target_cell.set_text(text);
952 // make this value the starting point, so that we can only undo
950 // make this value the starting point, so that we can only undo
953 // to this state, instead of a blank cell
951 // to this state, instead of a blank cell
954 target_cell.code_mirror.clearHistory();
952 target_cell.code_mirror.clearHistory();
955 source_element.remove();
953 source_element.remove();
956 this.select(i);
954 this.select(i);
957 var cursor = source_cell.code_mirror.getCursor();
955 var cursor = source_cell.code_mirror.getCursor();
958 target_cell.code_mirror.setCursor(cursor);
956 target_cell.code_mirror.setCursor(cursor);
959 this.set_dirty(true);
957 this.set_dirty(true);
960 }
958 }
961 }
959 }
962 };
960 };
963
961
964 /**
962 /**
965 * Turn a cell into a Markdown cell.
963 * Turn a cell into a Markdown cell.
966 *
964 *
967 * @method to_markdown
965 * @method to_markdown
968 * @param {Number} [index] A cell's index
966 * @param {Number} [index] A cell's index
969 */
967 */
970 Notebook.prototype.to_markdown = function (index) {
968 Notebook.prototype.to_markdown = function (index) {
971 var i = this.index_or_selected(index);
969 var i = this.index_or_selected(index);
972 if (this.is_valid_cell_index(i)) {
970 if (this.is_valid_cell_index(i)) {
973 var source_element = this.get_cell_element(i);
971 var source_element = this.get_cell_element(i);
974 var source_cell = source_element.data("cell");
972 var source_cell = source_element.data("cell");
975 if (!(source_cell instanceof textcell.MarkdownCell)) {
973 if (!(source_cell instanceof textcell.MarkdownCell)) {
976 var target_cell = this.insert_cell_below('markdown',i);
974 var target_cell = this.insert_cell_below('markdown',i);
977 var text = source_cell.get_text();
975 var text = source_cell.get_text();
978 if (text === source_cell.placeholder) {
976 if (text === source_cell.placeholder) {
979 text = '';
977 text = '';
980 }
978 }
981 // We must show the editor before setting its contents
979 // We must show the editor before setting its contents
982 target_cell.unrender();
980 target_cell.unrender();
983 target_cell.set_text(text);
981 target_cell.set_text(text);
984 // make this value the starting point, so that we can only undo
982 // make this value the starting point, so that we can only undo
985 // to this state, instead of a blank cell
983 // to this state, instead of a blank cell
986 target_cell.code_mirror.clearHistory();
984 target_cell.code_mirror.clearHistory();
987 source_element.remove();
985 source_element.remove();
988 this.select(i);
986 this.select(i);
989 if ((source_cell instanceof textcell.TextCell) && source_cell.rendered) {
987 if ((source_cell instanceof textcell.TextCell) && source_cell.rendered) {
990 target_cell.render();
988 target_cell.render();
991 }
989 }
992 var cursor = source_cell.code_mirror.getCursor();
990 var cursor = source_cell.code_mirror.getCursor();
993 target_cell.code_mirror.setCursor(cursor);
991 target_cell.code_mirror.setCursor(cursor);
994 this.set_dirty(true);
992 this.set_dirty(true);
995 }
993 }
996 }
994 }
997 };
995 };
998
996
999 /**
997 /**
1000 * Turn a cell into a raw text cell.
998 * Turn a cell into a raw text cell.
1001 *
999 *
1002 * @method to_raw
1000 * @method to_raw
1003 * @param {Number} [index] A cell's index
1001 * @param {Number} [index] A cell's index
1004 */
1002 */
1005 Notebook.prototype.to_raw = function (index) {
1003 Notebook.prototype.to_raw = function (index) {
1006 var i = this.index_or_selected(index);
1004 var i = this.index_or_selected(index);
1007 if (this.is_valid_cell_index(i)) {
1005 if (this.is_valid_cell_index(i)) {
1008 var source_element = this.get_cell_element(i);
1006 var source_element = this.get_cell_element(i);
1009 var source_cell = source_element.data("cell");
1007 var source_cell = source_element.data("cell");
1010 var target_cell = null;
1008 var target_cell = null;
1011 if (!(source_cell instanceof textcell.RawCell)) {
1009 if (!(source_cell instanceof textcell.RawCell)) {
1012 target_cell = this.insert_cell_below('raw',i);
1010 target_cell = this.insert_cell_below('raw',i);
1013 var text = source_cell.get_text();
1011 var text = source_cell.get_text();
1014 if (text === source_cell.placeholder) {
1012 if (text === source_cell.placeholder) {
1015 text = '';
1013 text = '';
1016 }
1014 }
1017 // We must show the editor before setting its contents
1015 // We must show the editor before setting its contents
1018 target_cell.unrender();
1016 target_cell.unrender();
1019 target_cell.set_text(text);
1017 target_cell.set_text(text);
1020 // make this value the starting point, so that we can only undo
1018 // make this value the starting point, so that we can only undo
1021 // to this state, instead of a blank cell
1019 // to this state, instead of a blank cell
1022 target_cell.code_mirror.clearHistory();
1020 target_cell.code_mirror.clearHistory();
1023 source_element.remove();
1021 source_element.remove();
1024 this.select(i);
1022 this.select(i);
1025 var cursor = source_cell.code_mirror.getCursor();
1023 var cursor = source_cell.code_mirror.getCursor();
1026 target_cell.code_mirror.setCursor(cursor);
1024 target_cell.code_mirror.setCursor(cursor);
1027 this.set_dirty(true);
1025 this.set_dirty(true);
1028 }
1026 }
1029 }
1027 }
1030 };
1028 };
1031
1029
1032 /**
1030 /**
1033 * Turn a cell into a heading cell.
1031 * Turn a cell into a heading cell.
1034 *
1032 *
1035 * @method to_heading
1033 * @method to_heading
1036 * @param {Number} [index] A cell's index
1034 * @param {Number} [index] A cell's index
1037 * @param {Number} [level] A heading level (e.g., 1 becomes &lt;h1&gt;)
1035 * @param {Number} [level] A heading level (e.g., 1 becomes &lt;h1&gt;)
1038 */
1036 */
1039 Notebook.prototype.to_heading = function (index, level) {
1037 Notebook.prototype.to_heading = function (index, level) {
1040 level = level || 1;
1038 level = level || 1;
1041 var i = this.index_or_selected(index);
1039 var i = this.index_or_selected(index);
1042 if (this.is_valid_cell_index(i)) {
1040 if (this.is_valid_cell_index(i)) {
1043 var source_element = this.get_cell_element(i);
1041 var source_element = this.get_cell_element(i);
1044 var source_cell = source_element.data("cell");
1042 var source_cell = source_element.data("cell");
1045 var target_cell = null;
1043 var target_cell = null;
1046 if (source_cell instanceof textcell.HeadingCell) {
1044 if (source_cell instanceof textcell.HeadingCell) {
1047 source_cell.set_level(level);
1045 source_cell.set_level(level);
1048 } else {
1046 } else {
1049 target_cell = this.insert_cell_below('heading',i);
1047 target_cell = this.insert_cell_below('heading',i);
1050 var text = source_cell.get_text();
1048 var text = source_cell.get_text();
1051 if (text === source_cell.placeholder) {
1049 if (text === source_cell.placeholder) {
1052 text = '';
1050 text = '';
1053 }
1051 }
1054 // We must show the editor before setting its contents
1052 // We must show the editor before setting its contents
1055 target_cell.set_level(level);
1053 target_cell.set_level(level);
1056 target_cell.unrender();
1054 target_cell.unrender();
1057 target_cell.set_text(text);
1055 target_cell.set_text(text);
1058 // make this value the starting point, so that we can only undo
1056 // make this value the starting point, so that we can only undo
1059 // to this state, instead of a blank cell
1057 // to this state, instead of a blank cell
1060 target_cell.code_mirror.clearHistory();
1058 target_cell.code_mirror.clearHistory();
1061 source_element.remove();
1059 source_element.remove();
1062 this.select(i);
1060 this.select(i);
1063 var cursor = source_cell.code_mirror.getCursor();
1061 var cursor = source_cell.code_mirror.getCursor();
1064 target_cell.code_mirror.setCursor(cursor);
1062 target_cell.code_mirror.setCursor(cursor);
1065 if ((source_cell instanceof textcell.TextCell) && source_cell.rendered) {
1063 if ((source_cell instanceof textcell.TextCell) && source_cell.rendered) {
1066 target_cell.render();
1064 target_cell.render();
1067 }
1065 }
1068 }
1066 }
1069 this.set_dirty(true);
1067 this.set_dirty(true);
1070 this.events.trigger('selected_cell_type_changed.Notebook',
1068 this.events.trigger('selected_cell_type_changed.Notebook',
1071 {'cell_type':'heading',level:level}
1069 {'cell_type':'heading',level:level}
1072 );
1070 );
1073 }
1071 }
1074 };
1072 };
1075
1073
1076
1074
1077 // Cut/Copy/Paste
1075 // Cut/Copy/Paste
1078
1076
1079 /**
1077 /**
1080 * Enable UI elements for pasting cells.
1078 * Enable UI elements for pasting cells.
1081 *
1079 *
1082 * @method enable_paste
1080 * @method enable_paste
1083 */
1081 */
1084 Notebook.prototype.enable_paste = function () {
1082 Notebook.prototype.enable_paste = function () {
1085 var that = this;
1083 var that = this;
1086 if (!this.paste_enabled) {
1084 if (!this.paste_enabled) {
1087 $('#paste_cell_replace').removeClass('disabled')
1085 $('#paste_cell_replace').removeClass('disabled')
1088 .on('click', function () {that.paste_cell_replace();});
1086 .on('click', function () {that.paste_cell_replace();});
1089 $('#paste_cell_above').removeClass('disabled')
1087 $('#paste_cell_above').removeClass('disabled')
1090 .on('click', function () {that.paste_cell_above();});
1088 .on('click', function () {that.paste_cell_above();});
1091 $('#paste_cell_below').removeClass('disabled')
1089 $('#paste_cell_below').removeClass('disabled')
1092 .on('click', function () {that.paste_cell_below();});
1090 .on('click', function () {that.paste_cell_below();});
1093 this.paste_enabled = true;
1091 this.paste_enabled = true;
1094 }
1092 }
1095 };
1093 };
1096
1094
1097 /**
1095 /**
1098 * Disable UI elements for pasting cells.
1096 * Disable UI elements for pasting cells.
1099 *
1097 *
1100 * @method disable_paste
1098 * @method disable_paste
1101 */
1099 */
1102 Notebook.prototype.disable_paste = function () {
1100 Notebook.prototype.disable_paste = function () {
1103 if (this.paste_enabled) {
1101 if (this.paste_enabled) {
1104 $('#paste_cell_replace').addClass('disabled').off('click');
1102 $('#paste_cell_replace').addClass('disabled').off('click');
1105 $('#paste_cell_above').addClass('disabled').off('click');
1103 $('#paste_cell_above').addClass('disabled').off('click');
1106 $('#paste_cell_below').addClass('disabled').off('click');
1104 $('#paste_cell_below').addClass('disabled').off('click');
1107 this.paste_enabled = false;
1105 this.paste_enabled = false;
1108 }
1106 }
1109 };
1107 };
1110
1108
1111 /**
1109 /**
1112 * Cut a cell.
1110 * Cut a cell.
1113 *
1111 *
1114 * @method cut_cell
1112 * @method cut_cell
1115 */
1113 */
1116 Notebook.prototype.cut_cell = function () {
1114 Notebook.prototype.cut_cell = function () {
1117 this.copy_cell();
1115 this.copy_cell();
1118 this.delete_cell();
1116 this.delete_cell();
1119 };
1117 };
1120
1118
1121 /**
1119 /**
1122 * Copy a cell.
1120 * Copy a cell.
1123 *
1121 *
1124 * @method copy_cell
1122 * @method copy_cell
1125 */
1123 */
1126 Notebook.prototype.copy_cell = function () {
1124 Notebook.prototype.copy_cell = function () {
1127 var cell = this.get_selected_cell();
1125 var cell = this.get_selected_cell();
1128 this.clipboard = cell.toJSON();
1126 this.clipboard = cell.toJSON();
1129 this.enable_paste();
1127 this.enable_paste();
1130 };
1128 };
1131
1129
1132 /**
1130 /**
1133 * Replace the selected cell with a cell in the clipboard.
1131 * Replace the selected cell with a cell in the clipboard.
1134 *
1132 *
1135 * @method paste_cell_replace
1133 * @method paste_cell_replace
1136 */
1134 */
1137 Notebook.prototype.paste_cell_replace = function () {
1135 Notebook.prototype.paste_cell_replace = function () {
1138 if (this.clipboard !== null && this.paste_enabled) {
1136 if (this.clipboard !== null && this.paste_enabled) {
1139 var cell_data = this.clipboard;
1137 var cell_data = this.clipboard;
1140 var new_cell = this.insert_cell_above(cell_data.cell_type);
1138 var new_cell = this.insert_cell_above(cell_data.cell_type);
1141 new_cell.fromJSON(cell_data);
1139 new_cell.fromJSON(cell_data);
1142 var old_cell = this.get_next_cell(new_cell);
1140 var old_cell = this.get_next_cell(new_cell);
1143 this.delete_cell(this.find_cell_index(old_cell));
1141 this.delete_cell(this.find_cell_index(old_cell));
1144 this.select(this.find_cell_index(new_cell));
1142 this.select(this.find_cell_index(new_cell));
1145 }
1143 }
1146 };
1144 };
1147
1145
1148 /**
1146 /**
1149 * Paste a cell from the clipboard above the selected cell.
1147 * Paste a cell from the clipboard above the selected cell.
1150 *
1148 *
1151 * @method paste_cell_above
1149 * @method paste_cell_above
1152 */
1150 */
1153 Notebook.prototype.paste_cell_above = function () {
1151 Notebook.prototype.paste_cell_above = function () {
1154 if (this.clipboard !== null && this.paste_enabled) {
1152 if (this.clipboard !== null && this.paste_enabled) {
1155 var cell_data = this.clipboard;
1153 var cell_data = this.clipboard;
1156 var new_cell = this.insert_cell_above(cell_data.cell_type);
1154 var new_cell = this.insert_cell_above(cell_data.cell_type);
1157 new_cell.fromJSON(cell_data);
1155 new_cell.fromJSON(cell_data);
1158 new_cell.focus_cell();
1156 new_cell.focus_cell();
1159 }
1157 }
1160 };
1158 };
1161
1159
1162 /**
1160 /**
1163 * Paste a cell from the clipboard below the selected cell.
1161 * Paste a cell from the clipboard below the selected cell.
1164 *
1162 *
1165 * @method paste_cell_below
1163 * @method paste_cell_below
1166 */
1164 */
1167 Notebook.prototype.paste_cell_below = function () {
1165 Notebook.prototype.paste_cell_below = function () {
1168 if (this.clipboard !== null && this.paste_enabled) {
1166 if (this.clipboard !== null && this.paste_enabled) {
1169 var cell_data = this.clipboard;
1167 var cell_data = this.clipboard;
1170 var new_cell = this.insert_cell_below(cell_data.cell_type);
1168 var new_cell = this.insert_cell_below(cell_data.cell_type);
1171 new_cell.fromJSON(cell_data);
1169 new_cell.fromJSON(cell_data);
1172 new_cell.focus_cell();
1170 new_cell.focus_cell();
1173 }
1171 }
1174 };
1172 };
1175
1173
1176 // Split/merge
1174 // Split/merge
1177
1175
1178 /**
1176 /**
1179 * Split the selected cell into two, at the cursor.
1177 * Split the selected cell into two, at the cursor.
1180 *
1178 *
1181 * @method split_cell
1179 * @method split_cell
1182 */
1180 */
1183 Notebook.prototype.split_cell = function () {
1181 Notebook.prototype.split_cell = function () {
1184 var mdc = textcell.MarkdownCell;
1182 var mdc = textcell.MarkdownCell;
1185 var rc = textcell.RawCell;
1183 var rc = textcell.RawCell;
1186 var cell = this.get_selected_cell();
1184 var cell = this.get_selected_cell();
1187 if (cell.is_splittable()) {
1185 if (cell.is_splittable()) {
1188 var texta = cell.get_pre_cursor();
1186 var texta = cell.get_pre_cursor();
1189 var textb = cell.get_post_cursor();
1187 var textb = cell.get_post_cursor();
1190 if (cell instanceof codecell.CodeCell) {
1188 if (cell instanceof codecell.CodeCell) {
1191 // In this case the operations keep the notebook in its existing mode
1189 // In this case the operations keep the notebook in its existing mode
1192 // so we don't need to do any post-op mode changes.
1190 // so we don't need to do any post-op mode changes.
1193 cell.set_text(textb);
1191 cell.set_text(textb);
1194 var new_cell = this.insert_cell_above('code');
1192 var new_cell = this.insert_cell_above('code');
1195 new_cell.set_text(texta);
1193 new_cell.set_text(texta);
1196 } else if ((cell instanceof mdc && !cell.rendered) || (cell instanceof rc)) {
1194 } else if ((cell instanceof mdc && !cell.rendered) || (cell instanceof rc)) {
1197 // We know cell is !rendered so we can use set_text.
1195 // We know cell is !rendered so we can use set_text.
1198 cell.set_text(textb);
1196 cell.set_text(textb);
1199 var new_cell = this.insert_cell_above(cell.cell_type);
1197 var new_cell = this.insert_cell_above(cell.cell_type);
1200 // Unrender the new cell so we can call set_text.
1198 // Unrender the new cell so we can call set_text.
1201 new_cell.unrender();
1199 new_cell.unrender();
1202 new_cell.set_text(texta);
1200 new_cell.set_text(texta);
1203 }
1201 }
1204 }
1202 }
1205 };
1203 };
1206
1204
1207 /**
1205 /**
1208 * Combine the selected cell into the cell above it.
1206 * Combine the selected cell into the cell above it.
1209 *
1207 *
1210 * @method merge_cell_above
1208 * @method merge_cell_above
1211 */
1209 */
1212 Notebook.prototype.merge_cell_above = function () {
1210 Notebook.prototype.merge_cell_above = function () {
1213 var mdc = textcell.MarkdownCell;
1211 var mdc = textcell.MarkdownCell;
1214 var rc = textcell.RawCell;
1212 var rc = textcell.RawCell;
1215 var index = this.get_selected_index();
1213 var index = this.get_selected_index();
1216 var cell = this.get_cell(index);
1214 var cell = this.get_cell(index);
1217 var render = cell.rendered;
1215 var render = cell.rendered;
1218 if (!cell.is_mergeable()) {
1216 if (!cell.is_mergeable()) {
1219 return;
1217 return;
1220 }
1218 }
1221 if (index > 0) {
1219 if (index > 0) {
1222 var upper_cell = this.get_cell(index-1);
1220 var upper_cell = this.get_cell(index-1);
1223 if (!upper_cell.is_mergeable()) {
1221 if (!upper_cell.is_mergeable()) {
1224 return;
1222 return;
1225 }
1223 }
1226 var upper_text = upper_cell.get_text();
1224 var upper_text = upper_cell.get_text();
1227 var text = cell.get_text();
1225 var text = cell.get_text();
1228 if (cell instanceof codecell.CodeCell) {
1226 if (cell instanceof codecell.CodeCell) {
1229 cell.set_text(upper_text+'\n'+text);
1227 cell.set_text(upper_text+'\n'+text);
1230 } else if ((cell instanceof mdc) || (cell instanceof rc)) {
1228 } else if ((cell instanceof mdc) || (cell instanceof rc)) {
1231 cell.unrender(); // Must unrender before we set_text.
1229 cell.unrender(); // Must unrender before we set_text.
1232 cell.set_text(upper_text+'\n\n'+text);
1230 cell.set_text(upper_text+'\n\n'+text);
1233 if (render) {
1231 if (render) {
1234 // The rendered state of the final cell should match
1232 // The rendered state of the final cell should match
1235 // that of the original selected cell;
1233 // that of the original selected cell;
1236 cell.render();
1234 cell.render();
1237 }
1235 }
1238 }
1236 }
1239 this.delete_cell(index-1);
1237 this.delete_cell(index-1);
1240 this.select(this.find_cell_index(cell));
1238 this.select(this.find_cell_index(cell));
1241 }
1239 }
1242 };
1240 };
1243
1241
1244 /**
1242 /**
1245 * Combine the selected cell into the cell below it.
1243 * Combine the selected cell into the cell below it.
1246 *
1244 *
1247 * @method merge_cell_below
1245 * @method merge_cell_below
1248 */
1246 */
1249 Notebook.prototype.merge_cell_below = function () {
1247 Notebook.prototype.merge_cell_below = function () {
1250 var mdc = textcell.MarkdownCell;
1248 var mdc = textcell.MarkdownCell;
1251 var rc = textcell.RawCell;
1249 var rc = textcell.RawCell;
1252 var index = this.get_selected_index();
1250 var index = this.get_selected_index();
1253 var cell = this.get_cell(index);
1251 var cell = this.get_cell(index);
1254 var render = cell.rendered;
1252 var render = cell.rendered;
1255 if (!cell.is_mergeable()) {
1253 if (!cell.is_mergeable()) {
1256 return;
1254 return;
1257 }
1255 }
1258 if (index < this.ncells()-1) {
1256 if (index < this.ncells()-1) {
1259 var lower_cell = this.get_cell(index+1);
1257 var lower_cell = this.get_cell(index+1);
1260 if (!lower_cell.is_mergeable()) {
1258 if (!lower_cell.is_mergeable()) {
1261 return;
1259 return;
1262 }
1260 }
1263 var lower_text = lower_cell.get_text();
1261 var lower_text = lower_cell.get_text();
1264 var text = cell.get_text();
1262 var text = cell.get_text();
1265 if (cell instanceof codecell.CodeCell) {
1263 if (cell instanceof codecell.CodeCell) {
1266 cell.set_text(text+'\n'+lower_text);
1264 cell.set_text(text+'\n'+lower_text);
1267 } else if ((cell instanceof mdc) || (cell instanceof rc)) {
1265 } else if ((cell instanceof mdc) || (cell instanceof rc)) {
1268 cell.unrender(); // Must unrender before we set_text.
1266 cell.unrender(); // Must unrender before we set_text.
1269 cell.set_text(text+'\n\n'+lower_text);
1267 cell.set_text(text+'\n\n'+lower_text);
1270 if (render) {
1268 if (render) {
1271 // The rendered state of the final cell should match
1269 // The rendered state of the final cell should match
1272 // that of the original selected cell;
1270 // that of the original selected cell;
1273 cell.render();
1271 cell.render();
1274 }
1272 }
1275 }
1273 }
1276 this.delete_cell(index+1);
1274 this.delete_cell(index+1);
1277 this.select(this.find_cell_index(cell));
1275 this.select(this.find_cell_index(cell));
1278 }
1276 }
1279 };
1277 };
1280
1278
1281
1279
1282 // Cell collapsing and output clearing
1280 // Cell collapsing and output clearing
1283
1281
1284 /**
1282 /**
1285 * Hide a cell's output.
1283 * Hide a cell's output.
1286 *
1284 *
1287 * @method collapse_output
1285 * @method collapse_output
1288 * @param {Number} index A cell's numeric index
1286 * @param {Number} index A cell's numeric index
1289 */
1287 */
1290 Notebook.prototype.collapse_output = function (index) {
1288 Notebook.prototype.collapse_output = function (index) {
1291 var i = this.index_or_selected(index);
1289 var i = this.index_or_selected(index);
1292 var cell = this.get_cell(i);
1290 var cell = this.get_cell(i);
1293 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1291 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1294 cell.collapse_output();
1292 cell.collapse_output();
1295 this.set_dirty(true);
1293 this.set_dirty(true);
1296 }
1294 }
1297 };
1295 };
1298
1296
1299 /**
1297 /**
1300 * Hide each code cell's output area.
1298 * Hide each code cell's output area.
1301 *
1299 *
1302 * @method collapse_all_output
1300 * @method collapse_all_output
1303 */
1301 */
1304 Notebook.prototype.collapse_all_output = function () {
1302 Notebook.prototype.collapse_all_output = function () {
1305 $.map(this.get_cells(), function (cell, i) {
1303 $.map(this.get_cells(), function (cell, i) {
1306 if (cell instanceof codecell.CodeCell) {
1304 if (cell instanceof codecell.CodeCell) {
1307 cell.collapse_output();
1305 cell.collapse_output();
1308 }
1306 }
1309 });
1307 });
1310 // this should not be set if the `collapse` key is removed from nbformat
1308 // this should not be set if the `collapse` key is removed from nbformat
1311 this.set_dirty(true);
1309 this.set_dirty(true);
1312 };
1310 };
1313
1311
1314 /**
1312 /**
1315 * Show a cell's output.
1313 * Show a cell's output.
1316 *
1314 *
1317 * @method expand_output
1315 * @method expand_output
1318 * @param {Number} index A cell's numeric index
1316 * @param {Number} index A cell's numeric index
1319 */
1317 */
1320 Notebook.prototype.expand_output = function (index) {
1318 Notebook.prototype.expand_output = function (index) {
1321 var i = this.index_or_selected(index);
1319 var i = this.index_or_selected(index);
1322 var cell = this.get_cell(i);
1320 var cell = this.get_cell(i);
1323 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1321 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1324 cell.expand_output();
1322 cell.expand_output();
1325 this.set_dirty(true);
1323 this.set_dirty(true);
1326 }
1324 }
1327 };
1325 };
1328
1326
1329 /**
1327 /**
1330 * Expand each code cell's output area, and remove scrollbars.
1328 * Expand each code cell's output area, and remove scrollbars.
1331 *
1329 *
1332 * @method expand_all_output
1330 * @method expand_all_output
1333 */
1331 */
1334 Notebook.prototype.expand_all_output = function () {
1332 Notebook.prototype.expand_all_output = function () {
1335 $.map(this.get_cells(), function (cell, i) {
1333 $.map(this.get_cells(), function (cell, i) {
1336 if (cell instanceof codecell.CodeCell) {
1334 if (cell instanceof codecell.CodeCell) {
1337 cell.expand_output();
1335 cell.expand_output();
1338 }
1336 }
1339 });
1337 });
1340 // this should not be set if the `collapse` key is removed from nbformat
1338 // this should not be set if the `collapse` key is removed from nbformat
1341 this.set_dirty(true);
1339 this.set_dirty(true);
1342 };
1340 };
1343
1341
1344 /**
1342 /**
1345 * Clear the selected CodeCell's output area.
1343 * Clear the selected CodeCell's output area.
1346 *
1344 *
1347 * @method clear_output
1345 * @method clear_output
1348 * @param {Number} index A cell's numeric index
1346 * @param {Number} index A cell's numeric index
1349 */
1347 */
1350 Notebook.prototype.clear_output = function (index) {
1348 Notebook.prototype.clear_output = function (index) {
1351 var i = this.index_or_selected(index);
1349 var i = this.index_or_selected(index);
1352 var cell = this.get_cell(i);
1350 var cell = this.get_cell(i);
1353 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1351 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1354 cell.clear_output();
1352 cell.clear_output();
1355 this.set_dirty(true);
1353 this.set_dirty(true);
1356 }
1354 }
1357 };
1355 };
1358
1356
1359 /**
1357 /**
1360 * Clear each code cell's output area.
1358 * Clear each code cell's output area.
1361 *
1359 *
1362 * @method clear_all_output
1360 * @method clear_all_output
1363 */
1361 */
1364 Notebook.prototype.clear_all_output = function () {
1362 Notebook.prototype.clear_all_output = function () {
1365 $.map(this.get_cells(), function (cell, i) {
1363 $.map(this.get_cells(), function (cell, i) {
1366 if (cell instanceof codecell.CodeCell) {
1364 if (cell instanceof codecell.CodeCell) {
1367 cell.clear_output();
1365 cell.clear_output();
1368 }
1366 }
1369 });
1367 });
1370 this.set_dirty(true);
1368 this.set_dirty(true);
1371 };
1369 };
1372
1370
1373 /**
1371 /**
1374 * Scroll the selected CodeCell's output area.
1372 * Scroll the selected CodeCell's output area.
1375 *
1373 *
1376 * @method scroll_output
1374 * @method scroll_output
1377 * @param {Number} index A cell's numeric index
1375 * @param {Number} index A cell's numeric index
1378 */
1376 */
1379 Notebook.prototype.scroll_output = function (index) {
1377 Notebook.prototype.scroll_output = function (index) {
1380 var i = this.index_or_selected(index);
1378 var i = this.index_or_selected(index);
1381 var cell = this.get_cell(i);
1379 var cell = this.get_cell(i);
1382 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1380 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1383 cell.scroll_output();
1381 cell.scroll_output();
1384 this.set_dirty(true);
1382 this.set_dirty(true);
1385 }
1383 }
1386 };
1384 };
1387
1385
1388 /**
1386 /**
1389 * Expand each code cell's output area, and add a scrollbar for long output.
1387 * Expand each code cell's output area, and add a scrollbar for long output.
1390 *
1388 *
1391 * @method scroll_all_output
1389 * @method scroll_all_output
1392 */
1390 */
1393 Notebook.prototype.scroll_all_output = function () {
1391 Notebook.prototype.scroll_all_output = function () {
1394 $.map(this.get_cells(), function (cell, i) {
1392 $.map(this.get_cells(), function (cell, i) {
1395 if (cell instanceof codecell.CodeCell) {
1393 if (cell instanceof codecell.CodeCell) {
1396 cell.scroll_output();
1394 cell.scroll_output();
1397 }
1395 }
1398 });
1396 });
1399 // this should not be set if the `collapse` key is removed from nbformat
1397 // this should not be set if the `collapse` key is removed from nbformat
1400 this.set_dirty(true);
1398 this.set_dirty(true);
1401 };
1399 };
1402
1400
1403 /** Toggle whether a cell's output is collapsed or expanded.
1401 /** Toggle whether a cell's output is collapsed or expanded.
1404 *
1402 *
1405 * @method toggle_output
1403 * @method toggle_output
1406 * @param {Number} index A cell's numeric index
1404 * @param {Number} index A cell's numeric index
1407 */
1405 */
1408 Notebook.prototype.toggle_output = function (index) {
1406 Notebook.prototype.toggle_output = function (index) {
1409 var i = this.index_or_selected(index);
1407 var i = this.index_or_selected(index);
1410 var cell = this.get_cell(i);
1408 var cell = this.get_cell(i);
1411 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1409 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1412 cell.toggle_output();
1410 cell.toggle_output();
1413 this.set_dirty(true);
1411 this.set_dirty(true);
1414 }
1412 }
1415 };
1413 };
1416
1414
1417 /**
1415 /**
1418 * Hide/show the output of all cells.
1416 * Hide/show the output of all cells.
1419 *
1417 *
1420 * @method toggle_all_output
1418 * @method toggle_all_output
1421 */
1419 */
1422 Notebook.prototype.toggle_all_output = function () {
1420 Notebook.prototype.toggle_all_output = function () {
1423 $.map(this.get_cells(), function (cell, i) {
1421 $.map(this.get_cells(), function (cell, i) {
1424 if (cell instanceof codecell.CodeCell) {
1422 if (cell instanceof codecell.CodeCell) {
1425 cell.toggle_output();
1423 cell.toggle_output();
1426 }
1424 }
1427 });
1425 });
1428 // this should not be set if the `collapse` key is removed from nbformat
1426 // this should not be set if the `collapse` key is removed from nbformat
1429 this.set_dirty(true);
1427 this.set_dirty(true);
1430 };
1428 };
1431
1429
1432 /**
1430 /**
1433 * Toggle a scrollbar for long cell outputs.
1431 * Toggle a scrollbar for long cell outputs.
1434 *
1432 *
1435 * @method toggle_output_scroll
1433 * @method toggle_output_scroll
1436 * @param {Number} index A cell's numeric index
1434 * @param {Number} index A cell's numeric index
1437 */
1435 */
1438 Notebook.prototype.toggle_output_scroll = function (index) {
1436 Notebook.prototype.toggle_output_scroll = function (index) {
1439 var i = this.index_or_selected(index);
1437 var i = this.index_or_selected(index);
1440 var cell = this.get_cell(i);
1438 var cell = this.get_cell(i);
1441 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1439 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1442 cell.toggle_output_scroll();
1440 cell.toggle_output_scroll();
1443 this.set_dirty(true);
1441 this.set_dirty(true);
1444 }
1442 }
1445 };
1443 };
1446
1444
1447 /**
1445 /**
1448 * Toggle the scrolling of long output on all cells.
1446 * Toggle the scrolling of long output on all cells.
1449 *
1447 *
1450 * @method toggle_all_output_scrolling
1448 * @method toggle_all_output_scrolling
1451 */
1449 */
1452 Notebook.prototype.toggle_all_output_scroll = function () {
1450 Notebook.prototype.toggle_all_output_scroll = function () {
1453 $.map(this.get_cells(), function (cell, i) {
1451 $.map(this.get_cells(), function (cell, i) {
1454 if (cell instanceof codecell.CodeCell) {
1452 if (cell instanceof codecell.CodeCell) {
1455 cell.toggle_output_scroll();
1453 cell.toggle_output_scroll();
1456 }
1454 }
1457 });
1455 });
1458 // this should not be set if the `collapse` key is removed from nbformat
1456 // this should not be set if the `collapse` key is removed from nbformat
1459 this.set_dirty(true);
1457 this.set_dirty(true);
1460 };
1458 };
1461
1459
1462 // Other cell functions: line numbers, ...
1460 // Other cell functions: line numbers, ...
1463
1461
1464 /**
1462 /**
1465 * Toggle line numbers in the selected cell's input area.
1463 * Toggle line numbers in the selected cell's input area.
1466 *
1464 *
1467 * @method cell_toggle_line_numbers
1465 * @method cell_toggle_line_numbers
1468 */
1466 */
1469 Notebook.prototype.cell_toggle_line_numbers = function() {
1467 Notebook.prototype.cell_toggle_line_numbers = function() {
1470 this.get_selected_cell().toggle_line_numbers();
1468 this.get_selected_cell().toggle_line_numbers();
1471 };
1469 };
1472
1470
1473 // Session related things
1471 // Session related things
1474
1472
1475 /**
1473 /**
1476 * Start a new session and set it on each code cell.
1474 * Start a new session and set it on each code cell.
1477 *
1475 *
1478 * @method start_session
1476 * @method start_session
1479 */
1477 */
1480 Notebook.prototype.start_session = function () {
1478 Notebook.prototype.start_session = function () {
1481 this.session = new session.Session({
1479 this.session = new session.Session({
1482 base_url: this.base_url,
1480 base_url: this.base_url,
1483 notebook_path: this.notebook_path,
1481 notebook_path: this.notebook_path,
1484 notebook_name: this.notebook_name,
1482 notebook_name: this.notebook_name,
1485 notebook: this});
1483 notebook: this});
1486 this.session.start($.proxy(this._session_started, this));
1484 this.session.start($.proxy(this._session_started, this));
1487 };
1485 };
1488
1486
1489
1487
1490 /**
1488 /**
1491 * Once a session is started, link the code cells to the kernel and pass the
1489 * Once a session is started, link the code cells to the kernel and pass the
1492 * comm manager to the widget manager
1490 * comm manager to the widget manager
1493 *
1491 *
1494 */
1492 */
1495 Notebook.prototype._session_started = function(){
1493 Notebook.prototype._session_started = function(){
1496 this.kernel = this.session.kernel;
1494 this.kernel = this.session.kernel;
1497 var ncells = this.ncells();
1495 var ncells = this.ncells();
1498 for (var i=0; i<ncells; i++) {
1496 for (var i=0; i<ncells; i++) {
1499 var cell = this.get_cell(i);
1497 var cell = this.get_cell(i);
1500 if (cell instanceof codecell.CodeCell) {
1498 if (cell instanceof codecell.CodeCell) {
1501 cell.set_kernel(this.session.kernel);
1499 cell.set_kernel(this.session.kernel);
1502 }
1500 }
1503 }
1501 }
1504 };
1502 };
1505
1503
1506 /**
1504 /**
1507 * Prompt the user to restart the IPython kernel.
1505 * Prompt the user to restart the IPython kernel.
1508 *
1506 *
1509 * @method restart_kernel
1507 * @method restart_kernel
1510 */
1508 */
1511 Notebook.prototype.restart_kernel = function () {
1509 Notebook.prototype.restart_kernel = function () {
1512 var that = this;
1510 var that = this;
1513 dialog.modal({
1511 dialog.modal({
1514 notebook: this,
1512 notebook: this,
1515 keyboard_manager: this.keyboard_manager,
1513 keyboard_manager: this.keyboard_manager,
1516 title : "Restart kernel or continue running?",
1514 title : "Restart kernel or continue running?",
1517 body : $("<p/>").text(
1515 body : $("<p/>").text(
1518 'Do you want to restart the current kernel? You will lose all variables defined in it.'
1516 'Do you want to restart the current kernel? You will lose all variables defined in it.'
1519 ),
1517 ),
1520 buttons : {
1518 buttons : {
1521 "Continue running" : {},
1519 "Continue running" : {},
1522 "Restart" : {
1520 "Restart" : {
1523 "class" : "btn-danger",
1521 "class" : "btn-danger",
1524 "click" : function() {
1522 "click" : function() {
1525 that.session.restart_kernel();
1523 that.session.restart_kernel();
1526 }
1524 }
1527 }
1525 }
1528 }
1526 }
1529 });
1527 });
1530 };
1528 };
1531
1529
1532 /**
1530 /**
1533 * Execute or render cell outputs and go into command mode.
1531 * Execute or render cell outputs and go into command mode.
1534 *
1532 *
1535 * @method execute_cell
1533 * @method execute_cell
1536 */
1534 */
1537 Notebook.prototype.execute_cell = function () {
1535 Notebook.prototype.execute_cell = function () {
1538 // mode = shift, ctrl, alt
1536 // mode = shift, ctrl, alt
1539 var cell = this.get_selected_cell();
1537 var cell = this.get_selected_cell();
1540 var cell_index = this.find_cell_index(cell);
1538 var cell_index = this.find_cell_index(cell);
1541
1539
1542 cell.execute();
1540 cell.execute();
1543 this.command_mode();
1541 this.command_mode();
1544 this.set_dirty(true);
1542 this.set_dirty(true);
1545 };
1543 };
1546
1544
1547 /**
1545 /**
1548 * Execute or render cell outputs and insert a new cell below.
1546 * Execute or render cell outputs and insert a new cell below.
1549 *
1547 *
1550 * @method execute_cell_and_insert_below
1548 * @method execute_cell_and_insert_below
1551 */
1549 */
1552 Notebook.prototype.execute_cell_and_insert_below = function () {
1550 Notebook.prototype.execute_cell_and_insert_below = function () {
1553 var cell = this.get_selected_cell();
1551 var cell = this.get_selected_cell();
1554 var cell_index = this.find_cell_index(cell);
1552 var cell_index = this.find_cell_index(cell);
1555
1553
1556 cell.execute();
1554 cell.execute();
1557
1555
1558 // If we are at the end always insert a new cell and return
1556 // If we are at the end always insert a new cell and return
1559 if (cell_index === (this.ncells()-1)) {
1557 if (cell_index === (this.ncells()-1)) {
1560 this.command_mode();
1558 this.command_mode();
1561 this.insert_cell_below();
1559 this.insert_cell_below();
1562 this.select(cell_index+1);
1560 this.select(cell_index+1);
1563 this.edit_mode();
1561 this.edit_mode();
1564 this.scroll_to_bottom();
1562 this.scroll_to_bottom();
1565 this.set_dirty(true);
1563 this.set_dirty(true);
1566 return;
1564 return;
1567 }
1565 }
1568
1566
1569 this.command_mode();
1567 this.command_mode();
1570 this.insert_cell_below();
1568 this.insert_cell_below();
1571 this.select(cell_index+1);
1569 this.select(cell_index+1);
1572 this.edit_mode();
1570 this.edit_mode();
1573 this.set_dirty(true);
1571 this.set_dirty(true);
1574 };
1572 };
1575
1573
1576 /**
1574 /**
1577 * Execute or render cell outputs and select the next cell.
1575 * Execute or render cell outputs and select the next cell.
1578 *
1576 *
1579 * @method execute_cell_and_select_below
1577 * @method execute_cell_and_select_below
1580 */
1578 */
1581 Notebook.prototype.execute_cell_and_select_below = function () {
1579 Notebook.prototype.execute_cell_and_select_below = function () {
1582
1580
1583 var cell = this.get_selected_cell();
1581 var cell = this.get_selected_cell();
1584 var cell_index = this.find_cell_index(cell);
1582 var cell_index = this.find_cell_index(cell);
1585
1583
1586 cell.execute();
1584 cell.execute();
1587
1585
1588 // If we are at the end always insert a new cell and return
1586 // If we are at the end always insert a new cell and return
1589 if (cell_index === (this.ncells()-1)) {
1587 if (cell_index === (this.ncells()-1)) {
1590 this.command_mode();
1588 this.command_mode();
1591 this.insert_cell_below();
1589 this.insert_cell_below();
1592 this.select(cell_index+1);
1590 this.select(cell_index+1);
1593 this.edit_mode();
1591 this.edit_mode();
1594 this.scroll_to_bottom();
1592 this.scroll_to_bottom();
1595 this.set_dirty(true);
1593 this.set_dirty(true);
1596 return;
1594 return;
1597 }
1595 }
1598
1596
1599 this.command_mode();
1597 this.command_mode();
1600 this.select(cell_index+1);
1598 this.select(cell_index+1);
1601 this.focus_cell();
1599 this.focus_cell();
1602 this.set_dirty(true);
1600 this.set_dirty(true);
1603 };
1601 };
1604
1602
1605 /**
1603 /**
1606 * Execute all cells below the selected cell.
1604 * Execute all cells below the selected cell.
1607 *
1605 *
1608 * @method execute_cells_below
1606 * @method execute_cells_below
1609 */
1607 */
1610 Notebook.prototype.execute_cells_below = function () {
1608 Notebook.prototype.execute_cells_below = function () {
1611 this.execute_cell_range(this.get_selected_index(), this.ncells());
1609 this.execute_cell_range(this.get_selected_index(), this.ncells());
1612 this.scroll_to_bottom();
1610 this.scroll_to_bottom();
1613 };
1611 };
1614
1612
1615 /**
1613 /**
1616 * Execute all cells above the selected cell.
1614 * Execute all cells above the selected cell.
1617 *
1615 *
1618 * @method execute_cells_above
1616 * @method execute_cells_above
1619 */
1617 */
1620 Notebook.prototype.execute_cells_above = function () {
1618 Notebook.prototype.execute_cells_above = function () {
1621 this.execute_cell_range(0, this.get_selected_index());
1619 this.execute_cell_range(0, this.get_selected_index());
1622 };
1620 };
1623
1621
1624 /**
1622 /**
1625 * Execute all cells.
1623 * Execute all cells.
1626 *
1624 *
1627 * @method execute_all_cells
1625 * @method execute_all_cells
1628 */
1626 */
1629 Notebook.prototype.execute_all_cells = function () {
1627 Notebook.prototype.execute_all_cells = function () {
1630 this.execute_cell_range(0, this.ncells());
1628 this.execute_cell_range(0, this.ncells());
1631 this.scroll_to_bottom();
1629 this.scroll_to_bottom();
1632 };
1630 };
1633
1631
1634 /**
1632 /**
1635 * Execute a contiguous range of cells.
1633 * Execute a contiguous range of cells.
1636 *
1634 *
1637 * @method execute_cell_range
1635 * @method execute_cell_range
1638 * @param {Number} start Index of the first cell to execute (inclusive)
1636 * @param {Number} start Index of the first cell to execute (inclusive)
1639 * @param {Number} end Index of the last cell to execute (exclusive)
1637 * @param {Number} end Index of the last cell to execute (exclusive)
1640 */
1638 */
1641 Notebook.prototype.execute_cell_range = function (start, end) {
1639 Notebook.prototype.execute_cell_range = function (start, end) {
1642 this.command_mode();
1640 this.command_mode();
1643 for (var i=start; i<end; i++) {
1641 for (var i=start; i<end; i++) {
1644 this.select(i);
1642 this.select(i);
1645 this.execute_cell();
1643 this.execute_cell();
1646 }
1644 }
1647 };
1645 };
1648
1646
1649 // Persistance and loading
1647 // Persistance and loading
1650
1648
1651 /**
1649 /**
1652 * Getter method for this notebook's name.
1650 * Getter method for this notebook's name.
1653 *
1651 *
1654 * @method get_notebook_name
1652 * @method get_notebook_name
1655 * @return {String} This notebook's name (excluding file extension)
1653 * @return {String} This notebook's name (excluding file extension)
1656 */
1654 */
1657 Notebook.prototype.get_notebook_name = function () {
1655 Notebook.prototype.get_notebook_name = function () {
1658 var nbname = this.notebook_name.substring(0,this.notebook_name.length-6);
1656 var nbname = this.notebook_name.substring(0,this.notebook_name.length-6);
1659 return nbname;
1657 return nbname;
1660 };
1658 };
1661
1659
1662 /**
1660 /**
1663 * Setter method for this notebook's name.
1661 * Setter method for this notebook's name.
1664 *
1662 *
1665 * @method set_notebook_name
1663 * @method set_notebook_name
1666 * @param {String} name A new name for this notebook
1664 * @param {String} name A new name for this notebook
1667 */
1665 */
1668 Notebook.prototype.set_notebook_name = function (name) {
1666 Notebook.prototype.set_notebook_name = function (name) {
1669 this.notebook_name = name;
1667 this.notebook_name = name;
1670 };
1668 };
1671
1669
1672 /**
1670 /**
1673 * Check that a notebook's name is valid.
1671 * Check that a notebook's name is valid.
1674 *
1672 *
1675 * @method test_notebook_name
1673 * @method test_notebook_name
1676 * @param {String} nbname A name for this notebook
1674 * @param {String} nbname A name for this notebook
1677 * @return {Boolean} True if the name is valid, false if invalid
1675 * @return {Boolean} True if the name is valid, false if invalid
1678 */
1676 */
1679 Notebook.prototype.test_notebook_name = function (nbname) {
1677 Notebook.prototype.test_notebook_name = function (nbname) {
1680 nbname = nbname || '';
1678 nbname = nbname || '';
1681 if (nbname.length>0 && !this.notebook_name_blacklist_re.test(nbname)) {
1679 if (nbname.length>0 && !this.notebook_name_blacklist_re.test(nbname)) {
1682 return true;
1680 return true;
1683 } else {
1681 } else {
1684 return false;
1682 return false;
1685 }
1683 }
1686 };
1684 };
1687
1685
1688 /**
1686 /**
1689 * Load a notebook from JSON (.ipynb).
1687 * Load a notebook from JSON (.ipynb).
1690 *
1688 *
1691 * This currently handles one worksheet: others are deleted.
1689 * This currently handles one worksheet: others are deleted.
1692 *
1690 *
1693 * @method fromJSON
1691 * @method fromJSON
1694 * @param {Object} data JSON representation of a notebook
1692 * @param {Object} data JSON representation of a notebook
1695 */
1693 */
1696 Notebook.prototype.fromJSON = function (data) {
1694 Notebook.prototype.fromJSON = function (data) {
1697 var content = data.content;
1695 var content = data.content;
1698 var ncells = this.ncells();
1696 var ncells = this.ncells();
1699 var i;
1697 var i;
1700 for (i=0; i<ncells; i++) {
1698 for (i=0; i<ncells; i++) {
1701 // Always delete cell 0 as they get renumbered as they are deleted.
1699 // Always delete cell 0 as they get renumbered as they are deleted.
1702 this.delete_cell(0);
1700 this.delete_cell(0);
1703 }
1701 }
1704 // Save the metadata and name.
1702 // Save the metadata and name.
1705 this.metadata = content.metadata;
1703 this.metadata = content.metadata;
1706 this.notebook_name = data.name;
1704 this.notebook_name = data.name;
1707 var trusted = true;
1705 var trusted = true;
1708 // Only handle 1 worksheet for now.
1706 // Only handle 1 worksheet for now.
1709 var worksheet = content.worksheets[0];
1707 var worksheet = content.worksheets[0];
1710 if (worksheet !== undefined) {
1708 if (worksheet !== undefined) {
1711 if (worksheet.metadata) {
1709 if (worksheet.metadata) {
1712 this.worksheet_metadata = worksheet.metadata;
1710 this.worksheet_metadata = worksheet.metadata;
1713 }
1711 }
1714 var new_cells = worksheet.cells;
1712 var new_cells = worksheet.cells;
1715 ncells = new_cells.length;
1713 ncells = new_cells.length;
1716 var cell_data = null;
1714 var cell_data = null;
1717 var new_cell = null;
1715 var new_cell = null;
1718 for (i=0; i<ncells; i++) {
1716 for (i=0; i<ncells; i++) {
1719 cell_data = new_cells[i];
1717 cell_data = new_cells[i];
1720 // VERSIONHACK: plaintext -> raw
1718 // VERSIONHACK: plaintext -> raw
1721 // handle never-released plaintext name for raw cells
1719 // handle never-released plaintext name for raw cells
1722 if (cell_data.cell_type === 'plaintext'){
1720 if (cell_data.cell_type === 'plaintext'){
1723 cell_data.cell_type = 'raw';
1721 cell_data.cell_type = 'raw';
1724 }
1722 }
1725
1723
1726 new_cell = this.insert_cell_at_index(cell_data.cell_type, i);
1724 new_cell = this.insert_cell_at_index(cell_data.cell_type, i);
1727 new_cell.fromJSON(cell_data);
1725 new_cell.fromJSON(cell_data);
1728 if (new_cell.cell_type == 'code' && !new_cell.output_area.trusted) {
1726 if (new_cell.cell_type == 'code' && !new_cell.output_area.trusted) {
1729 trusted = false;
1727 trusted = false;
1730 }
1728 }
1731 }
1729 }
1732 }
1730 }
1733 if (trusted != this.trusted) {
1731 if (trusted != this.trusted) {
1734 this.trusted = trusted;
1732 this.trusted = trusted;
1735 this.events.trigger("trust_changed.Notebook", trusted);
1733 this.events.trigger("trust_changed.Notebook", trusted);
1736 }
1734 }
1737 if (content.worksheets.length > 1) {
1735 if (content.worksheets.length > 1) {
1738 dialog.modal({
1736 dialog.modal({
1739 notebook: this,
1737 notebook: this,
1740 keyboard_manager: this.keyboard_manager,
1738 keyboard_manager: this.keyboard_manager,
1741 title : "Multiple worksheets",
1739 title : "Multiple worksheets",
1742 body : "This notebook has " + data.worksheets.length + " worksheets, " +
1740 body : "This notebook has " + data.worksheets.length + " worksheets, " +
1743 "but this version of IPython can only handle the first. " +
1741 "but this version of IPython can only handle the first. " +
1744 "If you save this notebook, worksheets after the first will be lost.",
1742 "If you save this notebook, worksheets after the first will be lost.",
1745 buttons : {
1743 buttons : {
1746 OK : {
1744 OK : {
1747 class : "btn-danger"
1745 class : "btn-danger"
1748 }
1746 }
1749 }
1747 }
1750 });
1748 });
1751 }
1749 }
1752 };
1750 };
1753
1751
1754 /**
1752 /**
1755 * Dump this notebook into a JSON-friendly object.
1753 * Dump this notebook into a JSON-friendly object.
1756 *
1754 *
1757 * @method toJSON
1755 * @method toJSON
1758 * @return {Object} A JSON-friendly representation of this notebook.
1756 * @return {Object} A JSON-friendly representation of this notebook.
1759 */
1757 */
1760 Notebook.prototype.toJSON = function () {
1758 Notebook.prototype.toJSON = function () {
1761 var cells = this.get_cells();
1759 var cells = this.get_cells();
1762 var ncells = cells.length;
1760 var ncells = cells.length;
1763 var cell_array = new Array(ncells);
1761 var cell_array = new Array(ncells);
1764 var trusted = true;
1762 var trusted = true;
1765 for (var i=0; i<ncells; i++) {
1763 for (var i=0; i<ncells; i++) {
1766 var cell = cells[i];
1764 var cell = cells[i];
1767 if (cell.cell_type == 'code' && !cell.output_area.trusted) {
1765 if (cell.cell_type == 'code' && !cell.output_area.trusted) {
1768 trusted = false;
1766 trusted = false;
1769 }
1767 }
1770 cell_array[i] = cell.toJSON();
1768 cell_array[i] = cell.toJSON();
1771 }
1769 }
1772 var data = {
1770 var data = {
1773 // Only handle 1 worksheet for now.
1771 // Only handle 1 worksheet for now.
1774 worksheets : [{
1772 worksheets : [{
1775 cells: cell_array,
1773 cells: cell_array,
1776 metadata: this.worksheet_metadata
1774 metadata: this.worksheet_metadata
1777 }],
1775 }],
1778 metadata : this.metadata
1776 metadata : this.metadata
1779 };
1777 };
1780 if (trusted != this.trusted) {
1778 if (trusted != this.trusted) {
1781 this.trusted = trusted;
1779 this.trusted = trusted;
1782 this.events.trigger("trust_changed.Notebook", trusted);
1780 this.events.trigger("trust_changed.Notebook", trusted);
1783 }
1781 }
1784 return data;
1782 return data;
1785 };
1783 };
1786
1784
1787 /**
1785 /**
1788 * Start an autosave timer, for periodically saving the notebook.
1786 * Start an autosave timer, for periodically saving the notebook.
1789 *
1787 *
1790 * @method set_autosave_interval
1788 * @method set_autosave_interval
1791 * @param {Integer} interval the autosave interval in milliseconds
1789 * @param {Integer} interval the autosave interval in milliseconds
1792 */
1790 */
1793 Notebook.prototype.set_autosave_interval = function (interval) {
1791 Notebook.prototype.set_autosave_interval = function (interval) {
1794 var that = this;
1792 var that = this;
1795 // clear previous interval, so we don't get simultaneous timers
1793 // clear previous interval, so we don't get simultaneous timers
1796 if (this.autosave_timer) {
1794 if (this.autosave_timer) {
1797 clearInterval(this.autosave_timer);
1795 clearInterval(this.autosave_timer);
1798 }
1796 }
1799
1797
1800 this.autosave_interval = this.minimum_autosave_interval = interval;
1798 this.autosave_interval = this.minimum_autosave_interval = interval;
1801 if (interval) {
1799 if (interval) {
1802 this.autosave_timer = setInterval(function() {
1800 this.autosave_timer = setInterval(function() {
1803 if (that.dirty) {
1801 if (that.dirty) {
1804 that.save_notebook();
1802 that.save_notebook();
1805 }
1803 }
1806 }, interval);
1804 }, interval);
1807 this.events.trigger("autosave_enabled.Notebook", interval);
1805 this.events.trigger("autosave_enabled.Notebook", interval);
1808 } else {
1806 } else {
1809 this.autosave_timer = null;
1807 this.autosave_timer = null;
1810 this.events.trigger("autosave_disabled.Notebook");
1808 this.events.trigger("autosave_disabled.Notebook");
1811 }
1809 }
1812 };
1810 };
1813
1811
1814 /**
1812 /**
1815 * Save this notebook on the server. This becomes a notebook instance's
1813 * Save this notebook on the server. This becomes a notebook instance's
1816 * .save_notebook method *after* the entire notebook has been loaded.
1814 * .save_notebook method *after* the entire notebook has been loaded.
1817 *
1815 *
1818 * @method save_notebook
1816 * @method save_notebook
1819 */
1817 */
1820 Notebook.prototype.save_notebook = function (extra_settings) {
1818 Notebook.prototype.save_notebook = function (extra_settings) {
1821 // Create a JSON model to be sent to the server.
1819 // Create a JSON model to be sent to the server.
1822 var model = {};
1820 var model = {};
1823 model.name = this.notebook_name;
1821 model.name = this.notebook_name;
1824 model.path = this.notebook_path;
1822 model.path = this.notebook_path;
1825 model.content = this.toJSON();
1823 model.content = this.toJSON();
1826 model.content.nbformat = this.nbformat;
1824 model.content.nbformat = this.nbformat;
1827 model.content.nbformat_minor = this.nbformat_minor;
1825 model.content.nbformat_minor = this.nbformat_minor;
1828 // time the ajax call for autosave tuning purposes.
1826 // time the ajax call for autosave tuning purposes.
1829 var start = new Date().getTime();
1827 var start = new Date().getTime();
1830 // We do the call with settings so we can set cache to false.
1828 // We do the call with settings so we can set cache to false.
1831 var settings = {
1829 var settings = {
1832 processData : false,
1830 processData : false,
1833 cache : false,
1831 cache : false,
1834 type : "PUT",
1832 type : "PUT",
1835 data : JSON.stringify(model),
1833 data : JSON.stringify(model),
1836 headers : {'Content-Type': 'application/json'},
1834 headers : {'Content-Type': 'application/json'},
1837 success : $.proxy(this.save_notebook_success, this, start),
1835 success : $.proxy(this.save_notebook_success, this, start),
1838 error : $.proxy(this.save_notebook_error, this)
1836 error : $.proxy(this.save_notebook_error, this)
1839 };
1837 };
1840 if (extra_settings) {
1838 if (extra_settings) {
1841 for (var key in extra_settings) {
1839 for (var key in extra_settings) {
1842 settings[key] = extra_settings[key];
1840 settings[key] = extra_settings[key];
1843 }
1841 }
1844 }
1842 }
1845 this.events.trigger('notebook_saving.Notebook');
1843 this.events.trigger('notebook_saving.Notebook');
1846 var url = utils.url_join_encode(
1844 var url = utils.url_join_encode(
1847 this.base_url,
1845 this.base_url,
1848 'api/notebooks',
1846 'api/notebooks',
1849 this.notebook_path,
1847 this.notebook_path,
1850 this.notebook_name
1848 this.notebook_name
1851 );
1849 );
1852 $.ajax(url, settings);
1850 $.ajax(url, settings);
1853 };
1851 };
1854
1852
1855 /**
1853 /**
1856 * Success callback for saving a notebook.
1854 * Success callback for saving a notebook.
1857 *
1855 *
1858 * @method save_notebook_success
1856 * @method save_notebook_success
1859 * @param {Integer} start the time when the save request started
1857 * @param {Integer} start the time when the save request started
1860 * @param {Object} data JSON representation of a notebook
1858 * @param {Object} data JSON representation of a notebook
1861 * @param {String} status Description of response status
1859 * @param {String} status Description of response status
1862 * @param {jqXHR} xhr jQuery Ajax object
1860 * @param {jqXHR} xhr jQuery Ajax object
1863 */
1861 */
1864 Notebook.prototype.save_notebook_success = function (start, data, status, xhr) {
1862 Notebook.prototype.save_notebook_success = function (start, data, status, xhr) {
1865 this.set_dirty(false);
1863 this.set_dirty(false);
1866 this.events.trigger('notebook_saved.Notebook');
1864 this.events.trigger('notebook_saved.Notebook');
1867 this._update_autosave_interval(start);
1865 this._update_autosave_interval(start);
1868 if (this._checkpoint_after_save) {
1866 if (this._checkpoint_after_save) {
1869 this.create_checkpoint();
1867 this.create_checkpoint();
1870 this._checkpoint_after_save = false;
1868 this._checkpoint_after_save = false;
1871 }
1869 }
1872 };
1870 };
1873
1871
1874 /**
1872 /**
1875 * update the autosave interval based on how long the last save took
1873 * update the autosave interval based on how long the last save took
1876 *
1874 *
1877 * @method _update_autosave_interval
1875 * @method _update_autosave_interval
1878 * @param {Integer} timestamp when the save request started
1876 * @param {Integer} timestamp when the save request started
1879 */
1877 */
1880 Notebook.prototype._update_autosave_interval = function (start) {
1878 Notebook.prototype._update_autosave_interval = function (start) {
1881 var duration = (new Date().getTime() - start);
1879 var duration = (new Date().getTime() - start);
1882 if (this.autosave_interval) {
1880 if (this.autosave_interval) {
1883 // new save interval: higher of 10x save duration or parameter (default 30 seconds)
1881 // new save interval: higher of 10x save duration or parameter (default 30 seconds)
1884 var interval = Math.max(10 * duration, this.minimum_autosave_interval);
1882 var interval = Math.max(10 * duration, this.minimum_autosave_interval);
1885 // round to 10 seconds, otherwise we will be setting a new interval too often
1883 // round to 10 seconds, otherwise we will be setting a new interval too often
1886 interval = 10000 * Math.round(interval / 10000);
1884 interval = 10000 * Math.round(interval / 10000);
1887 // set new interval, if it's changed
1885 // set new interval, if it's changed
1888 if (interval != this.autosave_interval) {
1886 if (interval != this.autosave_interval) {
1889 this.set_autosave_interval(interval);
1887 this.set_autosave_interval(interval);
1890 }
1888 }
1891 }
1889 }
1892 };
1890 };
1893
1891
1894 /**
1892 /**
1895 * Failure callback for saving a notebook.
1893 * Failure callback for saving a notebook.
1896 *
1894 *
1897 * @method save_notebook_error
1895 * @method save_notebook_error
1898 * @param {jqXHR} xhr jQuery Ajax object
1896 * @param {jqXHR} xhr jQuery Ajax object
1899 * @param {String} status Description of response status
1897 * @param {String} status Description of response status
1900 * @param {String} error HTTP error message
1898 * @param {String} error HTTP error message
1901 */
1899 */
1902 Notebook.prototype.save_notebook_error = function (xhr, status, error) {
1900 Notebook.prototype.save_notebook_error = function (xhr, status, error) {
1903 this.events.trigger('notebook_save_failed.Notebook', [xhr, status, error]);
1901 this.events.trigger('notebook_save_failed.Notebook', [xhr, status, error]);
1904 };
1902 };
1905
1903
1906 /**
1904 /**
1907 * Explicitly trust the output of this notebook.
1905 * Explicitly trust the output of this notebook.
1908 *
1906 *
1909 * @method trust_notebook
1907 * @method trust_notebook
1910 */
1908 */
1911 Notebook.prototype.trust_notebook = function (extra_settings) {
1909 Notebook.prototype.trust_notebook = function (extra_settings) {
1912 var body = $("<div>").append($("<p>")
1910 var body = $("<div>").append($("<p>")
1913 .text("A trusted IPython notebook may execute hidden malicious code ")
1911 .text("A trusted IPython notebook may execute hidden malicious code ")
1914 .append($("<strong>")
1912 .append($("<strong>")
1915 .append(
1913 .append(
1916 $("<em>").text("when you open it")
1914 $("<em>").text("when you open it")
1917 )
1915 )
1918 ).append(".").append(
1916 ).append(".").append(
1919 " Selecting trust will immediately reload this notebook in a trusted state."
1917 " Selecting trust will immediately reload this notebook in a trusted state."
1920 ).append(
1918 ).append(
1921 " For more information, see the "
1919 " For more information, see the "
1922 ).append($("<a>").attr("href", "http://ipython.org/ipython-doc/2/notebook/security.html")
1920 ).append($("<a>").attr("href", "http://ipython.org/ipython-doc/2/notebook/security.html")
1923 .text("IPython security documentation")
1921 .text("IPython security documentation")
1924 ).append(".")
1922 ).append(".")
1925 );
1923 );
1926
1924
1927 var nb = this;
1925 var nb = this;
1928 dialog.modal({
1926 dialog.modal({
1929 notebook: this,
1927 notebook: this,
1930 keyboard_manager: this.keyboard_manager,
1928 keyboard_manager: this.keyboard_manager,
1931 title: "Trust this notebook?",
1929 title: "Trust this notebook?",
1932 body: body,
1930 body: body,
1933
1931
1934 buttons: {
1932 buttons: {
1935 Cancel : {},
1933 Cancel : {},
1936 Trust : {
1934 Trust : {
1937 class : "btn-danger",
1935 class : "btn-danger",
1938 click : function () {
1936 click : function () {
1939 var cells = nb.get_cells();
1937 var cells = nb.get_cells();
1940 for (var i = 0; i < cells.length; i++) {
1938 for (var i = 0; i < cells.length; i++) {
1941 var cell = cells[i];
1939 var cell = cells[i];
1942 if (cell.cell_type == 'code') {
1940 if (cell.cell_type == 'code') {
1943 cell.output_area.trusted = true;
1941 cell.output_area.trusted = true;
1944 }
1942 }
1945 }
1943 }
1946 this.events.on('notebook_saved.Notebook', function () {
1944 this.events.on('notebook_saved.Notebook', function () {
1947 window.location.reload();
1945 window.location.reload();
1948 });
1946 });
1949 nb.save_notebook();
1947 nb.save_notebook();
1950 }
1948 }
1951 }
1949 }
1952 }
1950 }
1953 });
1951 });
1954 };
1952 };
1955
1953
1956 Notebook.prototype.new_notebook = function(){
1954 Notebook.prototype.new_notebook = function(){
1957 var path = this.notebook_path;
1955 var path = this.notebook_path;
1958 var base_url = this.base_url;
1956 var base_url = this.base_url;
1959 var settings = {
1957 var settings = {
1960 processData : false,
1958 processData : false,
1961 cache : false,
1959 cache : false,
1962 type : "POST",
1960 type : "POST",
1963 dataType : "json",
1961 dataType : "json",
1964 async : false,
1962 async : false,
1965 success : function (data, status, xhr){
1963 success : function (data, status, xhr){
1966 var notebook_name = data.name;
1964 var notebook_name = data.name;
1967 window.open(
1965 window.open(
1968 utils.url_join_encode(
1966 utils.url_join_encode(
1969 base_url,
1967 base_url,
1970 'notebooks',
1968 'notebooks',
1971 path,
1969 path,
1972 notebook_name
1970 notebook_name
1973 ),
1971 ),
1974 '_blank'
1972 '_blank'
1975 );
1973 );
1976 },
1974 },
1977 error : utils.log_ajax_error,
1975 error : utils.log_ajax_error,
1978 };
1976 };
1979 var url = utils.url_join_encode(
1977 var url = utils.url_join_encode(
1980 base_url,
1978 base_url,
1981 'api/notebooks',
1979 'api/notebooks',
1982 path
1980 path
1983 );
1981 );
1984 $.ajax(url,settings);
1982 $.ajax(url,settings);
1985 };
1983 };
1986
1984
1987
1985
1988 Notebook.prototype.copy_notebook = function(){
1986 Notebook.prototype.copy_notebook = function(){
1989 var path = this.notebook_path;
1987 var path = this.notebook_path;
1990 var base_url = this.base_url;
1988 var base_url = this.base_url;
1991 var settings = {
1989 var settings = {
1992 processData : false,
1990 processData : false,
1993 cache : false,
1991 cache : false,
1994 type : "POST",
1992 type : "POST",
1995 dataType : "json",
1993 dataType : "json",
1996 data : JSON.stringify({copy_from : this.notebook_name}),
1994 data : JSON.stringify({copy_from : this.notebook_name}),
1997 async : false,
1995 async : false,
1998 success : function (data, status, xhr) {
1996 success : function (data, status, xhr) {
1999 window.open(utils.url_join_encode(
1997 window.open(utils.url_join_encode(
2000 base_url,
1998 base_url,
2001 'notebooks',
1999 'notebooks',
2002 data.path,
2000 data.path,
2003 data.name
2001 data.name
2004 ), '_blank');
2002 ), '_blank');
2005 },
2003 },
2006 error : utils.log_ajax_error,
2004 error : utils.log_ajax_error,
2007 };
2005 };
2008 var url = utils.url_join_encode(
2006 var url = utils.url_join_encode(
2009 base_url,
2007 base_url,
2010 'api/notebooks',
2008 'api/notebooks',
2011 path
2009 path
2012 );
2010 );
2013 $.ajax(url,settings);
2011 $.ajax(url,settings);
2014 };
2012 };
2015
2013
2016 Notebook.prototype.rename = function (nbname) {
2014 Notebook.prototype.rename = function (nbname) {
2017 var that = this;
2015 var that = this;
2018 if (!nbname.match(/\.ipynb$/)) {
2016 if (!nbname.match(/\.ipynb$/)) {
2019 nbname = nbname + ".ipynb";
2017 nbname = nbname + ".ipynb";
2020 }
2018 }
2021 var data = {name: nbname};
2019 var data = {name: nbname};
2022 var settings = {
2020 var settings = {
2023 processData : false,
2021 processData : false,
2024 cache : false,
2022 cache : false,
2025 type : "PATCH",
2023 type : "PATCH",
2026 data : JSON.stringify(data),
2024 data : JSON.stringify(data),
2027 dataType: "json",
2025 dataType: "json",
2028 headers : {'Content-Type': 'application/json'},
2026 headers : {'Content-Type': 'application/json'},
2029 success : $.proxy(that.rename_success, this),
2027 success : $.proxy(that.rename_success, this),
2030 error : $.proxy(that.rename_error, this)
2028 error : $.proxy(that.rename_error, this)
2031 };
2029 };
2032 this.events.trigger('rename_notebook.Notebook', data);
2030 this.events.trigger('rename_notebook.Notebook', data);
2033 var url = utils.url_join_encode(
2031 var url = utils.url_join_encode(
2034 this.base_url,
2032 this.base_url,
2035 'api/notebooks',
2033 'api/notebooks',
2036 this.notebook_path,
2034 this.notebook_path,
2037 this.notebook_name
2035 this.notebook_name
2038 );
2036 );
2039 $.ajax(url, settings);
2037 $.ajax(url, settings);
2040 };
2038 };
2041
2039
2042 Notebook.prototype.delete = function () {
2040 Notebook.prototype.delete = function () {
2043 var that = this;
2041 var that = this;
2044 var settings = {
2042 var settings = {
2045 processData : false,
2043 processData : false,
2046 cache : false,
2044 cache : false,
2047 type : "DELETE",
2045 type : "DELETE",
2048 dataType: "json",
2046 dataType: "json",
2049 error : utils.log_ajax_error,
2047 error : utils.log_ajax_error,
2050 };
2048 };
2051 var url = utils.url_join_encode(
2049 var url = utils.url_join_encode(
2052 this.base_url,
2050 this.base_url,
2053 'api/notebooks',
2051 'api/notebooks',
2054 this.notebook_path,
2052 this.notebook_path,
2055 this.notebook_name
2053 this.notebook_name
2056 );
2054 );
2057 $.ajax(url, settings);
2055 $.ajax(url, settings);
2058 };
2056 };
2059
2057
2060
2058
2061 Notebook.prototype.rename_success = function (json, status, xhr) {
2059 Notebook.prototype.rename_success = function (json, status, xhr) {
2062 var name = this.notebook_name = json.name;
2060 var name = this.notebook_name = json.name;
2063 var path = json.path;
2061 var path = json.path;
2064 this.session.rename_notebook(name, path);
2062 this.session.rename_notebook(name, path);
2065 this.events.trigger('notebook_renamed.Notebook', json);
2063 this.events.trigger('notebook_renamed.Notebook', json);
2066 };
2064 };
2067
2065
2068 Notebook.prototype.rename_error = function (xhr, status, error) {
2066 Notebook.prototype.rename_error = function (xhr, status, error) {
2069 var that = this;
2067 var that = this;
2070 var dialog_body = $('<div/>').append(
2068 var dialog_body = $('<div/>').append(
2071 $("<p/>").addClass("rename-message")
2069 $("<p/>").addClass("rename-message")
2072 .text('This notebook name already exists.')
2070 .text('This notebook name already exists.')
2073 );
2071 );
2074 this.events.trigger('notebook_rename_failed.Notebook', [xhr, status, error]);
2072 this.events.trigger('notebook_rename_failed.Notebook', [xhr, status, error]);
2075 dialog.modal({
2073 dialog.modal({
2076 notebook: this,
2074 notebook: this,
2077 keyboard_manager: this.keyboard_manager,
2075 keyboard_manager: this.keyboard_manager,
2078 title: "Notebook Rename Error!",
2076 title: "Notebook Rename Error!",
2079 body: dialog_body,
2077 body: dialog_body,
2080 buttons : {
2078 buttons : {
2081 "Cancel": {},
2079 "Cancel": {},
2082 "OK": {
2080 "OK": {
2083 class: "btn-primary",
2081 class: "btn-primary",
2084 click: function () {
2082 click: function () {
2085 this.save_widget.rename_notebook({notebook:that});
2083 this.save_widget.rename_notebook({notebook:that});
2086 }}
2084 }}
2087 },
2085 },
2088 open : function (event, ui) {
2086 open : function (event, ui) {
2089 var that = $(this);
2087 var that = $(this);
2090 // Upon ENTER, click the OK button.
2088 // Upon ENTER, click the OK button.
2091 that.find('input[type="text"]').keydown(function (event, ui) {
2089 that.find('input[type="text"]').keydown(function (event, ui) {
2092 if (event.which === this.keyboard.keycodes.enter) {
2090 if (event.which === this.keyboard.keycodes.enter) {
2093 that.find('.btn-primary').first().click();
2091 that.find('.btn-primary').first().click();
2094 }
2092 }
2095 });
2093 });
2096 that.find('input[type="text"]').focus();
2094 that.find('input[type="text"]').focus();
2097 }
2095 }
2098 });
2096 });
2099 };
2097 };
2100
2098
2101 /**
2099 /**
2102 * Request a notebook's data from the server.
2100 * Request a notebook's data from the server.
2103 *
2101 *
2104 * @method load_notebook
2102 * @method load_notebook
2105 * @param {String} notebook_name and path A notebook to load
2103 * @param {String} notebook_name and path A notebook to load
2106 */
2104 */
2107 Notebook.prototype.load_notebook = function (notebook_name, notebook_path) {
2105 Notebook.prototype.load_notebook = function (notebook_name, notebook_path) {
2108 var that = this;
2106 var that = this;
2109 this.notebook_name = notebook_name;
2107 this.notebook_name = notebook_name;
2110 this.notebook_path = notebook_path;
2108 this.notebook_path = notebook_path;
2111 // We do the call with settings so we can set cache to false.
2109 // We do the call with settings so we can set cache to false.
2112 var settings = {
2110 var settings = {
2113 processData : false,
2111 processData : false,
2114 cache : false,
2112 cache : false,
2115 type : "GET",
2113 type : "GET",
2116 dataType : "json",
2114 dataType : "json",
2117 success : $.proxy(this.load_notebook_success,this),
2115 success : $.proxy(this.load_notebook_success,this),
2118 error : $.proxy(this.load_notebook_error,this),
2116 error : $.proxy(this.load_notebook_error,this),
2119 };
2117 };
2120 this.events.trigger('notebook_loading.Notebook');
2118 this.events.trigger('notebook_loading.Notebook');
2121 var url = utils.url_join_encode(
2119 var url = utils.url_join_encode(
2122 this.base_url,
2120 this.base_url,
2123 'api/notebooks',
2121 'api/notebooks',
2124 this.notebook_path,
2122 this.notebook_path,
2125 this.notebook_name
2123 this.notebook_name
2126 );
2124 );
2127 $.ajax(url, settings);
2125 $.ajax(url, settings);
2128 };
2126 };
2129
2127
2130 /**
2128 /**
2131 * Success callback for loading a notebook from the server.
2129 * Success callback for loading a notebook from the server.
2132 *
2130 *
2133 * Load notebook data from the JSON response.
2131 * Load notebook data from the JSON response.
2134 *
2132 *
2135 * @method load_notebook_success
2133 * @method load_notebook_success
2136 * @param {Object} data JSON representation of a notebook
2134 * @param {Object} data JSON representation of a notebook
2137 * @param {String} status Description of response status
2135 * @param {String} status Description of response status
2138 * @param {jqXHR} xhr jQuery Ajax object
2136 * @param {jqXHR} xhr jQuery Ajax object
2139 */
2137 */
2140 Notebook.prototype.load_notebook_success = function (data, status, xhr) {
2138 Notebook.prototype.load_notebook_success = function (data, status, xhr) {
2141 this.fromJSON(data);
2139 this.fromJSON(data);
2142 if (this.ncells() === 0) {
2140 if (this.ncells() === 0) {
2143 this.insert_cell_below('code');
2141 this.insert_cell_below('code');
2144 this.edit_mode(0);
2142 this.edit_mode(0);
2145 } else {
2143 } else {
2146 this.select(0);
2144 this.select(0);
2147 this.handle_command_mode(this.get_cell(0));
2145 this.handle_command_mode(this.get_cell(0));
2148 }
2146 }
2149 this.set_dirty(false);
2147 this.set_dirty(false);
2150 this.scroll_to_top();
2148 this.scroll_to_top();
2151 if (data.orig_nbformat !== undefined && data.nbformat !== data.orig_nbformat) {
2149 if (data.orig_nbformat !== undefined && data.nbformat !== data.orig_nbformat) {
2152 var msg = "This notebook has been converted from an older " +
2150 var msg = "This notebook has been converted from an older " +
2153 "notebook format (v"+data.orig_nbformat+") to the current notebook " +
2151 "notebook format (v"+data.orig_nbformat+") to the current notebook " +
2154 "format (v"+data.nbformat+"). The next time you save this notebook, the " +
2152 "format (v"+data.nbformat+"). The next time you save this notebook, the " +
2155 "newer notebook format will be used and older versions of IPython " +
2153 "newer notebook format will be used and older versions of IPython " +
2156 "may not be able to read it. To keep the older version, close the " +
2154 "may not be able to read it. To keep the older version, close the " +
2157 "notebook without saving it.";
2155 "notebook without saving it.";
2158 dialog.modal({
2156 dialog.modal({
2159 notebook: this,
2157 notebook: this,
2160 keyboard_manager: this.keyboard_manager,
2158 keyboard_manager: this.keyboard_manager,
2161 title : "Notebook converted",
2159 title : "Notebook converted",
2162 body : msg,
2160 body : msg,
2163 buttons : {
2161 buttons : {
2164 OK : {
2162 OK : {
2165 class : "btn-primary"
2163 class : "btn-primary"
2166 }
2164 }
2167 }
2165 }
2168 });
2166 });
2169 } else if (data.orig_nbformat_minor !== undefined && data.nbformat_minor !== data.orig_nbformat_minor) {
2167 } else if (data.orig_nbformat_minor !== undefined && data.nbformat_minor !== data.orig_nbformat_minor) {
2170 var that = this;
2168 var that = this;
2171 var orig_vs = 'v' + data.nbformat + '.' + data.orig_nbformat_minor;
2169 var orig_vs = 'v' + data.nbformat + '.' + data.orig_nbformat_minor;
2172 var this_vs = 'v' + data.nbformat + '.' + this.nbformat_minor;
2170 var this_vs = 'v' + data.nbformat + '.' + this.nbformat_minor;
2173 var msg = "This notebook is version " + orig_vs + ", but we only fully support up to " +
2171 var msg = "This notebook is version " + orig_vs + ", but we only fully support up to " +
2174 this_vs + ". You can still work with this notebook, but some features " +
2172 this_vs + ". You can still work with this notebook, but some features " +
2175 "introduced in later notebook versions may not be available.";
2173 "introduced in later notebook versions may not be available.";
2176
2174
2177 dialog.modal({
2175 dialog.modal({
2178 notebook: this,
2176 notebook: this,
2179 keyboard_manager: this.keyboard_manager,
2177 keyboard_manager: this.keyboard_manager,
2180 title : "Newer Notebook",
2178 title : "Newer Notebook",
2181 body : msg,
2179 body : msg,
2182 buttons : {
2180 buttons : {
2183 OK : {
2181 OK : {
2184 class : "btn-danger"
2182 class : "btn-danger"
2185 }
2183 }
2186 }
2184 }
2187 });
2185 });
2188
2186
2189 }
2187 }
2190
2188
2191 // Create the session after the notebook is completely loaded to prevent
2189 // Create the session after the notebook is completely loaded to prevent
2192 // code execution upon loading, which is a security risk.
2190 // code execution upon loading, which is a security risk.
2193 if (this.session === null) {
2191 if (this.session === null) {
2194 this.start_session();
2192 this.start_session();
2195 }
2193 }
2196 // load our checkpoint list
2194 // load our checkpoint list
2197 this.list_checkpoints();
2195 this.list_checkpoints();
2198
2196
2199 // load toolbar state
2197 // load toolbar state
2200 if (this.metadata.celltoolbar) {
2198 if (this.metadata.celltoolbar) {
2201 celltoolbar.CellToolbar.global_show();
2199 celltoolbar.CellToolbar.global_show();
2202 celltoolbar.CellToolbar.activate_preset(this.metadata.celltoolbar);
2200 celltoolbar.CellToolbar.activate_preset(this.metadata.celltoolbar);
2203 } else {
2201 } else {
2204 celltoolbar.CellToolbar.global_hide();
2202 celltoolbar.CellToolbar.global_hide();
2205 }
2203 }
2206
2204
2207 // now that we're fully loaded, it is safe to restore save functionality
2205 // now that we're fully loaded, it is safe to restore save functionality
2208 delete(this.save_notebook);
2206 delete(this.save_notebook);
2209 this.events.trigger('notebook_loaded.Notebook');
2207 this.events.trigger('notebook_loaded.Notebook');
2210 };
2208 };
2211
2209
2212 /**
2210 /**
2213 * Failure callback for loading a notebook from the server.
2211 * Failure callback for loading a notebook from the server.
2214 *
2212 *
2215 * @method load_notebook_error
2213 * @method load_notebook_error
2216 * @param {jqXHR} xhr jQuery Ajax object
2214 * @param {jqXHR} xhr jQuery Ajax object
2217 * @param {String} status Description of response status
2215 * @param {String} status Description of response status
2218 * @param {String} error HTTP error message
2216 * @param {String} error HTTP error message
2219 */
2217 */
2220 Notebook.prototype.load_notebook_error = function (xhr, status, error) {
2218 Notebook.prototype.load_notebook_error = function (xhr, status, error) {
2221 this.events.trigger('notebook_load_failed.Notebook', [xhr, status, error]);
2219 this.events.trigger('notebook_load_failed.Notebook', [xhr, status, error]);
2222 var msg;
2220 var msg;
2223 if (xhr.status === 400) {
2221 if (xhr.status === 400) {
2224 msg = error;
2222 msg = error;
2225 } else if (xhr.status === 500) {
2223 } else if (xhr.status === 500) {
2226 msg = "An unknown error occurred while loading this notebook. " +
2224 msg = "An unknown error occurred while loading this notebook. " +
2227 "This version can load notebook formats " +
2225 "This version can load notebook formats " +
2228 "v" + this.nbformat + " or earlier.";
2226 "v" + this.nbformat + " or earlier.";
2229 }
2227 }
2230 dialog.modal({
2228 dialog.modal({
2231 notebook: this,
2229 notebook: this,
2232 keyboard_manager: this.keyboard_manager,
2230 keyboard_manager: this.keyboard_manager,
2233 title: "Error loading notebook",
2231 title: "Error loading notebook",
2234 body : msg,
2232 body : msg,
2235 buttons : {
2233 buttons : {
2236 "OK": {}
2234 "OK": {}
2237 }
2235 }
2238 });
2236 });
2239 };
2237 };
2240
2238
2241 /********************* checkpoint-related *********************/
2239 /********************* checkpoint-related *********************/
2242
2240
2243 /**
2241 /**
2244 * Save the notebook then immediately create a checkpoint.
2242 * Save the notebook then immediately create a checkpoint.
2245 *
2243 *
2246 * @method save_checkpoint
2244 * @method save_checkpoint
2247 */
2245 */
2248 Notebook.prototype.save_checkpoint = function () {
2246 Notebook.prototype.save_checkpoint = function () {
2249 this._checkpoint_after_save = true;
2247 this._checkpoint_after_save = true;
2250 this.save_notebook();
2248 this.save_notebook();
2251 };
2249 };
2252
2250
2253 /**
2251 /**
2254 * Add a checkpoint for this notebook.
2252 * Add a checkpoint for this notebook.
2255 * for use as a callback from checkpoint creation.
2253 * for use as a callback from checkpoint creation.
2256 *
2254 *
2257 * @method add_checkpoint
2255 * @method add_checkpoint
2258 */
2256 */
2259 Notebook.prototype.add_checkpoint = function (checkpoint) {
2257 Notebook.prototype.add_checkpoint = function (checkpoint) {
2260 var found = false;
2258 var found = false;
2261 for (var i = 0; i < this.checkpoints.length; i++) {
2259 for (var i = 0; i < this.checkpoints.length; i++) {
2262 var existing = this.checkpoints[i];
2260 var existing = this.checkpoints[i];
2263 if (existing.id == checkpoint.id) {
2261 if (existing.id == checkpoint.id) {
2264 found = true;
2262 found = true;
2265 this.checkpoints[i] = checkpoint;
2263 this.checkpoints[i] = checkpoint;
2266 break;
2264 break;
2267 }
2265 }
2268 }
2266 }
2269 if (!found) {
2267 if (!found) {
2270 this.checkpoints.push(checkpoint);
2268 this.checkpoints.push(checkpoint);
2271 }
2269 }
2272 this.last_checkpoint = this.checkpoints[this.checkpoints.length - 1];
2270 this.last_checkpoint = this.checkpoints[this.checkpoints.length - 1];
2273 };
2271 };
2274
2272
2275 /**
2273 /**
2276 * List checkpoints for this notebook.
2274 * List checkpoints for this notebook.
2277 *
2275 *
2278 * @method list_checkpoints
2276 * @method list_checkpoints
2279 */
2277 */
2280 Notebook.prototype.list_checkpoints = function () {
2278 Notebook.prototype.list_checkpoints = function () {
2281 var url = utils.url_join_encode(
2279 var url = utils.url_join_encode(
2282 this.base_url,
2280 this.base_url,
2283 'api/notebooks',
2281 'api/notebooks',
2284 this.notebook_path,
2282 this.notebook_path,
2285 this.notebook_name,
2283 this.notebook_name,
2286 'checkpoints'
2284 'checkpoints'
2287 );
2285 );
2288 $.get(url).done(
2286 $.get(url).done(
2289 $.proxy(this.list_checkpoints_success, this)
2287 $.proxy(this.list_checkpoints_success, this)
2290 ).fail(
2288 ).fail(
2291 $.proxy(this.list_checkpoints_error, this)
2289 $.proxy(this.list_checkpoints_error, this)
2292 );
2290 );
2293 };
2291 };
2294
2292
2295 /**
2293 /**
2296 * Success callback for listing checkpoints.
2294 * Success callback for listing checkpoints.
2297 *
2295 *
2298 * @method list_checkpoint_success
2296 * @method list_checkpoint_success
2299 * @param {Object} data JSON representation of a checkpoint
2297 * @param {Object} data JSON representation of a checkpoint
2300 * @param {String} status Description of response status
2298 * @param {String} status Description of response status
2301 * @param {jqXHR} xhr jQuery Ajax object
2299 * @param {jqXHR} xhr jQuery Ajax object
2302 */
2300 */
2303 Notebook.prototype.list_checkpoints_success = function (data, status, xhr) {
2301 Notebook.prototype.list_checkpoints_success = function (data, status, xhr) {
2304 data = $.parseJSON(data);
2302 data = $.parseJSON(data);
2305 this.checkpoints = data;
2303 this.checkpoints = data;
2306 if (data.length) {
2304 if (data.length) {
2307 this.last_checkpoint = data[data.length - 1];
2305 this.last_checkpoint = data[data.length - 1];
2308 } else {
2306 } else {
2309 this.last_checkpoint = null;
2307 this.last_checkpoint = null;
2310 }
2308 }
2311 this.events.trigger('checkpoints_listed.Notebook', [data]);
2309 this.events.trigger('checkpoints_listed.Notebook', [data]);
2312 };
2310 };
2313
2311
2314 /**
2312 /**
2315 * Failure callback for listing a checkpoint.
2313 * Failure callback for listing a checkpoint.
2316 *
2314 *
2317 * @method list_checkpoint_error
2315 * @method list_checkpoint_error
2318 * @param {jqXHR} xhr jQuery Ajax object
2316 * @param {jqXHR} xhr jQuery Ajax object
2319 * @param {String} status Description of response status
2317 * @param {String} status Description of response status
2320 * @param {String} error_msg HTTP error message
2318 * @param {String} error_msg HTTP error message
2321 */
2319 */
2322 Notebook.prototype.list_checkpoints_error = function (xhr, status, error_msg) {
2320 Notebook.prototype.list_checkpoints_error = function (xhr, status, error_msg) {
2323 this.events.trigger('list_checkpoints_failed.Notebook');
2321 this.events.trigger('list_checkpoints_failed.Notebook');
2324 };
2322 };
2325
2323
2326 /**
2324 /**
2327 * Create a checkpoint of this notebook on the server from the most recent save.
2325 * Create a checkpoint of this notebook on the server from the most recent save.
2328 *
2326 *
2329 * @method create_checkpoint
2327 * @method create_checkpoint
2330 */
2328 */
2331 Notebook.prototype.create_checkpoint = function () {
2329 Notebook.prototype.create_checkpoint = function () {
2332 var url = utils.url_join_encode(
2330 var url = utils.url_join_encode(
2333 this.base_url,
2331 this.base_url,
2334 'api/notebooks',
2332 'api/notebooks',
2335 this.notebook_path,
2333 this.notebook_path,
2336 this.notebook_name,
2334 this.notebook_name,
2337 'checkpoints'
2335 'checkpoints'
2338 );
2336 );
2339 $.post(url).done(
2337 $.post(url).done(
2340 $.proxy(this.create_checkpoint_success, this)
2338 $.proxy(this.create_checkpoint_success, this)
2341 ).fail(
2339 ).fail(
2342 $.proxy(this.create_checkpoint_error, this)
2340 $.proxy(this.create_checkpoint_error, this)
2343 );
2341 );
2344 };
2342 };
2345
2343
2346 /**
2344 /**
2347 * Success callback for creating a checkpoint.
2345 * Success callback for creating a checkpoint.
2348 *
2346 *
2349 * @method create_checkpoint_success
2347 * @method create_checkpoint_success
2350 * @param {Object} data JSON representation of a checkpoint
2348 * @param {Object} data JSON representation of a checkpoint
2351 * @param {String} status Description of response status
2349 * @param {String} status Description of response status
2352 * @param {jqXHR} xhr jQuery Ajax object
2350 * @param {jqXHR} xhr jQuery Ajax object
2353 */
2351 */
2354 Notebook.prototype.create_checkpoint_success = function (data, status, xhr) {
2352 Notebook.prototype.create_checkpoint_success = function (data, status, xhr) {
2355 data = $.parseJSON(data);
2353 data = $.parseJSON(data);
2356 this.add_checkpoint(data);
2354 this.add_checkpoint(data);
2357 this.events.trigger('checkpoint_created.Notebook', data);
2355 this.events.trigger('checkpoint_created.Notebook', data);
2358 };
2356 };
2359
2357
2360 /**
2358 /**
2361 * Failure callback for creating a checkpoint.
2359 * Failure callback for creating a checkpoint.
2362 *
2360 *
2363 * @method create_checkpoint_error
2361 * @method create_checkpoint_error
2364 * @param {jqXHR} xhr jQuery Ajax object
2362 * @param {jqXHR} xhr jQuery Ajax object
2365 * @param {String} status Description of response status
2363 * @param {String} status Description of response status
2366 * @param {String} error_msg HTTP error message
2364 * @param {String} error_msg HTTP error message
2367 */
2365 */
2368 Notebook.prototype.create_checkpoint_error = function (xhr, status, error_msg) {
2366 Notebook.prototype.create_checkpoint_error = function (xhr, status, error_msg) {
2369 this.events.trigger('checkpoint_failed.Notebook');
2367 this.events.trigger('checkpoint_failed.Notebook');
2370 };
2368 };
2371
2369
2372 Notebook.prototype.restore_checkpoint_dialog = function (checkpoint) {
2370 Notebook.prototype.restore_checkpoint_dialog = function (checkpoint) {
2373 var that = this;
2371 var that = this;
2374 checkpoint = checkpoint || this.last_checkpoint;
2372 checkpoint = checkpoint || this.last_checkpoint;
2375 if ( ! checkpoint ) {
2373 if ( ! checkpoint ) {
2376 console.log("restore dialog, but no checkpoint to restore to!");
2374 console.log("restore dialog, but no checkpoint to restore to!");
2377 return;
2375 return;
2378 }
2376 }
2379 var body = $('<div/>').append(
2377 var body = $('<div/>').append(
2380 $('<p/>').addClass("p-space").text(
2378 $('<p/>').addClass("p-space").text(
2381 "Are you sure you want to revert the notebook to " +
2379 "Are you sure you want to revert the notebook to " +
2382 "the latest checkpoint?"
2380 "the latest checkpoint?"
2383 ).append(
2381 ).append(
2384 $("<strong/>").text(
2382 $("<strong/>").text(
2385 " This cannot be undone."
2383 " This cannot be undone."
2386 )
2384 )
2387 )
2385 )
2388 ).append(
2386 ).append(
2389 $('<p/>').addClass("p-space").text("The checkpoint was last updated at:")
2387 $('<p/>').addClass("p-space").text("The checkpoint was last updated at:")
2390 ).append(
2388 ).append(
2391 $('<p/>').addClass("p-space").text(
2389 $('<p/>').addClass("p-space").text(
2392 Date(checkpoint.last_modified)
2390 Date(checkpoint.last_modified)
2393 ).css("text-align", "center")
2391 ).css("text-align", "center")
2394 );
2392 );
2395
2393
2396 dialog.modal({
2394 dialog.modal({
2397 notebook: this,
2395 notebook: this,
2398 keyboard_manager: this.keyboard_manager,
2396 keyboard_manager: this.keyboard_manager,
2399 title : "Revert notebook to checkpoint",
2397 title : "Revert notebook to checkpoint",
2400 body : body,
2398 body : body,
2401 buttons : {
2399 buttons : {
2402 Revert : {
2400 Revert : {
2403 class : "btn-danger",
2401 class : "btn-danger",
2404 click : function () {
2402 click : function () {
2405 that.restore_checkpoint(checkpoint.id);
2403 that.restore_checkpoint(checkpoint.id);
2406 }
2404 }
2407 },
2405 },
2408 Cancel : {}
2406 Cancel : {}
2409 }
2407 }
2410 });
2408 });
2411 };
2409 };
2412
2410
2413 /**
2411 /**
2414 * Restore the notebook to a checkpoint state.
2412 * Restore the notebook to a checkpoint state.
2415 *
2413 *
2416 * @method restore_checkpoint
2414 * @method restore_checkpoint
2417 * @param {String} checkpoint ID
2415 * @param {String} checkpoint ID
2418 */
2416 */
2419 Notebook.prototype.restore_checkpoint = function (checkpoint) {
2417 Notebook.prototype.restore_checkpoint = function (checkpoint) {
2420 this.events.trigger('notebook_restoring.Notebook', checkpoint);
2418 this.events.trigger('notebook_restoring.Notebook', checkpoint);
2421 var url = utils.url_join_encode(
2419 var url = utils.url_join_encode(
2422 this.base_url,
2420 this.base_url,
2423 'api/notebooks',
2421 'api/notebooks',
2424 this.notebook_path,
2422 this.notebook_path,
2425 this.notebook_name,
2423 this.notebook_name,
2426 'checkpoints',
2424 'checkpoints',
2427 checkpoint
2425 checkpoint
2428 );
2426 );
2429 $.post(url).done(
2427 $.post(url).done(
2430 $.proxy(this.restore_checkpoint_success, this)
2428 $.proxy(this.restore_checkpoint_success, this)
2431 ).fail(
2429 ).fail(
2432 $.proxy(this.restore_checkpoint_error, this)
2430 $.proxy(this.restore_checkpoint_error, this)
2433 );
2431 );
2434 };
2432 };
2435
2433
2436 /**
2434 /**
2437 * Success callback for restoring a notebook to a checkpoint.
2435 * Success callback for restoring a notebook to a checkpoint.
2438 *
2436 *
2439 * @method restore_checkpoint_success
2437 * @method restore_checkpoint_success
2440 * @param {Object} data (ignored, should be empty)
2438 * @param {Object} data (ignored, should be empty)
2441 * @param {String} status Description of response status
2439 * @param {String} status Description of response status
2442 * @param {jqXHR} xhr jQuery Ajax object
2440 * @param {jqXHR} xhr jQuery Ajax object
2443 */
2441 */
2444 Notebook.prototype.restore_checkpoint_success = function (data, status, xhr) {
2442 Notebook.prototype.restore_checkpoint_success = function (data, status, xhr) {
2445 this.events.trigger('checkpoint_restored.Notebook');
2443 this.events.trigger('checkpoint_restored.Notebook');
2446 this.load_notebook(this.notebook_name, this.notebook_path);
2444 this.load_notebook(this.notebook_name, this.notebook_path);
2447 };
2445 };
2448
2446
2449 /**
2447 /**
2450 * Failure callback for restoring a notebook to a checkpoint.
2448 * Failure callback for restoring a notebook to a checkpoint.
2451 *
2449 *
2452 * @method restore_checkpoint_error
2450 * @method restore_checkpoint_error
2453 * @param {jqXHR} xhr jQuery Ajax object
2451 * @param {jqXHR} xhr jQuery Ajax object
2454 * @param {String} status Description of response status
2452 * @param {String} status Description of response status
2455 * @param {String} error_msg HTTP error message
2453 * @param {String} error_msg HTTP error message
2456 */
2454 */
2457 Notebook.prototype.restore_checkpoint_error = function (xhr, status, error_msg) {
2455 Notebook.prototype.restore_checkpoint_error = function (xhr, status, error_msg) {
2458 this.events.trigger('checkpoint_restore_failed.Notebook');
2456 this.events.trigger('checkpoint_restore_failed.Notebook');
2459 };
2457 };
2460
2458
2461 /**
2459 /**
2462 * Delete a notebook checkpoint.
2460 * Delete a notebook checkpoint.
2463 *
2461 *
2464 * @method delete_checkpoint
2462 * @method delete_checkpoint
2465 * @param {String} checkpoint ID
2463 * @param {String} checkpoint ID
2466 */
2464 */
2467 Notebook.prototype.delete_checkpoint = function (checkpoint) {
2465 Notebook.prototype.delete_checkpoint = function (checkpoint) {
2468 this.events.trigger('notebook_restoring.Notebook', checkpoint);
2466 this.events.trigger('notebook_restoring.Notebook', checkpoint);
2469 var url = utils.url_join_encode(
2467 var url = utils.url_join_encode(
2470 this.base_url,
2468 this.base_url,
2471 'api/notebooks',
2469 'api/notebooks',
2472 this.notebook_path,
2470 this.notebook_path,
2473 this.notebook_name,
2471 this.notebook_name,
2474 'checkpoints',
2472 'checkpoints',
2475 checkpoint
2473 checkpoint
2476 );
2474 );
2477 $.ajax(url, {
2475 $.ajax(url, {
2478 type: 'DELETE',
2476 type: 'DELETE',
2479 success: $.proxy(this.delete_checkpoint_success, this),
2477 success: $.proxy(this.delete_checkpoint_success, this),
2480 error: $.proxy(this.delete_checkpoint_error, this)
2478 error: $.proxy(this.delete_checkpoint_error, this)
2481 });
2479 });
2482 };
2480 };
2483
2481
2484 /**
2482 /**
2485 * Success callback for deleting a notebook checkpoint
2483 * Success callback for deleting a notebook checkpoint
2486 *
2484 *
2487 * @method delete_checkpoint_success
2485 * @method delete_checkpoint_success
2488 * @param {Object} data (ignored, should be empty)
2486 * @param {Object} data (ignored, should be empty)
2489 * @param {String} status Description of response status
2487 * @param {String} status Description of response status
2490 * @param {jqXHR} xhr jQuery Ajax object
2488 * @param {jqXHR} xhr jQuery Ajax object
2491 */
2489 */
2492 Notebook.prototype.delete_checkpoint_success = function (data, status, xhr) {
2490 Notebook.prototype.delete_checkpoint_success = function (data, status, xhr) {
2493 this.events.trigger('checkpoint_deleted.Notebook', data);
2491 this.events.trigger('checkpoint_deleted.Notebook', data);
2494 this.load_notebook(this.notebook_name, this.notebook_path);
2492 this.load_notebook(this.notebook_name, this.notebook_path);
2495 };
2493 };
2496
2494
2497 /**
2495 /**
2498 * Failure callback for deleting a notebook checkpoint.
2496 * Failure callback for deleting a notebook checkpoint.
2499 *
2497 *
2500 * @method delete_checkpoint_error
2498 * @method delete_checkpoint_error
2501 * @param {jqXHR} xhr jQuery Ajax object
2499 * @param {jqXHR} xhr jQuery Ajax object
2502 * @param {String} status Description of response status
2500 * @param {String} status Description of response status
2503 * @param {String} error_msg HTTP error message
2501 * @param {String} error_msg HTTP error message
2504 */
2502 */
2505 Notebook.prototype.delete_checkpoint_error = function (xhr, status, error_msg) {
2503 Notebook.prototype.delete_checkpoint_error = function (xhr, status, error_msg) {
2506 this.events.trigger('checkpoint_delete_failed.Notebook');
2504 this.events.trigger('checkpoint_delete_failed.Notebook');
2507 };
2505 };
2508
2506
2509
2507
2510 // For backwards compatability.
2508 // For backwards compatability.
2511 IPython.Notebook = Notebook;
2509 IPython.Notebook = Notebook;
2512
2510
2513 return {'Notebook': Notebook};
2511 return {'Notebook': Notebook};
2514 });
2512 });
@@ -1,77 +1,77 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 define([
4 define([
5 'base/js/namespace',
5 'base/js/namespace',
6 'jquery',
6 'jquery',
7 ], function(IPython, $) {
7 ], function(IPython, $) {
8 "use strict";
8 "use strict";
9
9
10 var NotificationWidget = function (selector) {
10 var NotificationWidget = function (selector) {
11 this.selector = selector;
11 this.selector = selector;
12 this.timeout = null;
12 this.timeout = null;
13 this.busy = false;
13 this.busy = false;
14 if (this.selector !== undefined) {
14 if (this.selector !== undefined) {
15 this.element = $(selector);
15 this.element = $(selector);
16 this.style();
16 this.style();
17 }
17 }
18 this.element.button();
18 this.element.button();
19 this.element.hide();
19 this.element.hide();
20 var that = this;
20 var that = this;
21
21
22 this.inner = $('<span/>');
22 this.inner = $('<span/>');
23 this.element.append(this.inner);
23 this.element.append(this.inner);
24
24
25 };
25 };
26
26
27 NotificationWidget.prototype.style = function () {
27 NotificationWidget.prototype.style = function () {
28 this.element.addClass('notification_widget pull-right');
28 this.element.addClass('notification_widget pull-right');
29 this.element.addClass('border-box-sizing');
29 this.element.addClass('border-box-sizing');
30 };
30 };
31
31
32 // msg : message to display
32 // msg : message to display
33 // timeout : time in ms before diseapearing
33 // timeout : time in ms before diseapearing
34 //
34 //
35 // if timeout <= 0
35 // if timeout <= 0
36 // click_callback : function called if user click on notification
36 // click_callback : function called if user click on notification
37 // could return false to prevent the notification to be dismissed
37 // could return false to prevent the notification to be dismissed
38 NotificationWidget.prototype.set_message = function (msg, timeout, click_callback, opts) {
38 NotificationWidget.prototype.set_message = function (msg, timeout, click_callback, options) {
39 opts = opts || {};
39 options = options || {};
40 var callback = click_callback || function() {return false;};
40 var callback = click_callback || function() {return false;};
41 var that = this;
41 var that = this;
42 this.inner.attr('class', opts.icon);
42 this.inner.attr('class', options.icon);
43 this.inner.attr('title', opts.title);
43 this.inner.attr('title', options.title);
44 this.inner.text(msg);
44 this.inner.text(msg);
45 this.element.fadeIn(100);
45 this.element.fadeIn(100);
46 if (this.timeout !== null) {
46 if (this.timeout !== null) {
47 clearTimeout(this.timeout);
47 clearTimeout(this.timeout);
48 this.timeout = null;
48 this.timeout = null;
49 }
49 }
50 if (timeout !== undefined && timeout >=0) {
50 if (timeout !== undefined && timeout >=0) {
51 this.timeout = setTimeout(function () {
51 this.timeout = setTimeout(function () {
52 that.element.fadeOut(100, function () {that.inner.text('');});
52 that.element.fadeOut(100, function () {that.inner.text('');});
53 that.timeout = null;
53 that.timeout = null;
54 }, timeout);
54 }, timeout);
55 } else {
55 } else {
56 this.element.click(function() {
56 this.element.click(function() {
57 if( callback() !== false ) {
57 if( callback() !== false ) {
58 that.element.fadeOut(100, function () {that.inner.text('');});
58 that.element.fadeOut(100, function () {that.inner.text('');});
59 that.element.unbind('click');
59 that.element.unbind('click');
60 }
60 }
61 if (that.timeout !== undefined) {
61 if (that.timeout !== undefined) {
62 that.timeout = undefined;
62 that.timeout = undefined;
63 clearTimeout(that.timeout);
63 clearTimeout(that.timeout);
64 }
64 }
65 });
65 });
66 }
66 }
67 };
67 };
68
68
69 NotificationWidget.prototype.get_message = function () {
69 NotificationWidget.prototype.get_message = function () {
70 return this.inner.html();
70 return this.inner.html();
71 };
71 };
72
72
73 // For backwards compatability.
73 // For backwards compatability.
74 IPython.NotificationWidget = NotificationWidget;
74 IPython.NotificationWidget = NotificationWidget;
75
75
76 return {'NotificationWidget': NotificationWidget};
76 return {'NotificationWidget': NotificationWidget};
77 });
77 });
@@ -1,992 +1,992 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 define([
4 define([
5 'base/js/namespace',
5 'base/js/namespace',
6 'jquery',
6 'jquery',
7 'base/js/utils',
7 'base/js/utils',
8 'base/js/security',
8 'base/js/security',
9 'base/js/keyboard',
9 'base/js/keyboard',
10 'notebook/js/mathjaxutils',
10 'notebook/js/mathjaxutils',
11 ], function(IPython, $, utils, security, keyboard, mathjaxutils) {
11 ], function(IPython, $, utils, security, keyboard, mathjaxutils) {
12 "use strict";
12 "use strict";
13
13
14 /**
14 /**
15 * @class OutputArea
15 * @class OutputArea
16 *
16 *
17 * @constructor
17 * @constructor
18 */
18 */
19
19
20 var OutputArea = function (selector, prompt_area, events, keyboard_manager) {
20 var OutputArea = function (options) {
21 this.selector = selector;
21 this.selector = options.selector;
22 this.events = events;
22 this.events = options.events;
23 this.keyboard_manager = keyboard_manager;
23 this.keyboard_manager = options.keyboard_manager;
24 this.wrapper = $(selector);
24 this.wrapper = $(options.selector);
25 this.outputs = [];
25 this.outputs = [];
26 this.collapsed = false;
26 this.collapsed = false;
27 this.scrolled = false;
27 this.scrolled = false;
28 this.trusted = true;
28 this.trusted = true;
29 this.clear_queued = null;
29 this.clear_queued = null;
30 if (prompt_area === undefined) {
30 if (options.prompt_area === undefined) {
31 this.prompt_area = true;
31 this.prompt_area = true;
32 } else {
32 } else {
33 this.prompt_area = prompt_area;
33 this.prompt_area = options.prompt_area;
34 }
34 }
35 this.create_elements();
35 this.create_elements();
36 this.style();
36 this.style();
37 this.bind_events();
37 this.bind_events();
38 };
38 };
39
39
40
40
41 /**
41 /**
42 * Class prototypes
42 * Class prototypes
43 **/
43 **/
44
44
45 OutputArea.prototype.create_elements = function () {
45 OutputArea.prototype.create_elements = function () {
46 this.element = $("<div/>");
46 this.element = $("<div/>");
47 this.collapse_button = $("<div/>");
47 this.collapse_button = $("<div/>");
48 this.prompt_overlay = $("<div/>");
48 this.prompt_overlay = $("<div/>");
49 this.wrapper.append(this.prompt_overlay);
49 this.wrapper.append(this.prompt_overlay);
50 this.wrapper.append(this.element);
50 this.wrapper.append(this.element);
51 this.wrapper.append(this.collapse_button);
51 this.wrapper.append(this.collapse_button);
52 };
52 };
53
53
54
54
55 OutputArea.prototype.style = function () {
55 OutputArea.prototype.style = function () {
56 this.collapse_button.hide();
56 this.collapse_button.hide();
57 this.prompt_overlay.hide();
57 this.prompt_overlay.hide();
58
58
59 this.wrapper.addClass('output_wrapper');
59 this.wrapper.addClass('output_wrapper');
60 this.element.addClass('output');
60 this.element.addClass('output');
61
61
62 this.collapse_button.addClass("btn btn-default output_collapsed");
62 this.collapse_button.addClass("btn btn-default output_collapsed");
63 this.collapse_button.attr('title', 'click to expand output');
63 this.collapse_button.attr('title', 'click to expand output');
64 this.collapse_button.text('. . .');
64 this.collapse_button.text('. . .');
65
65
66 this.prompt_overlay.addClass('out_prompt_overlay prompt');
66 this.prompt_overlay.addClass('out_prompt_overlay prompt');
67 this.prompt_overlay.attr('title', 'click to expand output; double click to hide output');
67 this.prompt_overlay.attr('title', 'click to expand output; double click to hide output');
68
68
69 this.collapse();
69 this.collapse();
70 };
70 };
71
71
72 /**
72 /**
73 * Should the OutputArea scroll?
73 * Should the OutputArea scroll?
74 * Returns whether the height (in lines) exceeds a threshold.
74 * Returns whether the height (in lines) exceeds a threshold.
75 *
75 *
76 * @private
76 * @private
77 * @method _should_scroll
77 * @method _should_scroll
78 * @param [lines=100]{Integer}
78 * @param [lines=100]{Integer}
79 * @return {Bool}
79 * @return {Bool}
80 *
80 *
81 */
81 */
82 OutputArea.prototype._should_scroll = function (lines) {
82 OutputArea.prototype._should_scroll = function (lines) {
83 if (lines <=0 ){ return }
83 if (lines <=0 ){ return }
84 if (!lines) {
84 if (!lines) {
85 lines = 100;
85 lines = 100;
86 }
86 }
87 // line-height from http://stackoverflow.com/questions/1185151
87 // line-height from http://stackoverflow.com/questions/1185151
88 var fontSize = this.element.css('font-size');
88 var fontSize = this.element.css('font-size');
89 var lineHeight = Math.floor(parseInt(fontSize.replace('px','')) * 1.5);
89 var lineHeight = Math.floor(parseInt(fontSize.replace('px','')) * 1.5);
90
90
91 return (this.element.height() > lines * lineHeight);
91 return (this.element.height() > lines * lineHeight);
92 };
92 };
93
93
94
94
95 OutputArea.prototype.bind_events = function () {
95 OutputArea.prototype.bind_events = function () {
96 var that = this;
96 var that = this;
97 this.prompt_overlay.dblclick(function () { that.toggle_output(); });
97 this.prompt_overlay.dblclick(function () { that.toggle_output(); });
98 this.prompt_overlay.click(function () { that.toggle_scroll(); });
98 this.prompt_overlay.click(function () { that.toggle_scroll(); });
99
99
100 this.element.resize(function () {
100 this.element.resize(function () {
101 // FIXME: Firefox on Linux misbehaves, so automatic scrolling is disabled
101 // FIXME: Firefox on Linux misbehaves, so automatic scrolling is disabled
102 if ( utils.browser[0] === "Firefox" ) {
102 if ( utils.browser[0] === "Firefox" ) {
103 return;
103 return;
104 }
104 }
105 // maybe scroll output,
105 // maybe scroll output,
106 // if it's grown large enough and hasn't already been scrolled.
106 // if it's grown large enough and hasn't already been scrolled.
107 if ( !that.scrolled && that._should_scroll(OutputArea.auto_scroll_threshold)) {
107 if ( !that.scrolled && that._should_scroll(OutputArea.auto_scroll_threshold)) {
108 that.scroll_area();
108 that.scroll_area();
109 }
109 }
110 });
110 });
111 this.collapse_button.click(function () {
111 this.collapse_button.click(function () {
112 that.expand();
112 that.expand();
113 });
113 });
114 };
114 };
115
115
116
116
117 OutputArea.prototype.collapse = function () {
117 OutputArea.prototype.collapse = function () {
118 if (!this.collapsed) {
118 if (!this.collapsed) {
119 this.element.hide();
119 this.element.hide();
120 this.prompt_overlay.hide();
120 this.prompt_overlay.hide();
121 if (this.element.html()){
121 if (this.element.html()){
122 this.collapse_button.show();
122 this.collapse_button.show();
123 }
123 }
124 this.collapsed = true;
124 this.collapsed = true;
125 }
125 }
126 };
126 };
127
127
128
128
129 OutputArea.prototype.expand = function () {
129 OutputArea.prototype.expand = function () {
130 if (this.collapsed) {
130 if (this.collapsed) {
131 this.collapse_button.hide();
131 this.collapse_button.hide();
132 this.element.show();
132 this.element.show();
133 this.prompt_overlay.show();
133 this.prompt_overlay.show();
134 this.collapsed = false;
134 this.collapsed = false;
135 }
135 }
136 };
136 };
137
137
138
138
139 OutputArea.prototype.toggle_output = function () {
139 OutputArea.prototype.toggle_output = function () {
140 if (this.collapsed) {
140 if (this.collapsed) {
141 this.expand();
141 this.expand();
142 } else {
142 } else {
143 this.collapse();
143 this.collapse();
144 }
144 }
145 };
145 };
146
146
147
147
148 OutputArea.prototype.scroll_area = function () {
148 OutputArea.prototype.scroll_area = function () {
149 this.element.addClass('output_scroll');
149 this.element.addClass('output_scroll');
150 this.prompt_overlay.attr('title', 'click to unscroll output; double click to hide');
150 this.prompt_overlay.attr('title', 'click to unscroll output; double click to hide');
151 this.scrolled = true;
151 this.scrolled = true;
152 };
152 };
153
153
154
154
155 OutputArea.prototype.unscroll_area = function () {
155 OutputArea.prototype.unscroll_area = function () {
156 this.element.removeClass('output_scroll');
156 this.element.removeClass('output_scroll');
157 this.prompt_overlay.attr('title', 'click to scroll output; double click to hide');
157 this.prompt_overlay.attr('title', 'click to scroll output; double click to hide');
158 this.scrolled = false;
158 this.scrolled = false;
159 };
159 };
160
160
161 /**
161 /**
162 *
162 *
163 * Scroll OutputArea if height supperior than a threshold (in lines).
163 * Scroll OutputArea if height supperior than a threshold (in lines).
164 *
164 *
165 * Threshold is a maximum number of lines. If unspecified, defaults to
165 * Threshold is a maximum number of lines. If unspecified, defaults to
166 * OutputArea.minimum_scroll_threshold.
166 * OutputArea.minimum_scroll_threshold.
167 *
167 *
168 * Negative threshold will prevent the OutputArea from ever scrolling.
168 * Negative threshold will prevent the OutputArea from ever scrolling.
169 *
169 *
170 * @method scroll_if_long
170 * @method scroll_if_long
171 *
171 *
172 * @param [lines=20]{Number} Default to 20 if not set,
172 * @param [lines=20]{Number} Default to 20 if not set,
173 * behavior undefined for value of `0`.
173 * behavior undefined for value of `0`.
174 *
174 *
175 **/
175 **/
176 OutputArea.prototype.scroll_if_long = function (lines) {
176 OutputArea.prototype.scroll_if_long = function (lines) {
177 var n = lines | OutputArea.minimum_scroll_threshold;
177 var n = lines | OutputArea.minimum_scroll_threshold;
178 if(n <= 0){
178 if(n <= 0){
179 return
179 return
180 }
180 }
181
181
182 if (this._should_scroll(n)) {
182 if (this._should_scroll(n)) {
183 // only allow scrolling long-enough output
183 // only allow scrolling long-enough output
184 this.scroll_area();
184 this.scroll_area();
185 }
185 }
186 };
186 };
187
187
188
188
189 OutputArea.prototype.toggle_scroll = function () {
189 OutputArea.prototype.toggle_scroll = function () {
190 if (this.scrolled) {
190 if (this.scrolled) {
191 this.unscroll_area();
191 this.unscroll_area();
192 } else {
192 } else {
193 // only allow scrolling long-enough output
193 // only allow scrolling long-enough output
194 this.scroll_if_long();
194 this.scroll_if_long();
195 }
195 }
196 };
196 };
197
197
198
198
199 // typeset with MathJax if MathJax is available
199 // typeset with MathJax if MathJax is available
200 OutputArea.prototype.typeset = function () {
200 OutputArea.prototype.typeset = function () {
201 if (window.MathJax){
201 if (window.MathJax){
202 MathJax.Hub.Queue(["Typeset",MathJax.Hub]);
202 MathJax.Hub.Queue(["Typeset",MathJax.Hub]);
203 }
203 }
204 };
204 };
205
205
206
206
207 OutputArea.prototype.handle_output = function (msg) {
207 OutputArea.prototype.handle_output = function (msg) {
208 var json = {};
208 var json = {};
209 var msg_type = json.output_type = msg.header.msg_type;
209 var msg_type = json.output_type = msg.header.msg_type;
210 var content = msg.content;
210 var content = msg.content;
211 if (msg_type === "stream") {
211 if (msg_type === "stream") {
212 json.text = content.data;
212 json.text = content.data;
213 json.stream = content.name;
213 json.stream = content.name;
214 } else if (msg_type === "display_data") {
214 } else if (msg_type === "display_data") {
215 json = content.data;
215 json = content.data;
216 json.output_type = msg_type;
216 json.output_type = msg_type;
217 json.metadata = content.metadata;
217 json.metadata = content.metadata;
218 } else if (msg_type === "execute_result") {
218 } else if (msg_type === "execute_result") {
219 json = content.data;
219 json = content.data;
220 json.output_type = msg_type;
220 json.output_type = msg_type;
221 json.metadata = content.metadata;
221 json.metadata = content.metadata;
222 json.prompt_number = content.execution_count;
222 json.prompt_number = content.execution_count;
223 } else if (msg_type === "error") {
223 } else if (msg_type === "error") {
224 json.ename = content.ename;
224 json.ename = content.ename;
225 json.evalue = content.evalue;
225 json.evalue = content.evalue;
226 json.traceback = content.traceback;
226 json.traceback = content.traceback;
227 } else {
227 } else {
228 console.log("unhandled output message", msg);
228 console.log("unhandled output message", msg);
229 return;
229 return;
230 }
230 }
231 this.append_output(json);
231 this.append_output(json);
232 };
232 };
233
233
234
234
235 OutputArea.prototype.rename_keys = function (data, key_map) {
235 OutputArea.prototype.rename_keys = function (data, key_map) {
236 var remapped = {};
236 var remapped = {};
237 for (var key in data) {
237 for (var key in data) {
238 var new_key = key_map[key] || key;
238 var new_key = key_map[key] || key;
239 remapped[new_key] = data[key];
239 remapped[new_key] = data[key];
240 }
240 }
241 return remapped;
241 return remapped;
242 };
242 };
243
243
244
244
245 OutputArea.output_types = [
245 OutputArea.output_types = [
246 'application/javascript',
246 'application/javascript',
247 'text/html',
247 'text/html',
248 'text/markdown',
248 'text/markdown',
249 'text/latex',
249 'text/latex',
250 'image/svg+xml',
250 'image/svg+xml',
251 'image/png',
251 'image/png',
252 'image/jpeg',
252 'image/jpeg',
253 'application/pdf',
253 'application/pdf',
254 'text/plain'
254 'text/plain'
255 ];
255 ];
256
256
257 OutputArea.prototype.validate_output = function (json) {
257 OutputArea.prototype.validate_output = function (json) {
258 // scrub invalid outputs
258 // scrub invalid outputs
259 // TODO: right now everything is a string, but JSON really shouldn't be.
259 // TODO: right now everything is a string, but JSON really shouldn't be.
260 // nbformat 4 will fix that.
260 // nbformat 4 will fix that.
261 $.map(OutputArea.output_types, function(key){
261 $.map(OutputArea.output_types, function(key){
262 if (json[key] !== undefined && typeof json[key] !== 'string') {
262 if (json[key] !== undefined && typeof json[key] !== 'string') {
263 console.log("Invalid type for " + key, json[key]);
263 console.log("Invalid type for " + key, json[key]);
264 delete json[key];
264 delete json[key];
265 }
265 }
266 });
266 });
267 return json;
267 return json;
268 };
268 };
269
269
270 OutputArea.prototype.append_output = function (json) {
270 OutputArea.prototype.append_output = function (json) {
271 this.expand();
271 this.expand();
272
272
273 // validate output data types
273 // validate output data types
274 json = this.validate_output(json);
274 json = this.validate_output(json);
275
275
276 // Clear the output if clear is queued.
276 // Clear the output if clear is queued.
277 var needs_height_reset = false;
277 var needs_height_reset = false;
278 if (this.clear_queued) {
278 if (this.clear_queued) {
279 this.clear_output(false);
279 this.clear_output(false);
280 needs_height_reset = true;
280 needs_height_reset = true;
281 }
281 }
282
282
283 if (json.output_type === 'execute_result') {
283 if (json.output_type === 'execute_result') {
284 this.append_execute_result(json);
284 this.append_execute_result(json);
285 } else if (json.output_type === 'error') {
285 } else if (json.output_type === 'error') {
286 this.append_error(json);
286 this.append_error(json);
287 } else if (json.output_type === 'stream') {
287 } else if (json.output_type === 'stream') {
288 this.append_stream(json);
288 this.append_stream(json);
289 }
289 }
290
290
291 // We must release the animation fixed height in a callback since Gecko
291 // We must release the animation fixed height in a callback since Gecko
292 // (FireFox) doesn't render the image immediately as the data is
292 // (FireFox) doesn't render the image immediately as the data is
293 // available.
293 // available.
294 var that = this;
294 var that = this;
295 var handle_appended = function ($el) {
295 var handle_appended = function ($el) {
296 // Only reset the height to automatic if the height is currently
296 // Only reset the height to automatic if the height is currently
297 // fixed (done by wait=True flag on clear_output).
297 // fixed (done by wait=True flag on clear_output).
298 if (needs_height_reset) {
298 if (needs_height_reset) {
299 that.element.height('');
299 that.element.height('');
300 }
300 }
301 that.element.trigger('resize');
301 that.element.trigger('resize');
302 };
302 };
303 if (json.output_type === 'display_data') {
303 if (json.output_type === 'display_data') {
304 this.append_display_data(json, handle_appended);
304 this.append_display_data(json, handle_appended);
305 } else {
305 } else {
306 handle_appended();
306 handle_appended();
307 }
307 }
308
308
309 this.outputs.push(json);
309 this.outputs.push(json);
310 };
310 };
311
311
312
312
313 OutputArea.prototype.create_output_area = function () {
313 OutputArea.prototype.create_output_area = function () {
314 var oa = $("<div/>").addClass("output_area");
314 var oa = $("<div/>").addClass("output_area");
315 if (this.prompt_area) {
315 if (this.prompt_area) {
316 oa.append($('<div/>').addClass('prompt'));
316 oa.append($('<div/>').addClass('prompt'));
317 }
317 }
318 return oa;
318 return oa;
319 };
319 };
320
320
321
321
322 function _get_metadata_key(metadata, key, mime) {
322 function _get_metadata_key(metadata, key, mime) {
323 var mime_md = metadata[mime];
323 var mime_md = metadata[mime];
324 // mime-specific higher priority
324 // mime-specific higher priority
325 if (mime_md && mime_md[key] !== undefined) {
325 if (mime_md && mime_md[key] !== undefined) {
326 return mime_md[key];
326 return mime_md[key];
327 }
327 }
328 // fallback on global
328 // fallback on global
329 return metadata[key];
329 return metadata[key];
330 }
330 }
331
331
332 OutputArea.prototype.create_output_subarea = function(md, classes, mime) {
332 OutputArea.prototype.create_output_subarea = function(md, classes, mime) {
333 var subarea = $('<div/>').addClass('output_subarea').addClass(classes);
333 var subarea = $('<div/>').addClass('output_subarea').addClass(classes);
334 if (_get_metadata_key(md, 'isolated', mime)) {
334 if (_get_metadata_key(md, 'isolated', mime)) {
335 // Create an iframe to isolate the subarea from the rest of the
335 // Create an iframe to isolate the subarea from the rest of the
336 // document
336 // document
337 var iframe = $('<iframe/>').addClass('box-flex1');
337 var iframe = $('<iframe/>').addClass('box-flex1');
338 iframe.css({'height':1, 'width':'100%', 'display':'block'});
338 iframe.css({'height':1, 'width':'100%', 'display':'block'});
339 iframe.attr('frameborder', 0);
339 iframe.attr('frameborder', 0);
340 iframe.attr('scrolling', 'auto');
340 iframe.attr('scrolling', 'auto');
341
341
342 // Once the iframe is loaded, the subarea is dynamically inserted
342 // Once the iframe is loaded, the subarea is dynamically inserted
343 iframe.on('load', function() {
343 iframe.on('load', function() {
344 // Workaround needed by Firefox, to properly render svg inside
344 // Workaround needed by Firefox, to properly render svg inside
345 // iframes, see http://stackoverflow.com/questions/10177190/
345 // iframes, see http://stackoverflow.com/questions/10177190/
346 // svg-dynamically-added-to-iframe-does-not-render-correctly
346 // svg-dynamically-added-to-iframe-does-not-render-correctly
347 this.contentDocument.open();
347 this.contentDocument.open();
348
348
349 // Insert the subarea into the iframe
349 // Insert the subarea into the iframe
350 // We must directly write the html. When using Jquery's append
350 // We must directly write the html. When using Jquery's append
351 // method, javascript is evaluated in the parent document and
351 // method, javascript is evaluated in the parent document and
352 // not in the iframe document. At this point, subarea doesn't
352 // not in the iframe document. At this point, subarea doesn't
353 // contain any user content.
353 // contain any user content.
354 this.contentDocument.write(subarea.html());
354 this.contentDocument.write(subarea.html());
355
355
356 this.contentDocument.close();
356 this.contentDocument.close();
357
357
358 var body = this.contentDocument.body;
358 var body = this.contentDocument.body;
359 // Adjust the iframe height automatically
359 // Adjust the iframe height automatically
360 iframe.height(body.scrollHeight + 'px');
360 iframe.height(body.scrollHeight + 'px');
361 });
361 });
362
362
363 // Elements should be appended to the inner subarea and not to the
363 // Elements should be appended to the inner subarea and not to the
364 // iframe
364 // iframe
365 iframe.append = function(that) {
365 iframe.append = function(that) {
366 subarea.append(that);
366 subarea.append(that);
367 };
367 };
368
368
369 return iframe;
369 return iframe;
370 } else {
370 } else {
371 return subarea;
371 return subarea;
372 }
372 }
373 }
373 }
374
374
375
375
376 OutputArea.prototype._append_javascript_error = function (err, element) {
376 OutputArea.prototype._append_javascript_error = function (err, element) {
377 // display a message when a javascript error occurs in display output
377 // display a message when a javascript error occurs in display output
378 var msg = "Javascript error adding output!"
378 var msg = "Javascript error adding output!"
379 if ( element === undefined ) return;
379 if ( element === undefined ) return;
380 element
380 element
381 .append($('<div/>').text(msg).addClass('js-error'))
381 .append($('<div/>').text(msg).addClass('js-error'))
382 .append($('<div/>').text(err.toString()).addClass('js-error'))
382 .append($('<div/>').text(err.toString()).addClass('js-error'))
383 .append($('<div/>').text('See your browser Javascript console for more details.').addClass('js-error'));
383 .append($('<div/>').text('See your browser Javascript console for more details.').addClass('js-error'));
384 };
384 };
385
385
386 OutputArea.prototype._safe_append = function (toinsert) {
386 OutputArea.prototype._safe_append = function (toinsert) {
387 // safely append an item to the document
387 // safely append an item to the document
388 // this is an object created by user code,
388 // this is an object created by user code,
389 // and may have errors, which should not be raised
389 // and may have errors, which should not be raised
390 // under any circumstances.
390 // under any circumstances.
391 try {
391 try {
392 this.element.append(toinsert);
392 this.element.append(toinsert);
393 } catch(err) {
393 } catch(err) {
394 console.log(err);
394 console.log(err);
395 // Create an actual output_area and output_subarea, which creates
395 // Create an actual output_area and output_subarea, which creates
396 // the prompt area and the proper indentation.
396 // the prompt area and the proper indentation.
397 var toinsert = this.create_output_area();
397 var toinsert = this.create_output_area();
398 var subarea = $('<div/>').addClass('output_subarea');
398 var subarea = $('<div/>').addClass('output_subarea');
399 toinsert.append(subarea);
399 toinsert.append(subarea);
400 this._append_javascript_error(err, subarea);
400 this._append_javascript_error(err, subarea);
401 this.element.append(toinsert);
401 this.element.append(toinsert);
402 }
402 }
403 };
403 };
404
404
405
405
406 OutputArea.prototype.append_execute_result = function (json) {
406 OutputArea.prototype.append_execute_result = function (json) {
407 var n = json.prompt_number || ' ';
407 var n = json.prompt_number || ' ';
408 var toinsert = this.create_output_area();
408 var toinsert = this.create_output_area();
409 if (this.prompt_area) {
409 if (this.prompt_area) {
410 toinsert.find('div.prompt').addClass('output_prompt').text('Out[' + n + ']:');
410 toinsert.find('div.prompt').addClass('output_prompt').text('Out[' + n + ']:');
411 }
411 }
412 var inserted = this.append_mime_type(json, toinsert);
412 var inserted = this.append_mime_type(json, toinsert);
413 if (inserted) {
413 if (inserted) {
414 inserted.addClass('output_result');
414 inserted.addClass('output_result');
415 }
415 }
416 this._safe_append(toinsert);
416 this._safe_append(toinsert);
417 // If we just output latex, typeset it.
417 // If we just output latex, typeset it.
418 if ((json['text/latex'] !== undefined) ||
418 if ((json['text/latex'] !== undefined) ||
419 (json['text/html'] !== undefined) ||
419 (json['text/html'] !== undefined) ||
420 (json['text/markdown'] !== undefined)) {
420 (json['text/markdown'] !== undefined)) {
421 this.typeset();
421 this.typeset();
422 }
422 }
423 };
423 };
424
424
425
425
426 OutputArea.prototype.append_error = function (json) {
426 OutputArea.prototype.append_error = function (json) {
427 var tb = json.traceback;
427 var tb = json.traceback;
428 if (tb !== undefined && tb.length > 0) {
428 if (tb !== undefined && tb.length > 0) {
429 var s = '';
429 var s = '';
430 var len = tb.length;
430 var len = tb.length;
431 for (var i=0; i<len; i++) {
431 for (var i=0; i<len; i++) {
432 s = s + tb[i] + '\n';
432 s = s + tb[i] + '\n';
433 }
433 }
434 s = s + '\n';
434 s = s + '\n';
435 var toinsert = this.create_output_area();
435 var toinsert = this.create_output_area();
436 var append_text = OutputArea.append_map['text/plain'];
436 var append_text = OutputArea.append_map['text/plain'];
437 if (append_text) {
437 if (append_text) {
438 append_text.apply(this, [s, {}, toinsert]).addClass('output_error');
438 append_text.apply(this, [s, {}, toinsert]).addClass('output_error');
439 }
439 }
440 this._safe_append(toinsert);
440 this._safe_append(toinsert);
441 }
441 }
442 };
442 };
443
443
444
444
445 OutputArea.prototype.append_stream = function (json) {
445 OutputArea.prototype.append_stream = function (json) {
446 // temporary fix: if stream undefined (json file written prior to this patch),
446 // temporary fix: if stream undefined (json file written prior to this patch),
447 // default to most likely stdout:
447 // default to most likely stdout:
448 if (json.stream === undefined){
448 if (json.stream === undefined){
449 json.stream = 'stdout';
449 json.stream = 'stdout';
450 }
450 }
451 var text = json.text;
451 var text = json.text;
452 var subclass = "output_"+json.stream;
452 var subclass = "output_"+json.stream;
453 if (this.outputs.length > 0){
453 if (this.outputs.length > 0){
454 // have at least one output to consider
454 // have at least one output to consider
455 var last = this.outputs[this.outputs.length-1];
455 var last = this.outputs[this.outputs.length-1];
456 if (last.output_type == 'stream' && json.stream == last.stream){
456 if (last.output_type == 'stream' && json.stream == last.stream){
457 // latest output was in the same stream,
457 // latest output was in the same stream,
458 // so append directly into its pre tag
458 // so append directly into its pre tag
459 // escape ANSI & HTML specials:
459 // escape ANSI & HTML specials:
460 var pre = this.element.find('div.'+subclass).last().find('pre');
460 var pre = this.element.find('div.'+subclass).last().find('pre');
461 var html = utils.fixCarriageReturn(
461 var html = utils.fixCarriageReturn(
462 pre.html() + utils.fixConsole(text));
462 pre.html() + utils.fixConsole(text));
463 // The only user content injected with this HTML call is
463 // The only user content injected with this HTML call is
464 // escaped by the fixConsole() method.
464 // escaped by the fixConsole() method.
465 pre.html(html);
465 pre.html(html);
466 return;
466 return;
467 }
467 }
468 }
468 }
469
469
470 if (!text.replace("\r", "")) {
470 if (!text.replace("\r", "")) {
471 // text is nothing (empty string, \r, etc.)
471 // text is nothing (empty string, \r, etc.)
472 // so don't append any elements, which might add undesirable space
472 // so don't append any elements, which might add undesirable space
473 return;
473 return;
474 }
474 }
475
475
476 // If we got here, attach a new div
476 // If we got here, attach a new div
477 var toinsert = this.create_output_area();
477 var toinsert = this.create_output_area();
478 var append_text = OutputArea.append_map['text/plain'];
478 var append_text = OutputArea.append_map['text/plain'];
479 if (append_text) {
479 if (append_text) {
480 append_text.apply(this, [text, {}, toinsert]).addClass("output_stream " + subclass);
480 append_text.apply(this, [text, {}, toinsert]).addClass("output_stream " + subclass);
481 }
481 }
482 this._safe_append(toinsert);
482 this._safe_append(toinsert);
483 };
483 };
484
484
485
485
486 OutputArea.prototype.append_display_data = function (json, handle_inserted) {
486 OutputArea.prototype.append_display_data = function (json, handle_inserted) {
487 var toinsert = this.create_output_area();
487 var toinsert = this.create_output_area();
488 if (this.append_mime_type(json, toinsert, handle_inserted)) {
488 if (this.append_mime_type(json, toinsert, handle_inserted)) {
489 this._safe_append(toinsert);
489 this._safe_append(toinsert);
490 // If we just output latex, typeset it.
490 // If we just output latex, typeset it.
491 if ((json['text/latex'] !== undefined) ||
491 if ((json['text/latex'] !== undefined) ||
492 (json['text/html'] !== undefined) ||
492 (json['text/html'] !== undefined) ||
493 (json['text/markdown'] !== undefined)) {
493 (json['text/markdown'] !== undefined)) {
494 this.typeset();
494 this.typeset();
495 }
495 }
496 }
496 }
497 };
497 };
498
498
499
499
500 OutputArea.safe_outputs = {
500 OutputArea.safe_outputs = {
501 'text/plain' : true,
501 'text/plain' : true,
502 'text/latex' : true,
502 'text/latex' : true,
503 'image/png' : true,
503 'image/png' : true,
504 'image/jpeg' : true
504 'image/jpeg' : true
505 };
505 };
506
506
507 OutputArea.prototype.append_mime_type = function (json, element, handle_inserted) {
507 OutputArea.prototype.append_mime_type = function (json, element, handle_inserted) {
508 for (var i=0; i < OutputArea.display_order.length; i++) {
508 for (var i=0; i < OutputArea.display_order.length; i++) {
509 var type = OutputArea.display_order[i];
509 var type = OutputArea.display_order[i];
510 var append = OutputArea.append_map[type];
510 var append = OutputArea.append_map[type];
511 if ((json[type] !== undefined) && append) {
511 if ((json[type] !== undefined) && append) {
512 var value = json[type];
512 var value = json[type];
513 if (!this.trusted && !OutputArea.safe_outputs[type]) {
513 if (!this.trusted && !OutputArea.safe_outputs[type]) {
514 // not trusted, sanitize HTML
514 // not trusted, sanitize HTML
515 if (type==='text/html' || type==='text/svg') {
515 if (type==='text/html' || type==='text/svg') {
516 value = security.sanitize_html(value);
516 value = security.sanitize_html(value);
517 } else {
517 } else {
518 // don't display if we don't know how to sanitize it
518 // don't display if we don't know how to sanitize it
519 console.log("Ignoring untrusted " + type + " output.");
519 console.log("Ignoring untrusted " + type + " output.");
520 continue;
520 continue;
521 }
521 }
522 }
522 }
523 var md = json.metadata || {};
523 var md = json.metadata || {};
524 var toinsert = append.apply(this, [value, md, element, handle_inserted]);
524 var toinsert = append.apply(this, [value, md, element, handle_inserted]);
525 // Since only the png and jpeg mime types call the inserted
525 // Since only the png and jpeg mime types call the inserted
526 // callback, if the mime type is something other we must call the
526 // callback, if the mime type is something other we must call the
527 // inserted callback only when the element is actually inserted
527 // inserted callback only when the element is actually inserted
528 // into the DOM. Use a timeout of 0 to do this.
528 // into the DOM. Use a timeout of 0 to do this.
529 if (['image/png', 'image/jpeg'].indexOf(type) < 0 && handle_inserted !== undefined) {
529 if (['image/png', 'image/jpeg'].indexOf(type) < 0 && handle_inserted !== undefined) {
530 setTimeout(handle_inserted, 0);
530 setTimeout(handle_inserted, 0);
531 }
531 }
532 this.events.trigger('output_appended.OutputArea', [type, value, md, toinsert]);
532 this.events.trigger('output_appended.OutputArea', [type, value, md, toinsert]);
533 return toinsert;
533 return toinsert;
534 }
534 }
535 }
535 }
536 return null;
536 return null;
537 };
537 };
538
538
539
539
540 var append_html = function (html, md, element) {
540 var append_html = function (html, md, element) {
541 var type = 'text/html';
541 var type = 'text/html';
542 var toinsert = this.create_output_subarea(md, "output_html rendered_html", type);
542 var toinsert = this.create_output_subarea(md, "output_html rendered_html", type);
543 this.keyboard_manager.register_events(toinsert);
543 this.keyboard_manager.register_events(toinsert);
544 toinsert.append(html);
544 toinsert.append(html);
545 element.append(toinsert);
545 element.append(toinsert);
546 return toinsert;
546 return toinsert;
547 };
547 };
548
548
549
549
550 var append_markdown = function(markdown, md, element) {
550 var append_markdown = function(markdown, md, element) {
551 var type = 'text/markdown';
551 var type = 'text/markdown';
552 var toinsert = this.create_output_subarea(md, "output_markdown", type);
552 var toinsert = this.create_output_subarea(md, "output_markdown", type);
553 var text_and_math = mathjaxutils.remove_math(markdown);
553 var text_and_math = mathjaxutils.remove_math(markdown);
554 var text = text_and_math[0];
554 var text = text_and_math[0];
555 var math = text_and_math[1];
555 var math = text_and_math[1];
556 var html = marked.parser(marked.lexer(text));
556 var html = marked.parser(marked.lexer(text));
557 html = mathjaxutils.replace_math(html, math);
557 html = mathjaxutils.replace_math(html, math);
558 toinsert.append(html);
558 toinsert.append(html);
559 element.append(toinsert);
559 element.append(toinsert);
560 return toinsert;
560 return toinsert;
561 };
561 };
562
562
563
563
564 var append_javascript = function (js, md, element) {
564 var append_javascript = function (js, md, element) {
565 // We just eval the JS code, element appears in the local scope.
565 // We just eval the JS code, element appears in the local scope.
566 var type = 'application/javascript';
566 var type = 'application/javascript';
567 var toinsert = this.create_output_subarea(md, "output_javascript", type);
567 var toinsert = this.create_output_subarea(md, "output_javascript", type);
568 this.keyboard_manager.register_events(toinsert);
568 this.keyboard_manager.register_events(toinsert);
569 element.append(toinsert);
569 element.append(toinsert);
570
570
571 // Fix for ipython/issues/5293, make sure `element` is the area which
571 // Fix for ipython/issues/5293, make sure `element` is the area which
572 // output can be inserted into at the time of JS execution.
572 // output can be inserted into at the time of JS execution.
573 element = toinsert;
573 element = toinsert;
574 try {
574 try {
575 eval(js);
575 eval(js);
576 } catch(err) {
576 } catch(err) {
577 console.log(err);
577 console.log(err);
578 this._append_javascript_error(err, toinsert);
578 this._append_javascript_error(err, toinsert);
579 }
579 }
580 return toinsert;
580 return toinsert;
581 };
581 };
582
582
583
583
584 var append_text = function (data, md, element) {
584 var append_text = function (data, md, element) {
585 var type = 'text/plain';
585 var type = 'text/plain';
586 var toinsert = this.create_output_subarea(md, "output_text", type);
586 var toinsert = this.create_output_subarea(md, "output_text", type);
587 // escape ANSI & HTML specials in plaintext:
587 // escape ANSI & HTML specials in plaintext:
588 data = utils.fixConsole(data);
588 data = utils.fixConsole(data);
589 data = utils.fixCarriageReturn(data);
589 data = utils.fixCarriageReturn(data);
590 data = utils.autoLinkUrls(data);
590 data = utils.autoLinkUrls(data);
591 // The only user content injected with this HTML call is
591 // The only user content injected with this HTML call is
592 // escaped by the fixConsole() method.
592 // escaped by the fixConsole() method.
593 toinsert.append($("<pre/>").html(data));
593 toinsert.append($("<pre/>").html(data));
594 element.append(toinsert);
594 element.append(toinsert);
595 return toinsert;
595 return toinsert;
596 };
596 };
597
597
598
598
599 var append_svg = function (svg_html, md, element) {
599 var append_svg = function (svg_html, md, element) {
600 var type = 'image/svg+xml';
600 var type = 'image/svg+xml';
601 var toinsert = this.create_output_subarea(md, "output_svg", type);
601 var toinsert = this.create_output_subarea(md, "output_svg", type);
602
602
603 // Get the svg element from within the HTML.
603 // Get the svg element from within the HTML.
604 var svg = $('<div />').html(svg_html).find('svg');
604 var svg = $('<div />').html(svg_html).find('svg');
605 var svg_area = $('<div />');
605 var svg_area = $('<div />');
606 var width = svg.attr('width');
606 var width = svg.attr('width');
607 var height = svg.attr('height');
607 var height = svg.attr('height');
608 svg
608 svg
609 .width('100%')
609 .width('100%')
610 .height('100%');
610 .height('100%');
611 svg_area
611 svg_area
612 .width(width)
612 .width(width)
613 .height(height);
613 .height(height);
614
614
615 // The jQuery resize handlers don't seem to work on the svg element.
615 // The jQuery resize handlers don't seem to work on the svg element.
616 // When the svg renders completely, measure it's size and set the parent
616 // When the svg renders completely, measure it's size and set the parent
617 // div to that size. Then set the svg to 100% the size of the parent
617 // div to that size. Then set the svg to 100% the size of the parent
618 // div and make the parent div resizable.
618 // div and make the parent div resizable.
619 this._dblclick_to_reset_size(svg_area, true, false);
619 this._dblclick_to_reset_size(svg_area, true, false);
620
620
621 svg_area.append(svg);
621 svg_area.append(svg);
622 toinsert.append(svg_area);
622 toinsert.append(svg_area);
623 element.append(toinsert);
623 element.append(toinsert);
624
624
625 return toinsert;
625 return toinsert;
626 };
626 };
627
627
628 OutputArea.prototype._dblclick_to_reset_size = function (img, immediately, resize_parent) {
628 OutputArea.prototype._dblclick_to_reset_size = function (img, immediately, resize_parent) {
629 // Add a resize handler to an element
629 // Add a resize handler to an element
630 //
630 //
631 // img: jQuery element
631 // img: jQuery element
632 // immediately: bool=False
632 // immediately: bool=False
633 // Wait for the element to load before creating the handle.
633 // Wait for the element to load before creating the handle.
634 // resize_parent: bool=True
634 // resize_parent: bool=True
635 // Should the parent of the element be resized when the element is
635 // Should the parent of the element be resized when the element is
636 // reset (by double click).
636 // reset (by double click).
637 var callback = function (){
637 var callback = function (){
638 var h0 = img.height();
638 var h0 = img.height();
639 var w0 = img.width();
639 var w0 = img.width();
640 if (!(h0 && w0)) {
640 if (!(h0 && w0)) {
641 // zero size, don't make it resizable
641 // zero size, don't make it resizable
642 return;
642 return;
643 }
643 }
644 img.resizable({
644 img.resizable({
645 aspectRatio: true,
645 aspectRatio: true,
646 autoHide: true
646 autoHide: true
647 });
647 });
648 img.dblclick(function () {
648 img.dblclick(function () {
649 // resize wrapper & image together for some reason:
649 // resize wrapper & image together for some reason:
650 img.height(h0);
650 img.height(h0);
651 img.width(w0);
651 img.width(w0);
652 if (resize_parent === undefined || resize_parent) {
652 if (resize_parent === undefined || resize_parent) {
653 img.parent().height(h0);
653 img.parent().height(h0);
654 img.parent().width(w0);
654 img.parent().width(w0);
655 }
655 }
656 });
656 });
657 };
657 };
658
658
659 if (immediately) {
659 if (immediately) {
660 callback();
660 callback();
661 } else {
661 } else {
662 img.on("load", callback);
662 img.on("load", callback);
663 }
663 }
664 };
664 };
665
665
666 var set_width_height = function (img, md, mime) {
666 var set_width_height = function (img, md, mime) {
667 // set width and height of an img element from metadata
667 // set width and height of an img element from metadata
668 var height = _get_metadata_key(md, 'height', mime);
668 var height = _get_metadata_key(md, 'height', mime);
669 if (height !== undefined) img.attr('height', height);
669 if (height !== undefined) img.attr('height', height);
670 var width = _get_metadata_key(md, 'width', mime);
670 var width = _get_metadata_key(md, 'width', mime);
671 if (width !== undefined) img.attr('width', width);
671 if (width !== undefined) img.attr('width', width);
672 };
672 };
673
673
674 var append_png = function (png, md, element, handle_inserted) {
674 var append_png = function (png, md, element, handle_inserted) {
675 var type = 'image/png';
675 var type = 'image/png';
676 var toinsert = this.create_output_subarea(md, "output_png", type);
676 var toinsert = this.create_output_subarea(md, "output_png", type);
677 var img = $("<img/>");
677 var img = $("<img/>");
678 if (handle_inserted !== undefined) {
678 if (handle_inserted !== undefined) {
679 img.on('load', function(){
679 img.on('load', function(){
680 handle_inserted(img);
680 handle_inserted(img);
681 });
681 });
682 }
682 }
683 img[0].src = 'data:image/png;base64,'+ png;
683 img[0].src = 'data:image/png;base64,'+ png;
684 set_width_height(img, md, 'image/png');
684 set_width_height(img, md, 'image/png');
685 this._dblclick_to_reset_size(img);
685 this._dblclick_to_reset_size(img);
686 toinsert.append(img);
686 toinsert.append(img);
687 element.append(toinsert);
687 element.append(toinsert);
688 return toinsert;
688 return toinsert;
689 };
689 };
690
690
691
691
692 var append_jpeg = function (jpeg, md, element, handle_inserted) {
692 var append_jpeg = function (jpeg, md, element, handle_inserted) {
693 var type = 'image/jpeg';
693 var type = 'image/jpeg';
694 var toinsert = this.create_output_subarea(md, "output_jpeg", type);
694 var toinsert = this.create_output_subarea(md, "output_jpeg", type);
695 var img = $("<img/>");
695 var img = $("<img/>");
696 if (handle_inserted !== undefined) {
696 if (handle_inserted !== undefined) {
697 img.on('load', function(){
697 img.on('load', function(){
698 handle_inserted(img);
698 handle_inserted(img);
699 });
699 });
700 }
700 }
701 img[0].src = 'data:image/jpeg;base64,'+ jpeg;
701 img[0].src = 'data:image/jpeg;base64,'+ jpeg;
702 set_width_height(img, md, 'image/jpeg');
702 set_width_height(img, md, 'image/jpeg');
703 this._dblclick_to_reset_size(img);
703 this._dblclick_to_reset_size(img);
704 toinsert.append(img);
704 toinsert.append(img);
705 element.append(toinsert);
705 element.append(toinsert);
706 return toinsert;
706 return toinsert;
707 };
707 };
708
708
709
709
710 var append_pdf = function (pdf, md, element) {
710 var append_pdf = function (pdf, md, element) {
711 var type = 'application/pdf';
711 var type = 'application/pdf';
712 var toinsert = this.create_output_subarea(md, "output_pdf", type);
712 var toinsert = this.create_output_subarea(md, "output_pdf", type);
713 var a = $('<a/>').attr('href', 'data:application/pdf;base64,'+pdf);
713 var a = $('<a/>').attr('href', 'data:application/pdf;base64,'+pdf);
714 a.attr('target', '_blank');
714 a.attr('target', '_blank');
715 a.text('View PDF')
715 a.text('View PDF')
716 toinsert.append(a);
716 toinsert.append(a);
717 element.append(toinsert);
717 element.append(toinsert);
718 return toinsert;
718 return toinsert;
719 }
719 }
720
720
721 var append_latex = function (latex, md, element) {
721 var append_latex = function (latex, md, element) {
722 // This method cannot do the typesetting because the latex first has to
722 // This method cannot do the typesetting because the latex first has to
723 // be on the page.
723 // be on the page.
724 var type = 'text/latex';
724 var type = 'text/latex';
725 var toinsert = this.create_output_subarea(md, "output_latex", type);
725 var toinsert = this.create_output_subarea(md, "output_latex", type);
726 toinsert.append(latex);
726 toinsert.append(latex);
727 element.append(toinsert);
727 element.append(toinsert);
728 return toinsert;
728 return toinsert;
729 };
729 };
730
730
731
731
732 OutputArea.prototype.append_raw_input = function (msg) {
732 OutputArea.prototype.append_raw_input = function (msg) {
733 var that = this;
733 var that = this;
734 this.expand();
734 this.expand();
735 var content = msg.content;
735 var content = msg.content;
736 var area = this.create_output_area();
736 var area = this.create_output_area();
737
737
738 // disable any other raw_inputs, if they are left around
738 // disable any other raw_inputs, if they are left around
739 $("div.output_subarea.raw_input_container").remove();
739 $("div.output_subarea.raw_input_container").remove();
740
740
741 var input_type = content.password ? 'password' : 'text';
741 var input_type = content.password ? 'password' : 'text';
742
742
743 area.append(
743 area.append(
744 $("<div/>")
744 $("<div/>")
745 .addClass("box-flex1 output_subarea raw_input_container")
745 .addClass("box-flex1 output_subarea raw_input_container")
746 .append(
746 .append(
747 $("<span/>")
747 $("<span/>")
748 .addClass("raw_input_prompt")
748 .addClass("raw_input_prompt")
749 .text(content.prompt)
749 .text(content.prompt)
750 )
750 )
751 .append(
751 .append(
752 $("<input/>")
752 $("<input/>")
753 .addClass("raw_input")
753 .addClass("raw_input")
754 .attr('type', input_type)
754 .attr('type', input_type)
755 .attr("size", 47)
755 .attr("size", 47)
756 .keydown(function (event, ui) {
756 .keydown(function (event, ui) {
757 // make sure we submit on enter,
757 // make sure we submit on enter,
758 // and don't re-execute the *cell* on shift-enter
758 // and don't re-execute the *cell* on shift-enter
759 if (event.which === keyboard.keycodes.enter) {
759 if (event.which === keyboard.keycodes.enter) {
760 that._submit_raw_input();
760 that._submit_raw_input();
761 return false;
761 return false;
762 }
762 }
763 })
763 })
764 )
764 )
765 );
765 );
766
766
767 this.element.append(area);
767 this.element.append(area);
768 var raw_input = area.find('input.raw_input');
768 var raw_input = area.find('input.raw_input');
769 // Register events that enable/disable the keyboard manager while raw
769 // Register events that enable/disable the keyboard manager while raw
770 // input is focused.
770 // input is focused.
771 this.keyboard_manager.register_events(raw_input);
771 this.keyboard_manager.register_events(raw_input);
772 // Note, the following line used to read raw_input.focus().focus().
772 // Note, the following line used to read raw_input.focus().focus().
773 // This seemed to be needed otherwise only the cell would be focused.
773 // This seemed to be needed otherwise only the cell would be focused.
774 // But with the modal UI, this seems to work fine with one call to focus().
774 // But with the modal UI, this seems to work fine with one call to focus().
775 raw_input.focus();
775 raw_input.focus();
776 }
776 }
777
777
778 OutputArea.prototype._submit_raw_input = function (evt) {
778 OutputArea.prototype._submit_raw_input = function (evt) {
779 var container = this.element.find("div.raw_input_container");
779 var container = this.element.find("div.raw_input_container");
780 var theprompt = container.find("span.raw_input_prompt");
780 var theprompt = container.find("span.raw_input_prompt");
781 var theinput = container.find("input.raw_input");
781 var theinput = container.find("input.raw_input");
782 var value = theinput.val();
782 var value = theinput.val();
783 var echo = value;
783 var echo = value;
784 // don't echo if it's a password
784 // don't echo if it's a password
785 if (theinput.attr('type') == 'password') {
785 if (theinput.attr('type') == 'password') {
786 echo = 'Β·Β·Β·Β·Β·Β·Β·Β·';
786 echo = 'Β·Β·Β·Β·Β·Β·Β·Β·';
787 }
787 }
788 var content = {
788 var content = {
789 output_type : 'stream',
789 output_type : 'stream',
790 stream : 'stdout',
790 stream : 'stdout',
791 text : theprompt.text() + echo + '\n'
791 text : theprompt.text() + echo + '\n'
792 }
792 }
793 // remove form container
793 // remove form container
794 container.parent().remove();
794 container.parent().remove();
795 // replace with plaintext version in stdout
795 // replace with plaintext version in stdout
796 this.append_output(content, false);
796 this.append_output(content, false);
797 this.events.trigger('send_input_reply.Kernel', value);
797 this.events.trigger('send_input_reply.Kernel', value);
798 }
798 }
799
799
800
800
801 OutputArea.prototype.handle_clear_output = function (msg) {
801 OutputArea.prototype.handle_clear_output = function (msg) {
802 // msg spec v4 had stdout, stderr, display keys
802 // msg spec v4 had stdout, stderr, display keys
803 // v4.1 replaced these with just wait
803 // v4.1 replaced these with just wait
804 // The default behavior is the same (stdout=stderr=display=True, wait=False),
804 // The default behavior is the same (stdout=stderr=display=True, wait=False),
805 // so v4 messages will still be properly handled,
805 // so v4 messages will still be properly handled,
806 // except for the rarely used clearing less than all output.
806 // except for the rarely used clearing less than all output.
807 this.clear_output(msg.content.wait || false);
807 this.clear_output(msg.content.wait || false);
808 };
808 };
809
809
810
810
811 OutputArea.prototype.clear_output = function(wait) {
811 OutputArea.prototype.clear_output = function(wait) {
812 if (wait) {
812 if (wait) {
813
813
814 // If a clear is queued, clear before adding another to the queue.
814 // If a clear is queued, clear before adding another to the queue.
815 if (this.clear_queued) {
815 if (this.clear_queued) {
816 this.clear_output(false);
816 this.clear_output(false);
817 };
817 };
818
818
819 this.clear_queued = true;
819 this.clear_queued = true;
820 } else {
820 } else {
821
821
822 // Fix the output div's height if the clear_output is waiting for
822 // Fix the output div's height if the clear_output is waiting for
823 // new output (it is being used in an animation).
823 // new output (it is being used in an animation).
824 if (this.clear_queued) {
824 if (this.clear_queued) {
825 var height = this.element.height();
825 var height = this.element.height();
826 this.element.height(height);
826 this.element.height(height);
827 this.clear_queued = false;
827 this.clear_queued = false;
828 }
828 }
829
829
830 // Clear all
830 // Clear all
831 // Remove load event handlers from img tags because we don't want
831 // Remove load event handlers from img tags because we don't want
832 // them to fire if the image is never added to the page.
832 // them to fire if the image is never added to the page.
833 this.element.find('img').off('load');
833 this.element.find('img').off('load');
834 this.element.html("");
834 this.element.html("");
835 this.outputs = [];
835 this.outputs = [];
836 this.trusted = true;
836 this.trusted = true;
837 this.unscroll_area();
837 this.unscroll_area();
838 return;
838 return;
839 };
839 };
840 };
840 };
841
841
842
842
843 // JSON serialization
843 // JSON serialization
844
844
845 OutputArea.prototype.fromJSON = function (outputs) {
845 OutputArea.prototype.fromJSON = function (outputs) {
846 var len = outputs.length;
846 var len = outputs.length;
847 var data;
847 var data;
848
848
849 for (var i=0; i<len; i++) {
849 for (var i=0; i<len; i++) {
850 data = outputs[i];
850 data = outputs[i];
851 var msg_type = data.output_type;
851 var msg_type = data.output_type;
852 if (msg_type == "pyout") {
852 if (msg_type == "pyout") {
853 // pyout message has been renamed to execute_result,
853 // pyout message has been renamed to execute_result,
854 // but the nbformat has not been updated,
854 // but the nbformat has not been updated,
855 // so transform back to pyout for json.
855 // so transform back to pyout for json.
856 msg_type = data.output_type = "execute_result";
856 msg_type = data.output_type = "execute_result";
857 } else if (msg_type == "pyerr") {
857 } else if (msg_type == "pyerr") {
858 // pyerr message has been renamed to error,
858 // pyerr message has been renamed to error,
859 // but the nbformat has not been updated,
859 // but the nbformat has not been updated,
860 // so transform back to pyerr for json.
860 // so transform back to pyerr for json.
861 msg_type = data.output_type = "error";
861 msg_type = data.output_type = "error";
862 }
862 }
863 if (msg_type === "display_data" || msg_type === "execute_result") {
863 if (msg_type === "display_data" || msg_type === "execute_result") {
864 // convert short keys to mime keys
864 // convert short keys to mime keys
865 // TODO: remove mapping of short keys when we update to nbformat 4
865 // TODO: remove mapping of short keys when we update to nbformat 4
866 data = this.rename_keys(data, OutputArea.mime_map_r);
866 data = this.rename_keys(data, OutputArea.mime_map_r);
867 data.metadata = this.rename_keys(data.metadata, OutputArea.mime_map_r);
867 data.metadata = this.rename_keys(data.metadata, OutputArea.mime_map_r);
868 // msg spec JSON is an object, nbformat v3 JSON is a JSON string
868 // msg spec JSON is an object, nbformat v3 JSON is a JSON string
869 if (data["application/json"] !== undefined && typeof data["application/json"] === 'string') {
869 if (data["application/json"] !== undefined && typeof data["application/json"] === 'string') {
870 data["application/json"] = JSON.parse(data["application/json"]);
870 data["application/json"] = JSON.parse(data["application/json"]);
871 }
871 }
872 }
872 }
873
873
874 this.append_output(data);
874 this.append_output(data);
875 }
875 }
876 };
876 };
877
877
878
878
879 OutputArea.prototype.toJSON = function () {
879 OutputArea.prototype.toJSON = function () {
880 var outputs = [];
880 var outputs = [];
881 var len = this.outputs.length;
881 var len = this.outputs.length;
882 var data;
882 var data;
883 for (var i=0; i<len; i++) {
883 for (var i=0; i<len; i++) {
884 data = this.outputs[i];
884 data = this.outputs[i];
885 var msg_type = data.output_type;
885 var msg_type = data.output_type;
886 if (msg_type === "display_data" || msg_type === "execute_result") {
886 if (msg_type === "display_data" || msg_type === "execute_result") {
887 // convert mime keys to short keys
887 // convert mime keys to short keys
888 data = this.rename_keys(data, OutputArea.mime_map);
888 data = this.rename_keys(data, OutputArea.mime_map);
889 data.metadata = this.rename_keys(data.metadata, OutputArea.mime_map);
889 data.metadata = this.rename_keys(data.metadata, OutputArea.mime_map);
890 // msg spec JSON is an object, nbformat v3 JSON is a JSON string
890 // msg spec JSON is an object, nbformat v3 JSON is a JSON string
891 if (data.json !== undefined && typeof data.json !== 'string') {
891 if (data.json !== undefined && typeof data.json !== 'string') {
892 data.json = JSON.stringify(data.json);
892 data.json = JSON.stringify(data.json);
893 }
893 }
894 }
894 }
895 if (msg_type == "execute_result") {
895 if (msg_type == "execute_result") {
896 // pyout message has been renamed to execute_result,
896 // pyout message has been renamed to execute_result,
897 // but the nbformat has not been updated,
897 // but the nbformat has not been updated,
898 // so transform back to pyout for json.
898 // so transform back to pyout for json.
899 data.output_type = "pyout";
899 data.output_type = "pyout";
900 } else if (msg_type == "error") {
900 } else if (msg_type == "error") {
901 // pyerr message has been renamed to error,
901 // pyerr message has been renamed to error,
902 // but the nbformat has not been updated,
902 // but the nbformat has not been updated,
903 // so transform back to pyerr for json.
903 // so transform back to pyerr for json.
904 data.output_type = "pyerr";
904 data.output_type = "pyerr";
905 }
905 }
906 outputs[i] = data;
906 outputs[i] = data;
907 }
907 }
908 return outputs;
908 return outputs;
909 };
909 };
910
910
911 /**
911 /**
912 * Class properties
912 * Class properties
913 **/
913 **/
914
914
915 /**
915 /**
916 * Threshold to trigger autoscroll when the OutputArea is resized,
916 * Threshold to trigger autoscroll when the OutputArea is resized,
917 * typically when new outputs are added.
917 * typically when new outputs are added.
918 *
918 *
919 * Behavior is undefined if autoscroll is lower than minimum_scroll_threshold,
919 * Behavior is undefined if autoscroll is lower than minimum_scroll_threshold,
920 * unless it is < 0, in which case autoscroll will never be triggered
920 * unless it is < 0, in which case autoscroll will never be triggered
921 *
921 *
922 * @property auto_scroll_threshold
922 * @property auto_scroll_threshold
923 * @type Number
923 * @type Number
924 * @default 100
924 * @default 100
925 *
925 *
926 **/
926 **/
927 OutputArea.auto_scroll_threshold = 100;
927 OutputArea.auto_scroll_threshold = 100;
928
928
929 /**
929 /**
930 * Lower limit (in lines) for OutputArea to be made scrollable. OutputAreas
930 * Lower limit (in lines) for OutputArea to be made scrollable. OutputAreas
931 * shorter than this are never scrolled.
931 * shorter than this are never scrolled.
932 *
932 *
933 * @property minimum_scroll_threshold
933 * @property minimum_scroll_threshold
934 * @type Number
934 * @type Number
935 * @default 20
935 * @default 20
936 *
936 *
937 **/
937 **/
938 OutputArea.minimum_scroll_threshold = 20;
938 OutputArea.minimum_scroll_threshold = 20;
939
939
940
940
941
941
942 OutputArea.mime_map = {
942 OutputArea.mime_map = {
943 "text/plain" : "text",
943 "text/plain" : "text",
944 "text/html" : "html",
944 "text/html" : "html",
945 "image/svg+xml" : "svg",
945 "image/svg+xml" : "svg",
946 "image/png" : "png",
946 "image/png" : "png",
947 "image/jpeg" : "jpeg",
947 "image/jpeg" : "jpeg",
948 "text/latex" : "latex",
948 "text/latex" : "latex",
949 "application/json" : "json",
949 "application/json" : "json",
950 "application/javascript" : "javascript",
950 "application/javascript" : "javascript",
951 };
951 };
952
952
953 OutputArea.mime_map_r = {
953 OutputArea.mime_map_r = {
954 "text" : "text/plain",
954 "text" : "text/plain",
955 "html" : "text/html",
955 "html" : "text/html",
956 "svg" : "image/svg+xml",
956 "svg" : "image/svg+xml",
957 "png" : "image/png",
957 "png" : "image/png",
958 "jpeg" : "image/jpeg",
958 "jpeg" : "image/jpeg",
959 "latex" : "text/latex",
959 "latex" : "text/latex",
960 "json" : "application/json",
960 "json" : "application/json",
961 "javascript" : "application/javascript",
961 "javascript" : "application/javascript",
962 };
962 };
963
963
964 OutputArea.display_order = [
964 OutputArea.display_order = [
965 'application/javascript',
965 'application/javascript',
966 'text/html',
966 'text/html',
967 'text/markdown',
967 'text/markdown',
968 'text/latex',
968 'text/latex',
969 'image/svg+xml',
969 'image/svg+xml',
970 'image/png',
970 'image/png',
971 'image/jpeg',
971 'image/jpeg',
972 'application/pdf',
972 'application/pdf',
973 'text/plain'
973 'text/plain'
974 ];
974 ];
975
975
976 OutputArea.append_map = {
976 OutputArea.append_map = {
977 "text/plain" : append_text,
977 "text/plain" : append_text,
978 "text/html" : append_html,
978 "text/html" : append_html,
979 "text/markdown": append_markdown,
979 "text/markdown": append_markdown,
980 "image/svg+xml" : append_svg,
980 "image/svg+xml" : append_svg,
981 "image/png" : append_png,
981 "image/png" : append_png,
982 "image/jpeg" : append_jpeg,
982 "image/jpeg" : append_jpeg,
983 "text/latex" : append_latex,
983 "text/latex" : append_latex,
984 "application/javascript" : append_javascript,
984 "application/javascript" : append_javascript,
985 "application/pdf" : append_pdf
985 "application/pdf" : append_pdf
986 };
986 };
987
987
988 // For backwards compatability.
988 // For backwards compatability.
989 IPython.OutputArea = OutputArea;
989 IPython.OutputArea = OutputArea;
990
990
991 return {'OutputArea': OutputArea};
991 return {'OutputArea': OutputArea};
992 });
992 });
@@ -1,177 +1,177 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 define([
4 define([
5 'base/js/namespace',
5 'base/js/namespace',
6 'jquery',
6 'jquery',
7 'base/js/utils',
7 'base/js/utils',
8 'base/js/dialog',
8 'base/js/dialog',
9 'base/js/keyboard',
9 'base/js/keyboard',
10 'dateformat/date.format',
10 'dateformat',
11 ], function(IPython, $, utils, dialog, keyboard) {
11 ], function(IPython, $, utils, dialog, keyboard) {
12 "use strict";
12 "use strict";
13
13
14 var SaveWidget = function (selector, options) {
14 var SaveWidget = function (selector, options) {
15 // TODO: Remove circulat ref.
15 // TODO: Remove circulat ref.
16 this.notebook = undefined;
16 this.notebook = undefined;
17 this.selector = selector;
17 this.selector = selector;
18 this.events = options.events;
18 this.events = options.events;
19 this.keyboard_manager = options.keyboard_manager;
19 this.keyboard_manager = options.keyboard_manager;
20 if (this.selector !== undefined) {
20 if (this.selector !== undefined) {
21 this.element = $(selector);
21 this.element = $(selector);
22 this.style();
22 this.style();
23 this.bind_events();
23 this.bind_events();
24 }
24 }
25 };
25 };
26
26
27 SaveWidget.prototype.style = function () {
27 SaveWidget.prototype.style = function () {
28 };
28 };
29
29
30
30
31 SaveWidget.prototype.bind_events = function () {
31 SaveWidget.prototype.bind_events = function () {
32 var that = this;
32 var that = this;
33 this.element.find('span#notebook_name').click(function () {
33 this.element.find('span#notebook_name').click(function () {
34 that.rename_notebook();
34 that.rename_notebook();
35 });
35 });
36 this.element.find('span#notebook_name').hover(function () {
36 this.element.find('span#notebook_name').hover(function () {
37 $(this).addClass("ui-state-hover");
37 $(this).addClass("ui-state-hover");
38 }, function () {
38 }, function () {
39 $(this).removeClass("ui-state-hover");
39 $(this).removeClass("ui-state-hover");
40 });
40 });
41 this.events.on('notebook_loaded.Notebook', function () {
41 this.events.on('notebook_loaded.Notebook', function () {
42 that.update_notebook_name();
42 that.update_notebook_name();
43 that.update_document_title();
43 that.update_document_title();
44 });
44 });
45 this.events.on('notebook_saved.Notebook', function () {
45 this.events.on('notebook_saved.Notebook', function () {
46 that.update_notebook_name();
46 that.update_notebook_name();
47 that.update_document_title();
47 that.update_document_title();
48 });
48 });
49 this.events.on('notebook_renamed.Notebook', function () {
49 this.events.on('notebook_renamed.Notebook', function () {
50 that.update_notebook_name();
50 that.update_notebook_name();
51 that.update_document_title();
51 that.update_document_title();
52 that.update_address_bar();
52 that.update_address_bar();
53 });
53 });
54 this.events.on('notebook_save_failed.Notebook', function () {
54 this.events.on('notebook_save_failed.Notebook', function () {
55 that.set_save_status('Autosave Failed!');
55 that.set_save_status('Autosave Failed!');
56 });
56 });
57 this.events.on('checkpoints_listed.Notebook', function (event, data) {
57 this.events.on('checkpoints_listed.Notebook', function (event, data) {
58 that.set_last_checkpoint(data[0]);
58 that.set_last_checkpoint(data[0]);
59 });
59 });
60
60
61 this.events.on('checkpoint_created.Notebook', function (event, data) {
61 this.events.on('checkpoint_created.Notebook', function (event, data) {
62 that.set_last_checkpoint(data);
62 that.set_last_checkpoint(data);
63 });
63 });
64 this.events.on('set_dirty.Notebook', function (event, data) {
64 this.events.on('set_dirty.Notebook', function (event, data) {
65 that.set_autosaved(data.value);
65 that.set_autosaved(data.value);
66 });
66 });
67 };
67 };
68
68
69
69
70 SaveWidget.prototype.rename_notebook = function (options) {
70 SaveWidget.prototype.rename_notebook = function (options) {
71 options = options || {};
71 options = options || {};
72 var that = this;
72 var that = this;
73 var dialog_body = $('<div/>').append(
73 var dialog_body = $('<div/>').append(
74 $("<p/>").addClass("rename-message")
74 $("<p/>").addClass("rename-message")
75 .text('Enter a new notebook name:')
75 .text('Enter a new notebook name:')
76 ).append(
76 ).append(
77 $("<br/>")
77 $("<br/>")
78 ).append(
78 ).append(
79 $('<input/>').attr('type','text').attr('size','25').addClass('form-control')
79 $('<input/>').attr('type','text').attr('size','25').addClass('form-control')
80 .val(that.notebook.get_notebook_name())
80 .val(that.notebook.get_notebook_name())
81 );
81 );
82 dialog.modal({
82 dialog.modal({
83 title: "Rename Notebook",
83 title: "Rename Notebook",
84 body: dialog_body,
84 body: dialog_body,
85 notebook: options.notebook,
85 notebook: options.notebook,
86 keyboard_manager: this.keyboard_manager,
86 keyboard_manager: this.keyboard_manager,
87 buttons : {
87 buttons : {
88 "Cancel": {},
88 "Cancel": {},
89 "OK": {
89 "OK": {
90 class: "btn-primary",
90 class: "btn-primary",
91 click: function () {
91 click: function () {
92 var new_name = $(this).find('input').val();
92 var new_name = $(this).find('input').val();
93 if (!that.notebook.test_notebook_name(new_name)) {
93 if (!that.notebook.test_notebook_name(new_name)) {
94 $(this).find('.rename-message').text(
94 $(this).find('.rename-message').text(
95 "Invalid notebook name. Notebook names must "+
95 "Invalid notebook name. Notebook names must "+
96 "have 1 or more characters and can contain any characters " +
96 "have 1 or more characters and can contain any characters " +
97 "except :/\\. Please enter a new notebook name:"
97 "except :/\\. Please enter a new notebook name:"
98 );
98 );
99 return false;
99 return false;
100 } else {
100 } else {
101 that.notebook.rename(new_name);
101 that.notebook.rename(new_name);
102 }
102 }
103 }}
103 }}
104 },
104 },
105 open : function (event, ui) {
105 open : function (event, ui) {
106 var that = $(this);
106 var that = $(this);
107 // Upon ENTER, click the OK button.
107 // Upon ENTER, click the OK button.
108 that.find('input[type="text"]').keydown(function (event, ui) {
108 that.find('input[type="text"]').keydown(function (event, ui) {
109 if (event.which === keyboard.keycodes.enter) {
109 if (event.which === keyboard.keycodes.enter) {
110 that.find('.btn-primary').first().click();
110 that.find('.btn-primary').first().click();
111 return false;
111 return false;
112 }
112 }
113 });
113 });
114 that.find('input[type="text"]').focus().select();
114 that.find('input[type="text"]').focus().select();
115 }
115 }
116 });
116 });
117 };
117 };
118
118
119
119
120 SaveWidget.prototype.update_notebook_name = function () {
120 SaveWidget.prototype.update_notebook_name = function () {
121 var nbname = this.notebook.get_notebook_name();
121 var nbname = this.notebook.get_notebook_name();
122 this.element.find('span#notebook_name').text(nbname);
122 this.element.find('span#notebook_name').text(nbname);
123 };
123 };
124
124
125
125
126 SaveWidget.prototype.update_document_title = function () {
126 SaveWidget.prototype.update_document_title = function () {
127 var nbname = this.notebook.get_notebook_name();
127 var nbname = this.notebook.get_notebook_name();
128 document.title = nbname;
128 document.title = nbname;
129 };
129 };
130
130
131 SaveWidget.prototype.update_address_bar = function(){
131 SaveWidget.prototype.update_address_bar = function(){
132 var base_url = this.notebook.base_url;
132 var base_url = this.notebook.base_url;
133 var nbname = this.notebook.notebook_name;
133 var nbname = this.notebook.notebook_name;
134 var path = this.notebook.notebook_path;
134 var path = this.notebook.notebook_path;
135 var state = {path : path, name: nbname};
135 var state = {path : path, name: nbname};
136 window.history.replaceState(state, "", utils.url_join_encode(
136 window.history.replaceState(state, "", utils.url_join_encode(
137 base_url,
137 base_url,
138 "notebooks",
138 "notebooks",
139 path,
139 path,
140 nbname)
140 nbname)
141 );
141 );
142 };
142 };
143
143
144
144
145 SaveWidget.prototype.set_save_status = function (msg) {
145 SaveWidget.prototype.set_save_status = function (msg) {
146 this.element.find('span#autosave_status').text(msg);
146 this.element.find('span#autosave_status').text(msg);
147 };
147 };
148
148
149 SaveWidget.prototype.set_checkpoint_status = function (msg) {
149 SaveWidget.prototype.set_checkpoint_status = function (msg) {
150 this.element.find('span#checkpoint_status').text(msg);
150 this.element.find('span#checkpoint_status').text(msg);
151 };
151 };
152
152
153 SaveWidget.prototype.set_last_checkpoint = function (checkpoint) {
153 SaveWidget.prototype.set_last_checkpoint = function (checkpoint) {
154 if (!checkpoint) {
154 if (!checkpoint) {
155 this.set_checkpoint_status("");
155 this.set_checkpoint_status("");
156 return;
156 return;
157 }
157 }
158 var d = new Date(checkpoint.last_modified);
158 var d = new Date(checkpoint.last_modified);
159 this.set_checkpoint_status(
159 this.set_checkpoint_status(
160 "Last Checkpoint: " + d.format('mmm dd HH:MM')
160 "Last Checkpoint: " + d.format('mmm dd HH:MM')
161 );
161 );
162 };
162 };
163
163
164 SaveWidget.prototype.set_autosaved = function (dirty) {
164 SaveWidget.prototype.set_autosaved = function (dirty) {
165 if (dirty) {
165 if (dirty) {
166 this.set_save_status("(unsaved changes)");
166 this.set_save_status("(unsaved changes)");
167 } else {
167 } else {
168 this.set_save_status("(autosaved)");
168 this.set_save_status("(autosaved)");
169 }
169 }
170 };
170 };
171
171
172 // Backwards compatability.
172 // Backwards compatability.
173 IPython.SaveWidget = SaveWidget;
173 IPython.SaveWidget = SaveWidget;
174
174
175 return {'SaveWidget': SaveWidget};
175 return {'SaveWidget': SaveWidget};
176
176
177 });
177 });
@@ -1,461 +1,464 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 define([
4 define([
5 'base/js/namespace',
5 'base/js/namespace',
6 'jquery',
6 'jquery',
7 'notebook/js/cell',
7 'notebook/js/cell',
8 'base/js/security',
8 'base/js/security',
9 'notebook/js/mathjaxutils',
9 'notebook/js/mathjaxutils',
10 'notebook/js/celltoolbar',
10 'notebook/js/celltoolbar',
11 'components/marked/lib/marked',
11 'components/marked/lib/marked',
12 ], function(IPython, $, cell, security, mathjaxutils, celltoolbar, marked) {
12 ], function(IPython, $, cell, security, mathjaxutils, celltoolbar, marked) {
13 "use strict";
13 "use strict";
14 var Cell = cell.Cell;
14 var Cell = cell.Cell;
15
15
16 var TextCell = function (options) {
16 var TextCell = function (options) {
17 // Constructor
17 // Constructor
18 //
18 //
19 // Construct a new TextCell, codemirror mode is by default 'htmlmixed',
19 // Construct a new TextCell, codemirror mode is by default 'htmlmixed',
20 // and cell type is 'text' cell start as not redered.
20 // and cell type is 'text' cell start as not redered.
21 //
21 //
22 // Parameters:
22 // Parameters:
23 // options: dictionary
23 // options: dictionary
24 // Dictionary of keyword arguments.
24 // Dictionary of keyword arguments.
25 // events: $(Events) instance
25 // events: $(Events) instance
26 // config: dictionary
26 // config: dictionary
27 // keyboard_manager: KeyboardManager instance
27 // keyboard_manager: KeyboardManager instance
28 // notebook: Notebook instance
28 // notebook: Notebook instance
29 options = options || {};
29 options = options || {};
30
30
31 // in all TextCell/Cell subclasses
31 // in all TextCell/Cell subclasses
32 // do not assign most of members here, just pass it down
32 // do not assign most of members here, just pass it down
33 // in the options dict potentially overwriting what you wish.
33 // in the options dict potentially overwriting what you wish.
34 // they will be assigned in the base class.
34 // they will be assigned in the base class.
35 this.notebook = options.notebook;
35 this.notebook = options.notebook;
36 this.events = options.events;
36 this.events = options.events;
37 this.config = options.config;
37 this.config = options.config;
38
38
39 // we cannot put this as a class key as it has handle to "this".
39 // we cannot put this as a class key as it has handle to "this".
40 var cm_overwrite_options = {
40 var cm_overwrite_options = {
41 onKeyEvent: $.proxy(this.handle_keyevent,this)
41 onKeyEvent: $.proxy(this.handle_keyevent,this)
42 };
42 };
43 var config = this.mergeopt(TextCell, this.config, {cm_config:cm_overwrite_options});
43 var config = this.mergeopt(TextCell, this.config, {cm_config:cm_overwrite_options});
44 Cell.apply(this, [{
44 Cell.apply(this, [{
45 config: config,
45 config: config,
46 keyboard_manager: options.keyboard_manager,
46 keyboard_manager: options.keyboard_manager,
47 events: this.events}]);
47 events: this.events}]);
48
48
49 this.cell_type = this.cell_type || 'text';
49 this.cell_type = this.cell_type || 'text';
50 mathjaxutils = mathjaxutils;
50 mathjaxutils = mathjaxutils;
51 this.rendered = false;
51 this.rendered = false;
52 };
52 };
53
53
54 TextCell.prototype = new Cell();
54 TextCell.prototype = new Cell();
55
55
56 TextCell.options_default = {
56 TextCell.options_default = {
57 cm_config : {
57 cm_config : {
58 extraKeys: {"Tab": "indentMore","Shift-Tab" : "indentLess"},
58 extraKeys: {"Tab": "indentMore","Shift-Tab" : "indentLess"},
59 mode: 'htmlmixed',
59 mode: 'htmlmixed',
60 lineWrapping : true,
60 lineWrapping : true,
61 }
61 }
62 };
62 };
63
63
64
64
65 /**
65 /**
66 * Create the DOM element of the TextCell
66 * Create the DOM element of the TextCell
67 * @method create_element
67 * @method create_element
68 * @private
68 * @private
69 */
69 */
70 TextCell.prototype.create_element = function () {
70 TextCell.prototype.create_element = function () {
71 Cell.prototype.create_element.apply(this, arguments);
71 Cell.prototype.create_element.apply(this, arguments);
72
72
73 var cell = $("<div>").addClass('cell text_cell border-box-sizing');
73 var cell = $("<div>").addClass('cell text_cell border-box-sizing');
74 cell.attr('tabindex','2');
74 cell.attr('tabindex','2');
75
75
76 var prompt = $('<div/>').addClass('prompt input_prompt');
76 var prompt = $('<div/>').addClass('prompt input_prompt');
77 cell.append(prompt);
77 cell.append(prompt);
78 var inner_cell = $('<div/>').addClass('inner_cell');
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 inner_cell.append(this.celltoolbar.element);
83 inner_cell.append(this.celltoolbar.element);
81 var input_area = $('<div/>').addClass('input_area');
84 var input_area = $('<div/>').addClass('input_area');
82 this.code_mirror = new CodeMirror(input_area.get(0), this.cm_config);
85 this.code_mirror = new CodeMirror(input_area.get(0), this.cm_config);
83 // The tabindex=-1 makes this div focusable.
86 // The tabindex=-1 makes this div focusable.
84 var render_area = $('<div/>').addClass('text_cell_render border-box-sizing').
87 var render_area = $('<div/>').addClass('text_cell_render border-box-sizing').
85 addClass('rendered_html').attr('tabindex','-1');
88 addClass('rendered_html').attr('tabindex','-1');
86 inner_cell.append(input_area).append(render_area);
89 inner_cell.append(input_area).append(render_area);
87 cell.append(inner_cell);
90 cell.append(inner_cell);
88 this.element = cell;
91 this.element = cell;
89 };
92 };
90
93
91
94
92 /**
95 /**
93 * Bind the DOM evet to cell actions
96 * Bind the DOM evet to cell actions
94 * Need to be called after TextCell.create_element
97 * Need to be called after TextCell.create_element
95 * @private
98 * @private
96 * @method bind_event
99 * @method bind_event
97 */
100 */
98 TextCell.prototype.bind_events = function () {
101 TextCell.prototype.bind_events = function () {
99 Cell.prototype.bind_events.apply(this);
102 Cell.prototype.bind_events.apply(this);
100 var that = this;
103 var that = this;
101
104
102 this.element.dblclick(function () {
105 this.element.dblclick(function () {
103 if (that.selected === false) {
106 if (that.selected === false) {
104 this.events.trigger('select.Cell', {'cell':that});
107 this.events.trigger('select.Cell', {'cell':that});
105 }
108 }
106 var cont = that.unrender();
109 var cont = that.unrender();
107 if (cont) {
110 if (cont) {
108 that.focus_editor();
111 that.focus_editor();
109 }
112 }
110 });
113 });
111 };
114 };
112
115
113 // Cell level actions
116 // Cell level actions
114
117
115 TextCell.prototype.select = function () {
118 TextCell.prototype.select = function () {
116 var cont = Cell.prototype.select.apply(this);
119 var cont = Cell.prototype.select.apply(this);
117 if (cont) {
120 if (cont) {
118 if (this.mode === 'edit') {
121 if (this.mode === 'edit') {
119 this.code_mirror.refresh();
122 this.code_mirror.refresh();
120 }
123 }
121 }
124 }
122 return cont;
125 return cont;
123 };
126 };
124
127
125 TextCell.prototype.unrender = function () {
128 TextCell.prototype.unrender = function () {
126 if (this.read_only) return;
129 if (this.read_only) return;
127 var cont = Cell.prototype.unrender.apply(this);
130 var cont = Cell.prototype.unrender.apply(this);
128 if (cont) {
131 if (cont) {
129 var text_cell = this.element;
132 var text_cell = this.element;
130 var output = text_cell.find("div.text_cell_render");
133 var output = text_cell.find("div.text_cell_render");
131 output.hide();
134 output.hide();
132 text_cell.find('div.input_area').show();
135 text_cell.find('div.input_area').show();
133 if (this.get_text() === this.placeholder) {
136 if (this.get_text() === this.placeholder) {
134 this.set_text('');
137 this.set_text('');
135 }
138 }
136 this.refresh();
139 this.refresh();
137 }
140 }
138 if (this.celltoolbar.ui_controls_list.length) {
141 if (this.celltoolbar.ui_controls_list.length) {
139 this.celltoolbar.show();
142 this.celltoolbar.show();
140 }
143 }
141 return cont;
144 return cont;
142 };
145 };
143
146
144 TextCell.prototype.execute = function () {
147 TextCell.prototype.execute = function () {
145 this.render();
148 this.render();
146 };
149 };
147
150
148 /**
151 /**
149 * setter: {{#crossLink "TextCell/set_text"}}{{/crossLink}}
152 * setter: {{#crossLink "TextCell/set_text"}}{{/crossLink}}
150 * @method get_text
153 * @method get_text
151 * @retrun {string} CodeMirror current text value
154 * @retrun {string} CodeMirror current text value
152 */
155 */
153 TextCell.prototype.get_text = function() {
156 TextCell.prototype.get_text = function() {
154 return this.code_mirror.getValue();
157 return this.code_mirror.getValue();
155 };
158 };
156
159
157 /**
160 /**
158 * @param {string} text - Codemiror text value
161 * @param {string} text - Codemiror text value
159 * @see TextCell#get_text
162 * @see TextCell#get_text
160 * @method set_text
163 * @method set_text
161 * */
164 * */
162 TextCell.prototype.set_text = function(text) {
165 TextCell.prototype.set_text = function(text) {
163 this.code_mirror.setValue(text);
166 this.code_mirror.setValue(text);
164 this.code_mirror.refresh();
167 this.code_mirror.refresh();
165 };
168 };
166
169
167 /**
170 /**
168 * setter :{{#crossLink "TextCell/set_rendered"}}{{/crossLink}}
171 * setter :{{#crossLink "TextCell/set_rendered"}}{{/crossLink}}
169 * @method get_rendered
172 * @method get_rendered
170 * */
173 * */
171 TextCell.prototype.get_rendered = function() {
174 TextCell.prototype.get_rendered = function() {
172 return this.element.find('div.text_cell_render').html();
175 return this.element.find('div.text_cell_render').html();
173 };
176 };
174
177
175 /**
178 /**
176 * @method set_rendered
179 * @method set_rendered
177 */
180 */
178 TextCell.prototype.set_rendered = function(text) {
181 TextCell.prototype.set_rendered = function(text) {
179 this.element.find('div.text_cell_render').html(text);
182 this.element.find('div.text_cell_render').html(text);
180 this.celltoolbar.hide();
183 this.celltoolbar.hide();
181 };
184 };
182
185
183
186
184 /**
187 /**
185 * Create Text cell from JSON
188 * Create Text cell from JSON
186 * @param {json} data - JSON serialized text-cell
189 * @param {json} data - JSON serialized text-cell
187 * @method fromJSON
190 * @method fromJSON
188 */
191 */
189 TextCell.prototype.fromJSON = function (data) {
192 TextCell.prototype.fromJSON = function (data) {
190 Cell.prototype.fromJSON.apply(this, arguments);
193 Cell.prototype.fromJSON.apply(this, arguments);
191 if (data.cell_type === this.cell_type) {
194 if (data.cell_type === this.cell_type) {
192 if (data.source !== undefined) {
195 if (data.source !== undefined) {
193 this.set_text(data.source);
196 this.set_text(data.source);
194 // make this value the starting point, so that we can only undo
197 // make this value the starting point, so that we can only undo
195 // to this state, instead of a blank cell
198 // to this state, instead of a blank cell
196 this.code_mirror.clearHistory();
199 this.code_mirror.clearHistory();
197 // TODO: This HTML needs to be treated as potentially dangerous
200 // TODO: This HTML needs to be treated as potentially dangerous
198 // user input and should be handled before set_rendered.
201 // user input and should be handled before set_rendered.
199 this.set_rendered(data.rendered || '');
202 this.set_rendered(data.rendered || '');
200 this.rendered = false;
203 this.rendered = false;
201 this.render();
204 this.render();
202 }
205 }
203 }
206 }
204 };
207 };
205
208
206 /** Generate JSON from cell
209 /** Generate JSON from cell
207 * @return {object} cell data serialised to json
210 * @return {object} cell data serialised to json
208 */
211 */
209 TextCell.prototype.toJSON = function () {
212 TextCell.prototype.toJSON = function () {
210 var data = Cell.prototype.toJSON.apply(this);
213 var data = Cell.prototype.toJSON.apply(this);
211 data.source = this.get_text();
214 data.source = this.get_text();
212 if (data.source == this.placeholder) {
215 if (data.source == this.placeholder) {
213 data.source = "";
216 data.source = "";
214 }
217 }
215 return data;
218 return data;
216 };
219 };
217
220
218
221
219 var MarkdownCell = function (options) {
222 var MarkdownCell = function (options) {
220 // Constructor
223 // Constructor
221 //
224 //
222 // Parameters:
225 // Parameters:
223 // options: dictionary
226 // options: dictionary
224 // Dictionary of keyword arguments.
227 // Dictionary of keyword arguments.
225 // events: $(Events) instance
228 // events: $(Events) instance
226 // config: dictionary
229 // config: dictionary
227 // keyboard_manager: KeyboardManager instance
230 // keyboard_manager: KeyboardManager instance
228 // notebook: Notebook instance
231 // notebook: Notebook instance
229 options = options || {};
232 options = options || {};
230 var config = this.mergeopt(MarkdownCell, options.config);
233 var config = this.mergeopt(MarkdownCell, options.config);
231 TextCell.apply(this, [$.extend({}, options, {config: config})]);
234 TextCell.apply(this, [$.extend({}, options, {config: config})]);
232
235
233 this.cell_type = 'markdown';
236 this.cell_type = 'markdown';
234 };
237 };
235
238
236 MarkdownCell.options_default = {
239 MarkdownCell.options_default = {
237 cm_config: {
240 cm_config: {
238 mode: 'ipythongfm'
241 mode: 'ipythongfm'
239 },
242 },
240 placeholder: "Type *Markdown* and LaTeX: $\\alpha^2$"
243 placeholder: "Type *Markdown* and LaTeX: $\\alpha^2$"
241 };
244 };
242
245
243 MarkdownCell.prototype = new TextCell();
246 MarkdownCell.prototype = new TextCell();
244
247
245 /**
248 /**
246 * @method render
249 * @method render
247 */
250 */
248 MarkdownCell.prototype.render = function () {
251 MarkdownCell.prototype.render = function () {
249 var cont = TextCell.prototype.render.apply(this);
252 var cont = TextCell.prototype.render.apply(this);
250 if (cont) {
253 if (cont) {
251 var text = this.get_text();
254 var text = this.get_text();
252 var math = null;
255 var math = null;
253 if (text === "") { text = this.placeholder; }
256 if (text === "") { text = this.placeholder; }
254 var text_and_math = mathjaxutils.remove_math(text);
257 var text_and_math = mathjaxutils.remove_math(text);
255 text = text_and_math[0];
258 text = text_and_math[0];
256 math = text_and_math[1];
259 math = text_and_math[1];
257 var html = marked.parser(marked.lexer(text));
260 var html = marked.parser(marked.lexer(text));
258 html = mathjaxutils.replace_math(html, math);
261 html = mathjaxutils.replace_math(html, math);
259 html = security.sanitize_html(html);
262 html = security.sanitize_html(html);
260 html = $($.parseHTML(html));
263 html = $($.parseHTML(html));
261 // links in markdown cells should open in new tabs
264 // links in markdown cells should open in new tabs
262 html.find("a[href]").not('[href^="#"]').attr("target", "_blank");
265 html.find("a[href]").not('[href^="#"]').attr("target", "_blank");
263 this.set_rendered(html);
266 this.set_rendered(html);
264 this.element.find('div.input_area').hide();
267 this.element.find('div.input_area').hide();
265 this.element.find("div.text_cell_render").show();
268 this.element.find("div.text_cell_render").show();
266 this.typeset();
269 this.typeset();
267 }
270 }
268 return cont;
271 return cont;
269 };
272 };
270
273
271
274
272 var RawCell = function (options) {
275 var RawCell = function (options) {
273 // Constructor
276 // Constructor
274 //
277 //
275 // Parameters:
278 // Parameters:
276 // options: dictionary
279 // options: dictionary
277 // Dictionary of keyword arguments.
280 // Dictionary of keyword arguments.
278 // events: $(Events) instance
281 // events: $(Events) instance
279 // config: dictionary
282 // config: dictionary
280 // keyboard_manager: KeyboardManager instance
283 // keyboard_manager: KeyboardManager instance
281 // notebook: Notebook instance
284 // notebook: Notebook instance
282 options = options || {};
285 options = options || {};
283 var config = this.mergeopt(RawCell, options.config);
286 var config = this.mergeopt(RawCell, options.config);
284 TextCell.apply(this, [$.extend({}, options, {config: config})]);
287 TextCell.apply(this, [$.extend({}, options, {config: config})]);
285
288
286 // RawCell should always hide its rendered div
289 // RawCell should always hide its rendered div
287 this.element.find('div.text_cell_render').hide();
290 this.element.find('div.text_cell_render').hide();
288 this.cell_type = 'raw';
291 this.cell_type = 'raw';
289 };
292 };
290
293
291 RawCell.options_default = {
294 RawCell.options_default = {
292 placeholder : "Write raw LaTeX or other formats here, for use with nbconvert. " +
295 placeholder : "Write raw LaTeX or other formats here, for use with nbconvert. " +
293 "It will not be rendered in the notebook. " +
296 "It will not be rendered in the notebook. " +
294 "When passing through nbconvert, a Raw Cell's content is added to the output unmodified."
297 "When passing through nbconvert, a Raw Cell's content is added to the output unmodified."
295 };
298 };
296
299
297 RawCell.prototype = new TextCell();
300 RawCell.prototype = new TextCell();
298
301
299 /** @method bind_events **/
302 /** @method bind_events **/
300 RawCell.prototype.bind_events = function () {
303 RawCell.prototype.bind_events = function () {
301 TextCell.prototype.bind_events.apply(this);
304 TextCell.prototype.bind_events.apply(this);
302 var that = this;
305 var that = this;
303 this.element.focusout(function() {
306 this.element.focusout(function() {
304 that.auto_highlight();
307 that.auto_highlight();
305 that.render();
308 that.render();
306 });
309 });
307
310
308 this.code_mirror.on('focus', function() { that.unrender(); });
311 this.code_mirror.on('focus', function() { that.unrender(); });
309 };
312 };
310
313
311 /**
314 /**
312 * Trigger autodetection of highlight scheme for current cell
315 * Trigger autodetection of highlight scheme for current cell
313 * @method auto_highlight
316 * @method auto_highlight
314 */
317 */
315 RawCell.prototype.auto_highlight = function () {
318 RawCell.prototype.auto_highlight = function () {
316 this._auto_highlight(this.config.raw_cell_highlight);
319 this._auto_highlight(this.config.raw_cell_highlight);
317 };
320 };
318
321
319 /** @method render **/
322 /** @method render **/
320 RawCell.prototype.render = function () {
323 RawCell.prototype.render = function () {
321 var cont = TextCell.prototype.render.apply(this);
324 var cont = TextCell.prototype.render.apply(this);
322 if (cont){
325 if (cont){
323 var text = this.get_text();
326 var text = this.get_text();
324 if (text === "") { text = this.placeholder; }
327 if (text === "") { text = this.placeholder; }
325 this.set_text(text);
328 this.set_text(text);
326 this.element.removeClass('rendered');
329 this.element.removeClass('rendered');
327 }
330 }
328 return cont;
331 return cont;
329 };
332 };
330
333
331
334
332 var HeadingCell = function (options) {
335 var HeadingCell = function (options) {
333 // Constructor
336 // Constructor
334 //
337 //
335 // Parameters:
338 // Parameters:
336 // options: dictionary
339 // options: dictionary
337 // Dictionary of keyword arguments.
340 // Dictionary of keyword arguments.
338 // events: $(Events) instance
341 // events: $(Events) instance
339 // config: dictionary
342 // config: dictionary
340 // keyboard_manager: KeyboardManager instance
343 // keyboard_manager: KeyboardManager instance
341 // notebook: Notebook instance
344 // notebook: Notebook instance
342 options = options || {};
345 options = options || {};
343 var config = this.mergeopt(HeadingCell, options.config);
346 var config = this.mergeopt(HeadingCell, options.config);
344 TextCell.apply(this, [$.extend({}, options, {config: config})]);
347 TextCell.apply(this, [$.extend({}, options, {config: config})]);
345
348
346 this.level = 1;
349 this.level = 1;
347 this.cell_type = 'heading';
350 this.cell_type = 'heading';
348 };
351 };
349
352
350 HeadingCell.options_default = {
353 HeadingCell.options_default = {
351 placeholder: "Type Heading Here"
354 placeholder: "Type Heading Here"
352 };
355 };
353
356
354 HeadingCell.prototype = new TextCell();
357 HeadingCell.prototype = new TextCell();
355
358
356 /** @method fromJSON */
359 /** @method fromJSON */
357 HeadingCell.prototype.fromJSON = function (data) {
360 HeadingCell.prototype.fromJSON = function (data) {
358 if (data.level !== undefined){
361 if (data.level !== undefined){
359 this.level = data.level;
362 this.level = data.level;
360 }
363 }
361 TextCell.prototype.fromJSON.apply(this, arguments);
364 TextCell.prototype.fromJSON.apply(this, arguments);
362 };
365 };
363
366
364
367
365 /** @method toJSON */
368 /** @method toJSON */
366 HeadingCell.prototype.toJSON = function () {
369 HeadingCell.prototype.toJSON = function () {
367 var data = TextCell.prototype.toJSON.apply(this);
370 var data = TextCell.prototype.toJSON.apply(this);
368 data.level = this.get_level();
371 data.level = this.get_level();
369 return data;
372 return data;
370 };
373 };
371
374
372 /**
375 /**
373 * can the cell be split into two cells
376 * can the cell be split into two cells
374 * @method is_splittable
377 * @method is_splittable
375 **/
378 **/
376 HeadingCell.prototype.is_splittable = function () {
379 HeadingCell.prototype.is_splittable = function () {
377 return false;
380 return false;
378 };
381 };
379
382
380
383
381 /**
384 /**
382 * can the cell be merged with other cells
385 * can the cell be merged with other cells
383 * @method is_mergeable
386 * @method is_mergeable
384 **/
387 **/
385 HeadingCell.prototype.is_mergeable = function () {
388 HeadingCell.prototype.is_mergeable = function () {
386 return false;
389 return false;
387 };
390 };
388
391
389 /**
392 /**
390 * Change heading level of cell, and re-render
393 * Change heading level of cell, and re-render
391 * @method set_level
394 * @method set_level
392 */
395 */
393 HeadingCell.prototype.set_level = function (level) {
396 HeadingCell.prototype.set_level = function (level) {
394 this.level = level;
397 this.level = level;
395 if (this.rendered) {
398 if (this.rendered) {
396 this.rendered = false;
399 this.rendered = false;
397 this.render();
400 this.render();
398 }
401 }
399 };
402 };
400
403
401 /** The depth of header cell, based on html (h1 to h6)
404 /** The depth of header cell, based on html (h1 to h6)
402 * @method get_level
405 * @method get_level
403 * @return {integer} level - for 1 to 6
406 * @return {integer} level - for 1 to 6
404 */
407 */
405 HeadingCell.prototype.get_level = function () {
408 HeadingCell.prototype.get_level = function () {
406 return this.level;
409 return this.level;
407 };
410 };
408
411
409
412
410 HeadingCell.prototype.get_rendered = function () {
413 HeadingCell.prototype.get_rendered = function () {
411 var r = this.element.find("div.text_cell_render");
414 var r = this.element.find("div.text_cell_render");
412 return r.children().first().html();
415 return r.children().first().html();
413 };
416 };
414
417
415 HeadingCell.prototype.render = function () {
418 HeadingCell.prototype.render = function () {
416 var cont = TextCell.prototype.render.apply(this);
419 var cont = TextCell.prototype.render.apply(this);
417 if (cont) {
420 if (cont) {
418 var text = this.get_text();
421 var text = this.get_text();
419 var math = null;
422 var math = null;
420 // Markdown headings must be a single line
423 // Markdown headings must be a single line
421 text = text.replace(/\n/g, ' ');
424 text = text.replace(/\n/g, ' ');
422 if (text === "") { text = this.placeholder; }
425 if (text === "") { text = this.placeholder; }
423 text = new Array(this.level + 1).join("#") + " " + text;
426 text = new Array(this.level + 1).join("#") + " " + text;
424 var text_and_math = mathjaxutils.remove_math(text);
427 var text_and_math = mathjaxutils.remove_math(text);
425 text = text_and_math[0];
428 text = text_and_math[0];
426 math = text_and_math[1];
429 math = text_and_math[1];
427 var html = marked.parser(marked.lexer(text));
430 var html = marked.parser(marked.lexer(text));
428 html = mathjaxutils.replace_math(html, math);
431 html = mathjaxutils.replace_math(html, math);
429 html = security.sanitize_html(html);
432 html = security.sanitize_html(html);
430 var h = $($.parseHTML(html));
433 var h = $($.parseHTML(html));
431 // add id and linkback anchor
434 // add id and linkback anchor
432 var hash = h.text().replace(/ /g, '-');
435 var hash = h.text().replace(/ /g, '-');
433 h.attr('id', hash);
436 h.attr('id', hash);
434 h.append(
437 h.append(
435 $('<a/>')
438 $('<a/>')
436 .addClass('anchor-link')
439 .addClass('anchor-link')
437 .attr('href', '#' + hash)
440 .attr('href', '#' + hash)
438 .text('ΒΆ')
441 .text('ΒΆ')
439 );
442 );
440 this.set_rendered(h);
443 this.set_rendered(h);
441 this.element.find('div.input_area').hide();
444 this.element.find('div.input_area').hide();
442 this.element.find("div.text_cell_render").show();
445 this.element.find("div.text_cell_render").show();
443 this.typeset();
446 this.typeset();
444 }
447 }
445 return cont;
448 return cont;
446 };
449 };
447
450
448 // Backwards compatability.
451 // Backwards compatability.
449 IPython.TextCell = TextCell;
452 IPython.TextCell = TextCell;
450 IPython.MarkdownCell = MarkdownCell;
453 IPython.MarkdownCell = MarkdownCell;
451 IPython.RawCell = RawCell;
454 IPython.RawCell = RawCell;
452 IPython.HeadingCell = HeadingCell;
455 IPython.HeadingCell = HeadingCell;
453
456
454 var Cells = {
457 var textcell = {
455 'TextCell': TextCell,
458 'TextCell': TextCell,
456 'MarkdownCell': MarkdownCell,
459 'MarkdownCell': MarkdownCell,
457 'RawCell': RawCell,
460 'RawCell': RawCell,
458 'HeadingCell': HeadingCell,
461 'HeadingCell': HeadingCell,
459 };
462 };
460 return Cells;
463 return textcell;
461 });
464 });
@@ -1,91 +1,95 b''
1 <!DOCTYPE HTML>
1 <!DOCTYPE HTML>
2 <html>
2 <html>
3
3
4 <head>
4 <head>
5 <meta charset="utf-8">
5 <meta charset="utf-8">
6
6
7 <title>{% block title %}IPython Notebook{% endblock %}</title>
7 <title>{% block title %}IPython Notebook{% endblock %}</title>
8 <link rel="shortcut icon" type="image/x-icon" href="{{static_url("base/images/favicon.ico") }}">
8 <link rel="shortcut icon" type="image/x-icon" href="{{static_url("base/images/favicon.ico") }}">
9 <meta http-equiv="X-UA-Compatible" content="chrome=1">
9 <meta http-equiv="X-UA-Compatible" content="chrome=1">
10 <link rel="stylesheet" href="{{static_url("components/jquery-ui/themes/smoothness/jquery-ui.min.css") }}" type="text/css" />
10 <link rel="stylesheet" href="{{static_url("components/jquery-ui/themes/smoothness/jquery-ui.min.css") }}" type="text/css" />
11 <meta name="viewport" content="width=device-width, initial-scale=1.0">
11 <meta name="viewport" content="width=device-width, initial-scale=1.0">
12
12
13 {% block stylesheet %}
13 {% block stylesheet %}
14 <link rel="stylesheet" href="{{ static_url("style/style.min.css") }}" type="text/css"/>
14 <link rel="stylesheet" href="{{ static_url("style/style.min.css") }}" type="text/css"/>
15 {% endblock %}
15 {% endblock %}
16 <link rel="stylesheet" href="{{ static_url("custom/custom.css") }}" type="text/css" />
16 <link rel="stylesheet" href="{{ static_url("custom/custom.css") }}" type="text/css" />
17 <script src="{{static_url("components/requirejs/require.js") }}" type="text/javascript" charset="utf-8"></script>
17 <script src="{{static_url("components/requirejs/require.js") }}" type="text/javascript" charset="utf-8"></script>
18 <script>
18 <script>
19 require.config({
19 require.config({
20 baseUrl: '{{static_url("", include_version=False)}}',
20 baseUrl: '{{static_url("", include_version=False)}}',
21 paths: {
21 paths: {
22 nbextensions : '{{ base_url }}nbextensions',
22 nbextensions : '{{ base_url }}nbextensions',
23 underscore : 'components/underscore/underscore-min',
23 underscore : 'components/underscore/underscore-min',
24 backbone : 'components/backbone/backbone-min',
24 backbone : 'components/backbone/backbone-min',
25 jquery: 'components/jquery/jquery.min',
25 jquery: 'components/jquery/jquery.min',
26 bootstraptour: 'components/bootstrap-tour/build/js/bootstrap-tour.min',
26 bootstraptour: 'components/bootstrap-tour/build/js/bootstrap-tour.min',
27 dateformat: 'dateformat/date.format',
27 },
28 },
28 shim: {
29 shim: {
29 underscore: {
30 underscore: {
30 exports: '_'
31 exports: '_'
31 },
32 },
32 backbone: {
33 backbone: {
33 deps: ["underscore", "jquery"],
34 deps: ["underscore", "jquery"],
34 exports: "Backbone"
35 exports: "Backbone"
35 },
36 },
36 bootstraptour: {
37 bootstraptour: {
37 exports: "Tour"
38 exports: "Tour"
39 },
40 dateformat: {
41 exports: "dateFormat"
38 }
42 }
39 }
43 }
40 });
44 });
41 </script>
45 </script>
42
46
43 {% block meta %}
47 {% block meta %}
44 {% endblock %}
48 {% endblock %}
45
49
46 </head>
50 </head>
47
51
48 <body {% block params %}{% endblock %}>
52 <body {% block params %}{% endblock %}>
49
53
50 <noscript>
54 <noscript>
51 <div id='noscript'>
55 <div id='noscript'>
52 IPython Notebook requires JavaScript.<br>
56 IPython Notebook requires JavaScript.<br>
53 Please enable it to proceed.
57 Please enable it to proceed.
54 </div>
58 </div>
55 </noscript>
59 </noscript>
56
60
57 <div id="header" class="navbar navbar-static-top">
61 <div id="header" class="navbar navbar-static-top">
58 <div class="container">
62 <div class="container">
59 <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>
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 {% block login_widget %}
65 {% block login_widget %}
62
66
63 <span id="login_widget">
67 <span id="login_widget">
64 {% if logged_in %}
68 {% if logged_in %}
65 <button id="logout">Logout</button>
69 <button id="logout">Logout</button>
66 {% elif login_available and not logged_in %}
70 {% elif login_available and not logged_in %}
67 <button id="login">Login</button>
71 <button id="login">Login</button>
68 {% endif %}
72 {% endif %}
69 </span>
73 </span>
70
74
71 {% endblock %}
75 {% endblock %}
72
76
73 {% block header %}
77 {% block header %}
74 {% endblock %}
78 {% endblock %}
75 </div>
79 </div>
76 </div>
80 </div>
77
81
78 <div id="site">
82 <div id="site">
79 {% block site %}
83 {% block site %}
80 {% endblock %}
84 {% endblock %}
81 </div>
85 </div>
82
86
83 {% block script %}
87 {% block script %}
84 <script src="{{static_url("base/js/pagemain.js") }}" type="text/javascript" charset="utf-8"></script>
88 <script src="{{static_url("base/js/pagemain.js") }}" type="text/javascript" charset="utf-8"></script>
85 {% endblock %}
89 {% endblock %}
86
90
87 <script src="{{static_url("custom/custom.js") }}" type="text/javascript" charset="utf-8"></script>
91 <script src="{{static_url("custom/custom.js") }}" type="text/javascript" charset="utf-8"></script>
88
92
89 </body>
93 </body>
90
94
91 </html>
95 </html>
General Comments 0
You need to be logged in to leave comments. Login now