##// END OF EJS Templates
Return dicts instead of classes,...
Jonathan Frederic -
Show More
@@ -1,10 +1,10 b''
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 4 var ipython = ipython || {};
5 require(['base/js/page'], function(Page) {
6 ipython.page = new Page();
5 require(['base/js/page'], function(page) {
6 ipython.page = new page.Page();
7 7 $('button#login_submit').addClass("btn btn-default");
8 8 ipython.page.show();
9 9 $('input#password_input').focus();
10 10 });
@@ -1,47 +1,47 b''
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 4 define([
5 5 'base/js/namespace',
6 6 'base/js/utils',
7 7 'jquery',
8 8 ], function(IPython, utils, $){
9 9 "use strict";
10 10
11 11 var LoginWidget = function (selector, options) {
12 12 options = options || {};
13 13 this.base_url = options.base_url || utils.get_body_data("baseUrl");
14 14 this.selector = selector;
15 15 if (this.selector !== undefined) {
16 16 this.element = $(selector);
17 17 this.style();
18 18 this.bind_events();
19 19 }
20 20 };
21 21
22 22 LoginWidget.prototype.style = function () {
23 23 this.element.find("button").addClass("btn btn-default btn-sm");
24 24 };
25 25
26 26
27 27 LoginWidget.prototype.bind_events = function () {
28 28 var that = this;
29 29 this.element.find("button#logout").click(function () {
30 30 window.location = utils.url_join_encode(
31 31 that.base_url,
32 32 "logout"
33 33 );
34 34 });
35 35 this.element.find("button#login").click(function () {
36 36 window.location = utils.url_join_encode(
37 37 that.base_url,
38 38 "login"
39 39 );
40 40 });
41 41 };
42 42
43 43 // Set module variables
44 44 IPython.LoginWidget = LoginWidget;
45 45
46 return LoginWidget;
46 return {'LoginWidget': LoginWidget};
47 47 }); No newline at end of file
@@ -1,9 +1,9 b''
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 4 var ipython = ipython || {};
5 5 require(['base/js/page'], function(Page) {
6 ipython.page = new Page();
6 ipython.page = new page.Page();
7 7 $('#ipython-main-app').addClass('border-box-sizing');
8 8 ipython.page.show();
9 9 });
@@ -1,158 +1,158 b''
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 4 define([
5 5 'base/js/namespace',
6 6 'jquery',
7 7 ], function(IPython, $) {
8 8 "use strict";
9 9
10 10 var modal = function (options, keyboard_manager, notebook) {
11 11 var modal = $("<div/>")
12 12 .addClass("modal")
13 13 .addClass("fade")
14 14 .attr("role", "dialog");
15 15 var dialog = $("<div/>")
16 16 .addClass("modal-dialog")
17 17 .appendTo(modal);
18 18 var dialog_content = $("<div/>")
19 19 .addClass("modal-content")
20 20 .appendTo(dialog);
21 21 dialog_content.append(
22 22 $("<div/>")
23 23 .addClass("modal-header")
24 24 .append($("<button>")
25 25 .attr("type", "button")
26 26 .addClass("close")
27 27 .attr("data-dismiss", "modal")
28 28 .attr("aria-hidden", "true")
29 29 .html("&times;")
30 30 ).append(
31 31 $("<h4/>")
32 32 .addClass('modal-title')
33 33 .text(options.title || "")
34 34 )
35 35 ).append(
36 36 $("<div/>").addClass("modal-body").append(
37 37 options.body || $("<p/>")
38 38 )
39 39 );
40 40
41 41 var footer = $("<div/>").addClass("modal-footer");
42 42
43 43 for (var label in options.buttons) {
44 44 var btn_opts = options.buttons[label];
45 45 var button = $("<button/>")
46 46 .addClass("btn btn-default btn-sm")
47 47 .attr("data-dismiss", "modal")
48 48 .text(label);
49 49 if (btn_opts.click) {
50 50 button.click($.proxy(btn_opts.click, dialog_content));
51 51 }
52 52 if (btn_opts.class) {
53 53 button.addClass(btn_opts.class);
54 54 }
55 55 footer.append(button);
56 56 }
57 57 dialog_content.append(footer);
58 58 // hook up on-open event
59 59 modal.on("shown.bs.modal", function() {
60 60 setTimeout(function() {
61 61 footer.find("button").last().focus();
62 62 if (options.open) {
63 63 $.proxy(options.open, modal)();
64 64 }
65 65 }, 0);
66 66 });
67 67
68 68 // destroy modal on hide, unless explicitly asked not to
69 69 if (options.destroy === undefined || options.destroy) {
70 70 modal.on("hidden.bs.modal", function () {
71 71 modal.remove();
72 72 });
73 73 }
74 74 modal.on("hidden.bs.modal", function () {
75 75 if (notebook) {
76 76 var cell = notebook.get_selected_cell();
77 77 if (cell) cell.select();
78 78 keyboard_manager.enable();
79 79 keyboard_manager.command_mode();
80 80 }
81 81 });
82 82
83 83 if (keyboard_manager) {
84 84 keyboard_manager.disable();
85 85 }
86 86
87 87 return modal.modal(options);
88 88 };
89 89
90 90 var edit_metadata = function (md, callback, name, keyboard_manager, notebook) {
91 91 name = name || "Cell";
92 92 var error_div = $('<div/>').css('color', 'red');
93 93 var message =
94 94 "Manually edit the JSON below to manipulate the metadata for this " + name + "." +
95 95 " We recommend putting custom metadata attributes in an appropriately named sub-structure," +
96 96 " so they don't conflict with those of others.";
97 97
98 98 var textarea = $('<textarea/>')
99 99 .attr('rows', '13')
100 100 .attr('cols', '80')
101 101 .attr('name', 'metadata')
102 102 .text(JSON.stringify(md || {}, null, 2));
103 103
104 104 var dialogform = $('<div/>').attr('title', 'Edit the metadata')
105 105 .append(
106 106 $('<form/>').append(
107 107 $('<fieldset/>').append(
108 108 $('<label/>')
109 109 .attr('for','metadata')
110 110 .text(message)
111 111 )
112 112 .append(error_div)
113 113 .append($('<br/>'))
114 114 .append(textarea)
115 115 )
116 116 );
117 117 var editor = CodeMirror.fromTextArea(textarea[0], {
118 118 lineNumbers: true,
119 119 matchBrackets: true,
120 120 indentUnit: 2,
121 121 autoIndent: true,
122 122 mode: 'application/json',
123 123 });
124 124 var modal = modal({
125 125 title: "Edit " + name + " Metadata",
126 126 body: dialogform,
127 127 buttons: {
128 128 OK: { class : "btn-primary",
129 129 click: function() {
130 130 // validate json and set it
131 131 var new_md;
132 132 try {
133 133 new_md = JSON.parse(editor.getValue());
134 134 } catch(e) {
135 135 console.log(e);
136 136 error_div.text('WARNING: Could not save invalid JSON.');
137 137 return false;
138 138 }
139 139 callback(new_md);
140 140 }
141 141 },
142 142 Cancel: {}
143 143 }
144 144 }, keyboard_manager, notebook);
145 145
146 146 modal.on('shown.bs.modal', function(){ editor.refresh(); });
147 147 };
148 148
149 149 var Dialog = {
150 150 modal : modal,
151 151 edit_metadata : edit_metadata,
152 152 };
153 153
154 154 // Backwards compatability.
155 155 IPython.Dialog = Dialog;
156 156
157 return Dialog;
157 return {'Dialog': Dialog};
158 158 });
@@ -1,19 +1,19 b''
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 4 // Give us an object to bind all events to. This object should be created
5 5 // before all other objects so it exists when others register event handlers.
6 6 // To trigger an event handler:
7 7 // $([IPython.events]).trigger('event.Namespace');
8 8 // To handle it:
9 9 // $([IPython.events]).on('event.Namespace',function () {});
10 10 define(['base/js/namespace'], function(IPython) {
11 11 "use strict";
12 12
13 13 var Events = function () {};
14 14
15 15 // Backwards compatability.
16 16 IPython.Events = Events;
17 17
18 return Events;
18 return {'Events': Events};
19 19 });
@@ -1,48 +1,48 b''
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 4 define([
5 5 'base/js/namespace',
6 6 'jquery',
7 7 'components/jquery-ui/ui/minified/jquery-ui.min',
8 8 'components/bootstrap/js/bootstrap.min',
9 9 'auth/js/loginwidget'
10 10 ], function(IPython, $){
11 11 "use strict";
12 12
13 13 var Page = function () {
14 14 this.style();
15 15 this.bind_events();
16 16 };
17 17
18 18 Page.prototype.style = function () {
19 19 $('div#header').addClass('border-box-sizing');
20 20 $('div#site').addClass('border-box-sizing');
21 21 };
22 22
23 23 Page.prototype.bind_events = function () {
24 24 };
25 25
26 26 Page.prototype.show = function () {
27 27 // The header and site divs start out hidden to prevent FLOUC.
28 28 // Main scripts should call this method after styling everything.
29 29 this.show_header();
30 30 this.show_site();
31 31 };
32 32
33 33 Page.prototype.show_header = function () {
34 34 // The header and site divs start out hidden to prevent FLOUC.
35 35 // Main scripts should call this method after styling everything.
36 36 $('div#header').css('display','block');
37 37 };
38 38
39 39 Page.prototype.show_site = function () {
40 40 // The header and site divs start out hidden to prevent FLOUC.
41 41 // Main scripts should call this method after styling everything.
42 42 $('div#site').css('display','block');
43 43 };
44 44
45 45 // Register self in the global namespace for convenience.
46 46 IPython.Page = Page;
47 return Page;
47 return {'Page': Page};
48 48 });
@@ -1,8 +1,8 b''
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 4 var ipython = ipython || {};
5 5 require(['base/js/page'], function(Page) {
6 ipython.page = new Page();
6 ipython.page = new page.Page();
7 7 ipython.page.show();
8 8 });
@@ -1,132 +1,132 b''
1 1 /*
2 2 * Date Format 1.2.3
3 3 * (c) 2007-2009 Steven Levithan <stevenlevithan.com>
4 4 * MIT license
5 5 *
6 6 * Includes enhancements by Scott Trenda <scott.trenda.net>
7 7 * and Kris Kowal <cixar.com/~kris.kowal/>
8 8 *
9 9 * Accepts a date, a mask, or a date and a mask.
10 10 * Returns a formatted version of the given date.
11 11 * The date defaults to the current date/time.
12 12 * The mask defaults to dateFormat.masks.default.
13 13 */
14 14 // Copyright (c) IPython Development Team.
15 15 // Distributed under the terms of the Modified BSD License.
16 16
17 17 // Require.js define call added by IPython team.
18 18 define([], function() {
19 19 var dateFormat = function () {
20 20 var token = /d{1,4}|m{1,4}|yy(?:yy)?|([HhMsTt])\1?|[LloSZ]|"[^"]*"|'[^']*'/g,
21 21 timezone = /\b(?:[PMCEA][SDP]T|(?:Pacific|Mountain|Central|Eastern|Atlantic) (?:Standard|Daylight|Prevailing) Time|(?:GMT|UTC)(?:[-+]\d{4})?)\b/g,
22 22 timezoneClip = /[^-+\dA-Z]/g,
23 23 pad = function (val, len) {
24 24 val = String(val);
25 25 len = len || 2;
26 26 while (val.length < len) val = "0" + val;
27 27 return val;
28 28 };
29 29
30 30 // Regexes and supporting functions are cached through closure
31 31 return function (date, mask, utc) {
32 32 var dF = dateFormat;
33 33
34 34 // You can't provide utc if you skip other args (use the "UTC:" mask prefix)
35 35 if (arguments.length == 1 && Object.prototype.toString.call(date) == "[object String]" && !/\d/.test(date)) {
36 36 mask = date;
37 37 date = undefined;
38 38 }
39 39
40 40 // Passing date through Date applies Date.parse, if necessary
41 41 date = date ? new Date(date) : new Date;
42 42 if (isNaN(date)) throw SyntaxError("invalid date");
43 43
44 44 mask = String(dF.masks[mask] || mask || dF.masks["default"]);
45 45
46 46 // Allow setting the utc argument via the mask
47 47 if (mask.slice(0, 4) == "UTC:") {
48 48 mask = mask.slice(4);
49 49 utc = true;
50 50 }
51 51
52 52 var _ = utc ? "getUTC" : "get",
53 53 d = date[_ + "Date"](),
54 54 D = date[_ + "Day"](),
55 55 m = date[_ + "Month"](),
56 56 y = date[_ + "FullYear"](),
57 57 H = date[_ + "Hours"](),
58 58 M = date[_ + "Minutes"](),
59 59 s = date[_ + "Seconds"](),
60 60 L = date[_ + "Milliseconds"](),
61 61 o = utc ? 0 : date.getTimezoneOffset(),
62 62 flags = {
63 63 d: d,
64 64 dd: pad(d),
65 65 ddd: dF.i18n.dayNames[D],
66 66 dddd: dF.i18n.dayNames[D + 7],
67 67 m: m + 1,
68 68 mm: pad(m + 1),
69 69 mmm: dF.i18n.monthNames[m],
70 70 mmmm: dF.i18n.monthNames[m + 12],
71 71 yy: String(y).slice(2),
72 72 yyyy: y,
73 73 h: H % 12 || 12,
74 74 hh: pad(H % 12 || 12),
75 75 H: H,
76 76 HH: pad(H),
77 77 M: M,
78 78 MM: pad(M),
79 79 s: s,
80 80 ss: pad(s),
81 81 l: pad(L, 3),
82 82 L: pad(L > 99 ? Math.round(L / 10) : L),
83 83 t: H < 12 ? "a" : "p",
84 84 tt: H < 12 ? "am" : "pm",
85 85 T: H < 12 ? "A" : "P",
86 86 TT: H < 12 ? "AM" : "PM",
87 87 Z: utc ? "UTC" : (String(date).match(timezone) || [""]).pop().replace(timezoneClip, ""),
88 88 o: (o > 0 ? "-" : "+") + pad(Math.floor(Math.abs(o) / 60) * 100 + Math.abs(o) % 60, 4),
89 89 S: ["th", "st", "nd", "rd"][d % 10 > 3 ? 0 : (d % 100 - d % 10 != 10) * d % 10]
90 90 };
91 91
92 92 return mask.replace(token, function ($0) {
93 93 return $0 in flags ? flags[$0] : $0.slice(1, $0.length - 1);
94 94 });
95 95 };
96 96 }();
97 97
98 98 // Some common format strings
99 99 dateFormat.masks = {
100 100 "default": "ddd mmm dd yyyy HH:MM:ss",
101 101 shortDate: "m/d/yy",
102 102 mediumDate: "mmm d, yyyy",
103 103 longDate: "mmmm d, yyyy",
104 104 fullDate: "dddd, mmmm d, yyyy",
105 105 shortTime: "h:MM TT",
106 106 mediumTime: "h:MM:ss TT",
107 107 longTime: "h:MM:ss TT Z",
108 108 isoDate: "yyyy-mm-dd",
109 109 isoTime: "HH:MM:ss",
110 110 isoDateTime: "yyyy-mm-dd'T'HH:MM:ss",
111 111 isoUtcDateTime: "UTC:yyyy-mm-dd'T'HH:MM:ss'Z'"
112 112 };
113 113
114 114 // Internationalization strings
115 115 dateFormat.i18n = {
116 116 dayNames: [
117 117 "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat",
118 118 "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"
119 119 ],
120 120 monthNames: [
121 121 "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
122 122 "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"
123 123 ]
124 124 };
125 125
126 126 // For convenience...
127 127 Date.prototype.format = function (mask, utc) {
128 128 return dateFormat(this, mask, utc);
129 129 };
130 130
131 return dateFormat;
131 return {'dateFormat': dateFormat};
132 132 }); No newline at end of file
@@ -1,564 +1,564 b''
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 4 define([
5 5 'base/js/namespace',
6 6 'jquery',
7 7 'base/js/utils',
8 8 ], function(IPython, $, utils) {
9 9 "use strict";
10 10
11 11 // monkey patch CM to be able to syntax highlight cell magics
12 12 // bug reported upstream,
13 13 // see https://github.com/marijnh/CodeMirror2/issues/670
14 14 if(CodeMirror.getMode(1,'text/plain').indent === undefined ){
15 15 CodeMirror.modes.null = function() {
16 16 return {token: function(stream) {stream.skipToEnd();},indent : function(){return 0;}};
17 17 };
18 18 }
19 19
20 20 CodeMirror.patchedGetMode = function(config, mode){
21 21 var cmmode = CodeMirror.getMode(config, mode);
22 22 if(cmmode.indent === null) {
23 23 console.log('patch mode "' , mode, '" on the fly');
24 24 cmmode.indent = function(){return 0;};
25 25 }
26 26 return cmmode;
27 27 };
28 28 // end monkey patching CodeMirror
29 29
30 30 /**
31 31 * The Base `Cell` class from which to inherit
32 32 * @class Cell
33 33 **/
34 34
35 35 /*
36 36 * @constructor
37 37 *
38 38 * @param {object|undefined} [options]
39 39 * @param [options.cm_config] {object} config to pass to CodeMirror, will extend default parameters
40 40 */
41 41 var Cell = function (options, keyboard_manager, events) {
42 42 this.keyboard_manager = keyboard_manager;
43 43 this.events = events;
44 44 options = this.mergeopt(Cell, options);
45 45 // superclass default overwrite our default
46 46
47 47 this.placeholder = options.placeholder || '';
48 48 this.read_only = options.cm_config.readOnly;
49 49 this.selected = false;
50 50 this.rendered = false;
51 51 this.mode = 'command';
52 52 this.metadata = {};
53 53 // load this from metadata later ?
54 54 this.user_highlight = 'auto';
55 55 this.cm_config = options.cm_config;
56 56 this.cell_id = utils.uuid();
57 57 this._options = options;
58 58
59 59 // For JS VM engines optimization, attributes should be all set (even
60 60 // to null) in the constructor, and if possible, if different subclass
61 61 // have new attributes with same name, they should be created in the
62 62 // same order. Easiest is to create and set to null in parent class.
63 63
64 64 this.element = null;
65 65 this.cell_type = this.cell_type || null;
66 66 this.code_mirror = null;
67 67
68 68 this.create_element();
69 69 if (this.element !== null) {
70 70 this.element.data("cell", this);
71 71 this.bind_events();
72 72 this.init_classes();
73 73 }
74 74 };
75 75
76 76 Cell.options_default = {
77 77 cm_config : {
78 78 indentUnit : 4,
79 79 readOnly: false,
80 80 theme: "default",
81 81 extraKeys: {
82 82 "Cmd-Right":"goLineRight",
83 83 "End":"goLineRight",
84 84 "Cmd-Left":"goLineLeft"
85 85 }
86 86 }
87 87 };
88 88
89 89 // FIXME: Workaround CM Bug #332 (Safari segfault on drag)
90 90 // by disabling drag/drop altogether on Safari
91 91 // https://github.com/marijnh/CodeMirror/issues/332
92 92 if (utils.browser[0] == "Safari") {
93 93 Cell.options_default.cm_config.dragDrop = false;
94 94 }
95 95
96 96 Cell.prototype.mergeopt = function(_class, options, overwrite){
97 97 options = options || {};
98 98 overwrite = overwrite || {};
99 99 return $.extend(true, {}, _class.options_default, options, overwrite);
100 100 };
101 101
102 102 /**
103 103 * Empty. Subclasses must implement create_element.
104 104 * This should contain all the code to create the DOM element in notebook
105 105 * and will be called by Base Class constructor.
106 106 * @method create_element
107 107 */
108 108 Cell.prototype.create_element = function () {
109 109 };
110 110
111 111 Cell.prototype.init_classes = function () {
112 112 // Call after this.element exists to initialize the css classes
113 113 // related to selected, rendered and mode.
114 114 if (this.selected) {
115 115 this.element.addClass('selected');
116 116 } else {
117 117 this.element.addClass('unselected');
118 118 }
119 119 if (this.rendered) {
120 120 this.element.addClass('rendered');
121 121 } else {
122 122 this.element.addClass('unrendered');
123 123 }
124 124 if (this.mode === 'edit') {
125 125 this.element.addClass('edit_mode');
126 126 } else {
127 127 this.element.addClass('command_mode');
128 128 }
129 129 };
130 130
131 131 /**
132 132 * Subclasses can implement override bind_events.
133 133 * Be carefull to call the parent method when overwriting as it fires event.
134 134 * this will be triggerd after create_element in constructor.
135 135 * @method bind_events
136 136 */
137 137 Cell.prototype.bind_events = function () {
138 138 var that = this;
139 139 // We trigger events so that Cell doesn't have to depend on Notebook.
140 140 that.element.click(function (event) {
141 141 if (!that.selected) {
142 142 that.events.trigger('select.Cell', {'cell':that});
143 143 }
144 144 });
145 145 that.element.focusin(function (event) {
146 146 if (!that.selected) {
147 147 that.events.trigger('select.Cell', {'cell':that});
148 148 }
149 149 });
150 150 if (this.code_mirror) {
151 151 this.code_mirror.on("change", function(cm, change) {
152 152 that.events.trigger("set_dirty.Notebook", {value: true});
153 153 });
154 154 }
155 155 if (this.code_mirror) {
156 156 this.code_mirror.on('focus', function(cm, change) {
157 157 that.events.trigger('edit_mode.Cell', {cell: that});
158 158 });
159 159 }
160 160 if (this.code_mirror) {
161 161 this.code_mirror.on('blur', function(cm, change) {
162 162 that.events.trigger('command_mode.Cell', {cell: that});
163 163 });
164 164 }
165 165 };
166 166
167 167 /**
168 168 * This method gets called in CodeMirror's onKeyDown/onKeyPress
169 169 * handlers and is used to provide custom key handling.
170 170 *
171 171 * To have custom handling, subclasses should override this method, but still call it
172 172 * in order to process the Edit mode keyboard shortcuts.
173 173 *
174 174 * @method handle_codemirror_keyevent
175 175 * @param {CodeMirror} editor - The codemirror instance bound to the cell
176 176 * @param {event} event - key press event which either should or should not be handled by CodeMirror
177 177 * @return {Boolean} `true` if CodeMirror should ignore the event, `false` Otherwise
178 178 */
179 179 Cell.prototype.handle_codemirror_keyevent = function (editor, event) {
180 180 var that = this;
181 181 var shortcuts = this.keyboard_manager.edit_shortcuts;
182 182
183 183 // if this is an edit_shortcuts shortcut, the global keyboard/shortcut
184 184 // manager will handle it
185 185 if (shortcuts.handles(event)) { return true; }
186 186
187 187 return false;
188 188 };
189 189
190 190
191 191 /**
192 192 * Triger typsetting of math by mathjax on current cell element
193 193 * @method typeset
194 194 */
195 195 Cell.prototype.typeset = function () {
196 196 if (window.MathJax) {
197 197 var cell_math = this.element.get(0);
198 198 MathJax.Hub.Queue(["Typeset", MathJax.Hub, cell_math]);
199 199 }
200 200 };
201 201
202 202 /**
203 203 * handle cell level logic when a cell is selected
204 204 * @method select
205 205 * @return is the action being taken
206 206 */
207 207 Cell.prototype.select = function () {
208 208 if (!this.selected) {
209 209 this.element.addClass('selected');
210 210 this.element.removeClass('unselected');
211 211 this.selected = true;
212 212 return true;
213 213 } else {
214 214 return false;
215 215 }
216 216 };
217 217
218 218 /**
219 219 * handle cell level logic when a cell is unselected
220 220 * @method unselect
221 221 * @return is the action being taken
222 222 */
223 223 Cell.prototype.unselect = function () {
224 224 if (this.selected) {
225 225 this.element.addClass('unselected');
226 226 this.element.removeClass('selected');
227 227 this.selected = false;
228 228 return true;
229 229 } else {
230 230 return false;
231 231 }
232 232 };
233 233
234 234 /**
235 235 * handle cell level logic when a cell is rendered
236 236 * @method render
237 237 * @return is the action being taken
238 238 */
239 239 Cell.prototype.render = function () {
240 240 if (!this.rendered) {
241 241 this.element.addClass('rendered');
242 242 this.element.removeClass('unrendered');
243 243 this.rendered = true;
244 244 return true;
245 245 } else {
246 246 return false;
247 247 }
248 248 };
249 249
250 250 /**
251 251 * handle cell level logic when a cell is unrendered
252 252 * @method unrender
253 253 * @return is the action being taken
254 254 */
255 255 Cell.prototype.unrender = function () {
256 256 if (this.rendered) {
257 257 this.element.addClass('unrendered');
258 258 this.element.removeClass('rendered');
259 259 this.rendered = false;
260 260 return true;
261 261 } else {
262 262 return false;
263 263 }
264 264 };
265 265
266 266 /**
267 267 * Delegates keyboard shortcut handling to either IPython keyboard
268 268 * manager when in command mode, or CodeMirror when in edit mode
269 269 *
270 270 * @method handle_keyevent
271 271 * @param {CodeMirror} editor - The codemirror instance bound to the cell
272 272 * @param {event} - key event to be handled
273 273 * @return {Boolean} `true` if CodeMirror should ignore the event, `false` Otherwise
274 274 */
275 275 Cell.prototype.handle_keyevent = function (editor, event) {
276 276
277 277 // console.log('CM', this.mode, event.which, event.type)
278 278
279 279 if (this.mode === 'command') {
280 280 return true;
281 281 } else if (this.mode === 'edit') {
282 282 return this.handle_codemirror_keyevent(editor, event);
283 283 }
284 284 };
285 285
286 286 /**
287 287 * @method at_top
288 288 * @return {Boolean}
289 289 */
290 290 Cell.prototype.at_top = function () {
291 291 var cm = this.code_mirror;
292 292 var cursor = cm.getCursor();
293 293 if (cursor.line === 0 && cursor.ch === 0) {
294 294 return true;
295 295 }
296 296 return false;
297 297 };
298 298
299 299 /**
300 300 * @method at_bottom
301 301 * @return {Boolean}
302 302 * */
303 303 Cell.prototype.at_bottom = function () {
304 304 var cm = this.code_mirror;
305 305 var cursor = cm.getCursor();
306 306 if (cursor.line === (cm.lineCount()-1) && cursor.ch === cm.getLine(cursor.line).length) {
307 307 return true;
308 308 }
309 309 return false;
310 310 };
311 311
312 312 /**
313 313 * enter the command mode for the cell
314 314 * @method command_mode
315 315 * @return is the action being taken
316 316 */
317 317 Cell.prototype.command_mode = function () {
318 318 if (this.mode !== 'command') {
319 319 this.element.addClass('command_mode');
320 320 this.element.removeClass('edit_mode');
321 321 this.mode = 'command';
322 322 return true;
323 323 } else {
324 324 return false;
325 325 }
326 326 };
327 327
328 328 /**
329 329 * enter the edit mode for the cell
330 330 * @method command_mode
331 331 * @return is the action being taken
332 332 */
333 333 Cell.prototype.edit_mode = function () {
334 334 if (this.mode !== 'edit') {
335 335 this.element.addClass('edit_mode');
336 336 this.element.removeClass('command_mode');
337 337 this.mode = 'edit';
338 338 return true;
339 339 } else {
340 340 return false;
341 341 }
342 342 };
343 343
344 344 /**
345 345 * Focus the cell in the DOM sense
346 346 * @method focus_cell
347 347 */
348 348 Cell.prototype.focus_cell = function () {
349 349 this.element.focus();
350 350 };
351 351
352 352 /**
353 353 * Focus the editor area so a user can type
354 354 *
355 355 * NOTE: If codemirror is focused via a mouse click event, you don't want to
356 356 * call this because it will cause a page jump.
357 357 * @method focus_editor
358 358 */
359 359 Cell.prototype.focus_editor = function () {
360 360 this.refresh();
361 361 this.code_mirror.focus();
362 362 };
363 363
364 364 /**
365 365 * Refresh codemirror instance
366 366 * @method refresh
367 367 */
368 368 Cell.prototype.refresh = function () {
369 369 this.code_mirror.refresh();
370 370 };
371 371
372 372 /**
373 373 * should be overritten by subclass
374 374 * @method get_text
375 375 */
376 376 Cell.prototype.get_text = function () {
377 377 };
378 378
379 379 /**
380 380 * should be overritten by subclass
381 381 * @method set_text
382 382 * @param {string} text
383 383 */
384 384 Cell.prototype.set_text = function (text) {
385 385 };
386 386
387 387 /**
388 388 * should be overritten by subclass
389 389 * serialise cell to json.
390 390 * @method toJSON
391 391 **/
392 392 Cell.prototype.toJSON = function () {
393 393 var data = {};
394 394 data.metadata = this.metadata;
395 395 data.cell_type = this.cell_type;
396 396 return data;
397 397 };
398 398
399 399
400 400 /**
401 401 * should be overritten by subclass
402 402 * @method fromJSON
403 403 **/
404 404 Cell.prototype.fromJSON = function (data) {
405 405 if (data.metadata !== undefined) {
406 406 this.metadata = data.metadata;
407 407 }
408 408 this.celltoolbar.rebuild();
409 409 };
410 410
411 411
412 412 /**
413 413 * can the cell be split into two cells
414 414 * @method is_splittable
415 415 **/
416 416 Cell.prototype.is_splittable = function () {
417 417 return true;
418 418 };
419 419
420 420
421 421 /**
422 422 * can the cell be merged with other cells
423 423 * @method is_mergeable
424 424 **/
425 425 Cell.prototype.is_mergeable = function () {
426 426 return true;
427 427 };
428 428
429 429
430 430 /**
431 431 * @return {String} - the text before the cursor
432 432 * @method get_pre_cursor
433 433 **/
434 434 Cell.prototype.get_pre_cursor = function () {
435 435 var cursor = this.code_mirror.getCursor();
436 436 var text = this.code_mirror.getRange({line:0, ch:0}, cursor);
437 437 text = text.replace(/^\n+/, '').replace(/\n+$/, '');
438 438 return text;
439 439 };
440 440
441 441
442 442 /**
443 443 * @return {String} - the text after the cursor
444 444 * @method get_post_cursor
445 445 **/
446 446 Cell.prototype.get_post_cursor = function () {
447 447 var cursor = this.code_mirror.getCursor();
448 448 var last_line_num = this.code_mirror.lineCount()-1;
449 449 var last_line_len = this.code_mirror.getLine(last_line_num).length;
450 450 var end = {line:last_line_num, ch:last_line_len};
451 451 var text = this.code_mirror.getRange(cursor, end);
452 452 text = text.replace(/^\n+/, '').replace(/\n+$/, '');
453 453 return text;
454 454 };
455 455
456 456 /**
457 457 * Show/Hide CodeMirror LineNumber
458 458 * @method show_line_numbers
459 459 *
460 460 * @param value {Bool} show (true), or hide (false) the line number in CodeMirror
461 461 **/
462 462 Cell.prototype.show_line_numbers = function (value) {
463 463 this.code_mirror.setOption('lineNumbers', value);
464 464 this.code_mirror.refresh();
465 465 };
466 466
467 467 /**
468 468 * Toggle CodeMirror LineNumber
469 469 * @method toggle_line_numbers
470 470 **/
471 471 Cell.prototype.toggle_line_numbers = function () {
472 472 var val = this.code_mirror.getOption('lineNumbers');
473 473 this.show_line_numbers(!val);
474 474 };
475 475
476 476 /**
477 477 * Force codemirror highlight mode
478 478 * @method force_highlight
479 479 * @param {object} - CodeMirror mode
480 480 **/
481 481 Cell.prototype.force_highlight = function(mode) {
482 482 this.user_highlight = mode;
483 483 this.auto_highlight();
484 484 };
485 485
486 486 /**
487 487 * Try to autodetect cell highlight mode, or use selected mode
488 488 * @methods _auto_highlight
489 489 * @private
490 490 * @param {String|object|undefined} - CodeMirror mode | 'auto'
491 491 **/
492 492 Cell.prototype._auto_highlight = function (modes) {
493 493 //Here we handle manually selected modes
494 494 var mode;
495 495 if( this.user_highlight !== undefined && this.user_highlight != 'auto' )
496 496 {
497 497 mode = this.user_highlight;
498 498 CodeMirror.autoLoadMode(this.code_mirror, mode);
499 499 this.code_mirror.setOption('mode', mode);
500 500 return;
501 501 }
502 502 var current_mode = this.code_mirror.getOption('mode', mode);
503 503 var first_line = this.code_mirror.getLine(0);
504 504 // loop on every pairs
505 505 for(mode in modes) {
506 506 var regs = modes[mode].reg;
507 507 // only one key every time but regexp can't be keys...
508 508 for(var i=0; i<regs.length; i++) {
509 509 // here we handle non magic_modes
510 510 if(first_line.match(regs[i]) !== null) {
511 511 if(current_mode == mode){
512 512 return;
513 513 }
514 514 if (mode.search('magic_') !== 0) {
515 515 this.code_mirror.setOption('mode', mode);
516 516 CodeMirror.autoLoadMode(this.code_mirror, mode);
517 517 return;
518 518 }
519 519 var open = modes[mode].open || "%%";
520 520 var close = modes[mode].close || "%%end";
521 521 var mmode = mode;
522 522 mode = mmode.substr(6);
523 523 if(current_mode == mode){
524 524 return;
525 525 }
526 526 CodeMirror.autoLoadMode(this.code_mirror, mode);
527 527 // create on the fly a mode that swhitch between
528 528 // plain/text and smth else otherwise `%%` is
529 529 // source of some highlight issues.
530 530 // we use patchedGetMode to circumvent a bug in CM
531 531 CodeMirror.defineMode(mmode , function(config) {
532 532 return CodeMirror.multiplexingMode(
533 533 CodeMirror.patchedGetMode(config, 'text/plain'),
534 534 // always set someting on close
535 535 {open: open, close: close,
536 536 mode: CodeMirror.patchedGetMode(config, mode),
537 537 delimStyle: "delimit"
538 538 }
539 539 );
540 540 });
541 541 this.code_mirror.setOption('mode', mmode);
542 542 return;
543 543 }
544 544 }
545 545 }
546 546 // fallback on default
547 547 var default_mode;
548 548 try {
549 549 default_mode = this._options.cm_config.mode;
550 550 } catch(e) {
551 551 default_mode = 'text/plain';
552 552 }
553 553 if( current_mode === default_mode){
554 554 return;
555 555 }
556 556 this.code_mirror.setOption('mode', default_mode);
557 557 };
558 558
559 559 // Backwards compatability.
560 560 IPython.Cell = Cell;
561 561
562 return Cell;
562 return {'Cell': Cell};
563 563
564 564 });
@@ -1,415 +1,415 b''
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 4 define([
5 5 'base/js/namespace',
6 6 'jquery',
7 7 'notebook/js/textcell',
8 8 ], function(IPython, $, TextCell) {
9 9 "use strict";
10 10
11 11 /**
12 12 * @constructor
13 13 * @class CellToolbar
14 14 * @param {The cell to attach the metadata UI to} cell
15 15 */
16 16 var CellToolbar = function (cell, events, notebook) {
17 17 CellToolbar._instances.push(this);
18 18 this.notebook = notebook;
19 19 this.events = events;
20 20 this.cell = cell;
21 21 this.create_element();
22 22 this.rebuild();
23 23 return this;
24 24 };
25 25
26 26
27 27 CellToolbar.prototype.create_element = function () {
28 28 this.inner_element = $('<div/>').addClass('celltoolbar');
29 29 this.element = $('<div/>').addClass('ctb_hideshow')
30 30 .append(this.inner_element);
31 31 };
32 32
33 33
34 34 // The default css style for the outer celltoolbar div
35 35 // (ctb_hideshow) is display: none.
36 36 // To show the cell toolbar, *both* of the following conditions must be met:
37 37 // - A parent container has class `ctb_global_show`
38 38 // - The celltoolbar has the class `ctb_show`
39 39 // This allows global show/hide, as well as per-cell show/hide.
40 40
41 41 CellToolbar.global_hide = function () {
42 42 $('body').removeClass('ctb_global_show');
43 43 };
44 44
45 45
46 46 CellToolbar.global_show = function () {
47 47 $('body').addClass('ctb_global_show');
48 48 };
49 49
50 50
51 51 CellToolbar.prototype.hide = function () {
52 52 this.element.removeClass('ctb_show');
53 53 };
54 54
55 55
56 56 CellToolbar.prototype.show = function () {
57 57 this.element.addClass('ctb_show');
58 58 };
59 59
60 60
61 61 /**
62 62 * Class variable that should contain a dict of all available callback
63 63 * we need to think of wether or not we allow nested namespace
64 64 * @property _callback_dict
65 65 * @private
66 66 * @static
67 67 * @type Dict
68 68 */
69 69 CellToolbar._callback_dict = {};
70 70
71 71
72 72 /**
73 73 * Class variable that should contain the reverse order list of the button
74 74 * to add to the toolbar of each cell
75 75 * @property _ui_controls_list
76 76 * @private
77 77 * @static
78 78 * @type List
79 79 */
80 80 CellToolbar._ui_controls_list = [];
81 81
82 82
83 83 /**
84 84 * Class variable that should contain the CellToolbar instances for each
85 85 * cell of the notebook
86 86 *
87 87 * @private
88 88 * @property _instances
89 89 * @static
90 90 * @type List
91 91 */
92 92 CellToolbar._instances = [];
93 93
94 94
95 95 /**
96 96 * keep a list of all the available presets for the toolbar
97 97 * @private
98 98 * @property _presets
99 99 * @static
100 100 * @type Dict
101 101 */
102 102 CellToolbar._presets = {};
103 103
104 104
105 105 // this is by design not a prototype.
106 106 /**
107 107 * Register a callback to create an UI element in a cell toolbar.
108 108 * @method register_callback
109 109 * @param name {String} name to use to refer to the callback. It is advised to use a prefix with the name
110 110 * for easier sorting and avoid collision
111 111 * @param callback {function(div, cell)} callback that will be called to generate the ui element
112 112 * @param [cell_types] {List of String|undefined} optional list of cell types. If present the UI element
113 113 * will be added only to cells of types in the list.
114 114 *
115 115 *
116 116 * The callback will receive the following element :
117 117 *
118 118 * * a div in which to add element.
119 119 * * the cell it is responsible from
120 120 *
121 121 * @example
122 122 *
123 123 * Example that create callback for a button that toggle between `true` and `false` label,
124 124 * with the metadata under the key 'foo' to reflect the status of the button.
125 125 *
126 126 * // first param reference to a DOM div
127 127 * // second param reference to the cell.
128 128 * var toggle = function(div, cell) {
129 129 * var button_container = $(div)
130 130 *
131 131 * // let's create a button that show the current value of the metadata
132 132 * var button = $('<div/>').button({label:String(cell.metadata.foo)});
133 133 *
134 134 * // On click, change the metadata value and update the button label
135 135 * button.click(function(){
136 136 * var v = cell.metadata.foo;
137 137 * cell.metadata.foo = !v;
138 138 * button.button("option", "label", String(!v));
139 139 * })
140 140 *
141 141 * // add the button to the DOM div.
142 142 * button_container.append(button);
143 143 * }
144 144 *
145 145 * // now we register the callback under the name `foo` to give the
146 146 * // user the ability to use it later
147 147 * CellToolbar.register_callback('foo', toggle);
148 148 */
149 149 CellToolbar.register_callback = function(name, callback, cell_types) {
150 150 // Overwrite if it already exists.
151 151 CellToolbar._callback_dict[name] = cell_types ? {callback: callback, cell_types: cell_types} : callback;
152 152 };
153 153
154 154
155 155 /**
156 156 * Register a preset of UI element in a cell toolbar.
157 157 * Not supported Yet.
158 158 * @method register_preset
159 159 * @param name {String} name to use to refer to the preset. It is advised to use a prefix with the name
160 160 * for easier sorting and avoid collision
161 161 * @param preset_list {List of String} reverse order of the button in the toolbar. Each String of the list
162 162 * should correspond to a name of a registerd callback.
163 163 *
164 164 * @private
165 165 * @example
166 166 *
167 167 * CellToolbar.register_callback('foo.c1', function(div, cell){...});
168 168 * CellToolbar.register_callback('foo.c2', function(div, cell){...});
169 169 * CellToolbar.register_callback('foo.c3', function(div, cell){...});
170 170 * CellToolbar.register_callback('foo.c4', function(div, cell){...});
171 171 * CellToolbar.register_callback('foo.c5', function(div, cell){...});
172 172 *
173 173 * CellToolbar.register_preset('foo.foo_preset1', ['foo.c1', 'foo.c2', 'foo.c5'])
174 174 * CellToolbar.register_preset('foo.foo_preset2', ['foo.c4', 'foo.c5'])
175 175 */
176 176 CellToolbar.register_preset = function(name, preset_list) {
177 177 CellToolbar._presets[name] = preset_list;
178 178 this.events.trigger('preset_added.CellToolbar', {name: name});
179 179 // When "register_callback" is called by a custom extension, it may be executed after notebook is loaded.
180 180 // In that case, activate the preset if needed.
181 181 if (this.notebook && this.notebook.metadata && this.notebook.metadata.celltoolbar === name)
182 182 this.activate_preset(name);
183 183 };
184 184
185 185
186 186 /**
187 187 * List the names of the presets that are currently registered.
188 188 *
189 189 * @method list_presets
190 190 * @static
191 191 */
192 192 CellToolbar.list_presets = function() {
193 193 var keys = [];
194 194 for (var k in CellToolbar._presets) {
195 195 keys.push(k);
196 196 }
197 197 return keys;
198 198 };
199 199
200 200
201 201 /**
202 202 * Activate an UI preset from `register_preset`
203 203 *
204 204 * This does not update the selection UI.
205 205 *
206 206 * @method activate_preset
207 207 * @param preset_name {String} string corresponding to the preset name
208 208 *
209 209 * @static
210 210 * @private
211 211 * @example
212 212 *
213 213 * CellToolbar.activate_preset('foo.foo_preset1');
214 214 */
215 215 CellToolbar.activate_preset = function(preset_name){
216 216 var preset = CellToolbar._presets[preset_name];
217 217
218 218 if(preset !== undefined){
219 219 CellToolbar._ui_controls_list = preset;
220 220 CellToolbar.rebuild_all();
221 221 }
222 222
223 223 if (this.events) {
224 224 this.events.trigger('preset_activated.CellToolbar', {name: preset_name});
225 225 }
226 226 };
227 227
228 228
229 229 /**
230 230 * This should be called on the class and not on a instance as it will trigger
231 231 * rebuild of all the instances.
232 232 * @method rebuild_all
233 233 * @static
234 234 *
235 235 */
236 236 CellToolbar.rebuild_all = function(){
237 237 for(var i=0; i < CellToolbar._instances.length; i++){
238 238 CellToolbar._instances[i].rebuild();
239 239 }
240 240 };
241 241
242 242 /**
243 243 * Rebuild all the button on the toolbar to update its state.
244 244 * @method rebuild
245 245 */
246 246 CellToolbar.prototype.rebuild = function(){
247 247 // strip evrything from the div
248 248 // which is probably inner_element
249 249 // or this.element.
250 250 this.inner_element.empty();
251 251 this.ui_controls_list = [];
252 252
253 253 var callbacks = CellToolbar._callback_dict;
254 254 var preset = CellToolbar._ui_controls_list;
255 255 // Yes we iterate on the class variable, not the instance one.
256 256 for (var i=0; i < preset.length; i++) {
257 257 var key = preset[i];
258 258 var callback = callbacks[key];
259 259 if (!callback) continue;
260 260
261 261 if (typeof callback === 'object') {
262 262 if (callback.cell_types.indexOf(this.cell.cell_type) === -1) continue;
263 263 callback = callback.callback;
264 264 }
265 265
266 266 var local_div = $('<div/>').addClass('button_container');
267 267 try {
268 268 callback(local_div, this.cell, this);
269 269 this.ui_controls_list.push(key);
270 270 } catch (e) {
271 271 console.log("Error in cell toolbar callback " + key, e);
272 272 continue;
273 273 }
274 274 // only append if callback succeeded.
275 275 this.inner_element.append(local_div);
276 276 }
277 277
278 278 // If there are no controls or the cell is a rendered TextCell hide the toolbar.
279 279 if (!this.ui_controls_list.length || (this.cell instanceof TextCell && this.cell.rendered)) {
280 280 this.hide();
281 281 } else {
282 282 this.show();
283 283 }
284 284 };
285 285
286 286
287 287 /**
288 288 */
289 289 CellToolbar.utils = {};
290 290
291 291
292 292 /**
293 293 * A utility function to generate bindings between a checkbox and cell/metadata
294 294 * @method utils.checkbox_ui_generator
295 295 * @static
296 296 *
297 297 * @param name {string} Label in front of the checkbox
298 298 * @param setter {function( cell, newValue )}
299 299 * A setter method to set the newValue
300 300 * @param getter {function( cell )}
301 301 * A getter methods which return the current value.
302 302 *
303 303 * @return callback {function( div, cell )} Callback to be passed to `register_callback`
304 304 *
305 305 * @example
306 306 *
307 307 * An exmple that bind the subkey `slideshow.isSectionStart` to a checkbox with a `New Slide` label
308 308 *
309 309 * var newSlide = CellToolbar.utils.checkbox_ui_generator('New Slide',
310 310 * // setter
311 311 * function(cell, value){
312 312 * // we check that the slideshow namespace exist and create it if needed
313 313 * if (cell.metadata.slideshow == undefined){cell.metadata.slideshow = {}}
314 314 * // set the value
315 315 * cell.metadata.slideshow.isSectionStart = value
316 316 * },
317 317 * //geter
318 318 * function(cell){ var ns = cell.metadata.slideshow;
319 319 * // if the slideshow namespace does not exist return `undefined`
320 320 * // (will be interpreted as `false` by checkbox) otherwise
321 321 * // return the value
322 322 * return (ns == undefined)? undefined: ns.isSectionStart
323 323 * }
324 324 * );
325 325 *
326 326 * CellToolbar.register_callback('newSlide', newSlide);
327 327 *
328 328 */
329 329 CellToolbar.utils.checkbox_ui_generator = function(name, setter, getter){
330 330 return function(div, cell, celltoolbar) {
331 331 var button_container = $(div);
332 332
333 333 var chkb = $('<input/>').attr('type', 'checkbox');
334 334 var lbl = $('<label/>').append($('<span/>').text(name));
335 335 lbl.append(chkb);
336 336 chkb.attr("checked", getter(cell));
337 337
338 338 chkb.click(function(){
339 339 var v = getter(cell);
340 340 setter(cell, !v);
341 341 chkb.attr("checked", !v);
342 342 });
343 343 button_container.append($('<div/>').append(lbl));
344 344 };
345 345 };
346 346
347 347
348 348 /**
349 349 * A utility function to generate bindings between a dropdown list cell
350 350 * @method utils.select_ui_generator
351 351 * @static
352 352 *
353 353 * @param list_list {list of sublist} List of sublist of metadata value and name in the dropdown list.
354 354 * subslit shoud contain 2 element each, first a string that woul be displayed in the dropdown list,
355 355 * and second the corresponding value to be passed to setter/return by getter. the corresponding value
356 356 * should not be "undefined" or behavior can be unexpected.
357 357 * @param setter {function( cell, newValue )}
358 358 * A setter method to set the newValue
359 359 * @param getter {function( cell )}
360 360 * A getter methods which return the current value of the metadata.
361 361 * @param [label=""] {String} optionnal label for the dropdown menu
362 362 *
363 363 * @return callback {function( div, cell )} Callback to be passed to `register_callback`
364 364 *
365 365 * @example
366 366 *
367 367 * var select_type = CellToolbar.utils.select_ui_generator([
368 368 * ["<None>" , "None" ],
369 369 * ["Header Slide" , "header_slide" ],
370 370 * ["Slide" , "slide" ],
371 371 * ["Fragment" , "fragment" ],
372 372 * ["Skip" , "skip" ],
373 373 * ],
374 374 * // setter
375 375 * function(cell, value){
376 376 * // we check that the slideshow namespace exist and create it if needed
377 377 * if (cell.metadata.slideshow == undefined){cell.metadata.slideshow = {}}
378 378 * // set the value
379 379 * cell.metadata.slideshow.slide_type = value
380 380 * },
381 381 * //geter
382 382 * function(cell){ var ns = cell.metadata.slideshow;
383 383 * // if the slideshow namespace does not exist return `undefined`
384 384 * // (will be interpreted as `false` by checkbox) otherwise
385 385 * // return the value
386 386 * return (ns == undefined)? undefined: ns.slide_type
387 387 * }
388 388 * CellToolbar.register_callback('slideshow.select', select_type);
389 389 *
390 390 */
391 391 CellToolbar.utils.select_ui_generator = function(list_list, setter, getter, label) {
392 392 label = label || "";
393 393 return function(div, cell, celltoolbar) {
394 394 var button_container = $(div);
395 395 var lbl = $("<label/>").append($('<span/>').text(label));
396 396 var select = $('<select/>').addClass('ui-widget ui-widget-content');
397 397 for(var i=0; i < list_list.length; i++){
398 398 var opt = $('<option/>')
399 399 .attr('value', list_list[i][1])
400 400 .text(list_list[i][0]);
401 401 select.append(opt);
402 402 }
403 403 select.val(getter(cell));
404 404 select.change(function(){
405 405 setter(cell, select.val());
406 406 });
407 407 button_container.append($('<div/>').append(lbl).append(select));
408 408 };
409 409 };
410 410
411 411 // Backwards compatability.
412 412 IPython.CellToolbar = CellToolbar;
413 413
414 return CellToolbar;
414 return {'CellToolbar': CellToolbar};
415 415 });
@@ -1,515 +1,515 b''
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 4 define([
5 5 'base/js/namespace',
6 6 'jquery',
7 7 'base/js/utils',
8 8 'notebook/js/tooltip',
9 9 'base/js/keyboard',
10 10 'notebook/js/cell',
11 11 'notebook/js/outputarea',
12 12 'notebook/js/completer',
13 13 'notebook/js/celltoolbar',
14 14 ], function(IPython, $, utils, Tooltip, keyboard, Cell, OutputArea, Completer, CellToolbar) {
15 15 "use strict";
16 16
17 17 /* local util for codemirror */
18 18 var posEq = function(a, b) {return a.line == b.line && a.ch == b.ch;};
19 19
20 20 /**
21 21 *
22 22 * function to delete until previous non blanking space character
23 23 * or first multiple of 4 tabstop.
24 24 * @private
25 25 */
26 26 CodeMirror.commands.delSpaceToPrevTabStop = function(cm){
27 27 var from = cm.getCursor(true), to = cm.getCursor(false), sel = !posEq(from, to);
28 28 if (!posEq(from, to)) { cm.replaceRange("", from, to); return; }
29 29 var cur = cm.getCursor(), line = cm.getLine(cur.line);
30 30 var tabsize = cm.getOption('tabSize');
31 31 var chToPrevTabStop = cur.ch-(Math.ceil(cur.ch/tabsize)-1)*tabsize;
32 32 from = {ch:cur.ch-chToPrevTabStop,line:cur.line};
33 33 var select = cm.getRange(from,cur);
34 34 if( select.match(/^\ +$/) !== null){
35 35 cm.replaceRange("",from,cur);
36 36 } else {
37 37 cm.deleteH(-1,"char");
38 38 }
39 39 };
40 40
41 41 var keycodes = keyboard.keycodes;
42 42
43 43 /**
44 44 * A Cell conceived to write code.
45 45 *
46 46 * The kernel doesn't have to be set at creation time, in that case
47 47 * it will be null and set_kernel has to be called later.
48 48 * @class CodeCell
49 49 * @extends Cell
50 50 *
51 51 * @constructor
52 52 * @param {Object|null} kernel
53 53 * @param {object|undefined} [options]
54 54 * @param [options.cm_config] {object} config to pass to CodeMirror
55 55 */
56 56 var CodeCell = function (kernel, options, events, config, keyboard_manager, notebook) {
57 57 this.kernel = kernel || null;
58 58 this.notebook = notebook;
59 59 this.collapsed = false;
60 60 this.tooltip = new Tooltip(events);
61 61 this.events = events;
62 62 this.config = config;
63 63
64 64 // create all attributed in constructor function
65 65 // even if null for V8 VM optimisation
66 66 this.input_prompt_number = null;
67 67 this.celltoolbar = null;
68 68 this.output_area = null;
69 69 this.last_msg_id = null;
70 70 this.completer = null;
71 71
72 72
73 73 var cm_overwrite_options = {
74 74 onKeyEvent: $.proxy(this.handle_keyevent,this)
75 75 };
76 76
77 77 options = this.mergeopt(CodeCell, options, {cm_config:cm_overwrite_options});
78 78
79 79 Cell.apply(this,[options, keyboard_manager, events]);
80 80
81 81 // Attributes we want to override in this subclass.
82 82 this.cell_type = "code";
83 83
84 84 var that = this;
85 85 this.element.focusout(
86 86 function() { that.auto_highlight(); }
87 87 );
88 88 };
89 89
90 90 CodeCell.options_default = {
91 91 cm_config : {
92 92 extraKeys: {
93 93 "Tab" : "indentMore",
94 94 "Shift-Tab" : "indentLess",
95 95 "Backspace" : "delSpaceToPrevTabStop",
96 96 "Cmd-/" : "toggleComment",
97 97 "Ctrl-/" : "toggleComment"
98 98 },
99 99 mode: 'ipython',
100 100 theme: 'ipython',
101 101 matchBrackets: true,
102 102 // don't auto-close strings because of CodeMirror #2385
103 103 autoCloseBrackets: "()[]{}"
104 104 }
105 105 };
106 106
107 107 CodeCell.msg_cells = {};
108 108
109 109 CodeCell.prototype = new Cell();
110 110
111 111 /**
112 112 * @method auto_highlight
113 113 */
114 114 CodeCell.prototype.auto_highlight = function () {
115 115 this._auto_highlight(this.config.cell_magic_highlight);
116 116 };
117 117
118 118 /** @method create_element */
119 119 CodeCell.prototype.create_element = function () {
120 120 Cell.prototype.create_element.apply(this, arguments);
121 121
122 122 var cell = $('<div></div>').addClass('cell border-box-sizing code_cell');
123 123 cell.attr('tabindex','2');
124 124
125 125 var input = $('<div></div>').addClass('input');
126 126 var prompt = $('<div/>').addClass('prompt input_prompt');
127 127 var inner_cell = $('<div/>').addClass('inner_cell');
128 128 this.celltoolbar = new CellToolbar(this, this.events, this.notebook);
129 129 inner_cell.append(this.celltoolbar.element);
130 130 var input_area = $('<div/>').addClass('input_area');
131 131 this.code_mirror = CodeMirror(input_area.get(0), this.cm_config);
132 132 $(this.code_mirror.getInputField()).attr("spellcheck", "false");
133 133 inner_cell.append(input_area);
134 134 input.append(prompt).append(inner_cell);
135 135
136 136 var widget_area = $('<div/>')
137 137 .addClass('widget-area')
138 138 .hide();
139 139 this.widget_area = widget_area;
140 140 var widget_prompt = $('<div/>')
141 141 .addClass('prompt')
142 142 .appendTo(widget_area);
143 143 var widget_subarea = $('<div/>')
144 144 .addClass('widget-subarea')
145 145 .appendTo(widget_area);
146 146 this.widget_subarea = widget_subarea;
147 147 var widget_clear_buton = $('<button />')
148 148 .addClass('close')
149 149 .html('&times;')
150 150 .click(function() {
151 151 widget_area.slideUp('', function(){ widget_subarea.html(''); });
152 152 })
153 153 .appendTo(widget_prompt);
154 154
155 155 var output = $('<div></div>');
156 156 cell.append(input).append(widget_area).append(output);
157 157 this.element = cell;
158 158 this.output_area = new OutputArea(output, true, this.events, this.keyboard_manager);
159 159 this.completer = new Completer(this, this.events);
160 160 };
161 161
162 162 /** @method bind_events */
163 163 CodeCell.prototype.bind_events = function () {
164 164 Cell.prototype.bind_events.apply(this);
165 165 var that = this;
166 166
167 167 this.element.focusout(
168 168 function() { that.auto_highlight(); }
169 169 );
170 170 };
171 171
172 172
173 173 /**
174 174 * This method gets called in CodeMirror's onKeyDown/onKeyPress
175 175 * handlers and is used to provide custom key handling. Its return
176 176 * value is used to determine if CodeMirror should ignore the event:
177 177 * true = ignore, false = don't ignore.
178 178 * @method handle_codemirror_keyevent
179 179 */
180 180 CodeCell.prototype.handle_codemirror_keyevent = function (editor, event) {
181 181
182 182 var that = this;
183 183 // whatever key is pressed, first, cancel the tooltip request before
184 184 // they are sent, and remove tooltip if any, except for tab again
185 185 var tooltip_closed = null;
186 186 if (event.type === 'keydown' && event.which != keycodes.tab ) {
187 187 tooltip_closed = this.tooltip.remove_and_cancel_tooltip();
188 188 }
189 189
190 190 var cur = editor.getCursor();
191 191 if (event.keyCode === keycodes.enter){
192 192 this.auto_highlight();
193 193 }
194 194
195 195 if (event.which === keycodes.down && event.type === 'keypress' && this.tooltip.time_before_tooltip >= 0) {
196 196 // triger on keypress (!) otherwise inconsistent event.which depending on plateform
197 197 // browser and keyboard layout !
198 198 // Pressing '(' , request tooltip, don't forget to reappend it
199 199 // The second argument says to hide the tooltip if the docstring
200 200 // is actually empty
201 201 this.tooltip.pending(that, true);
202 202 } else if ( tooltip_closed && event.which === keycodes.esc && event.type === 'keydown') {
203 203 // If tooltip is active, cancel it. The call to
204 204 // remove_and_cancel_tooltip above doesn't pass, force=true.
205 205 // Because of this it won't actually close the tooltip
206 206 // if it is in sticky mode. Thus, we have to check again if it is open
207 207 // and close it with force=true.
208 208 if (!this.tooltip._hidden) {
209 209 this.tooltip.remove_and_cancel_tooltip(true);
210 210 }
211 211 // If we closed the tooltip, don't let CM or the global handlers
212 212 // handle this event.
213 213 event.stop();
214 214 return true;
215 215 } else if (event.keyCode === keycodes.tab && event.type === 'keydown' && event.shiftKey) {
216 216 if (editor.somethingSelected()){
217 217 var anchor = editor.getCursor("anchor");
218 218 var head = editor.getCursor("head");
219 219 if( anchor.line != head.line){
220 220 return false;
221 221 }
222 222 }
223 223 this.tooltip.request(that);
224 224 event.stop();
225 225 return true;
226 226 } else if (event.keyCode === keycodes.tab && event.type == 'keydown') {
227 227 // Tab completion.
228 228 this.tooltip.remove_and_cancel_tooltip();
229 229 if (editor.somethingSelected()) {
230 230 return false;
231 231 }
232 232 var pre_cursor = editor.getRange({line:cur.line,ch:0},cur);
233 233 if (pre_cursor.trim() === "") {
234 234 // Don't autocomplete if the part of the line before the cursor
235 235 // is empty. In this case, let CodeMirror handle indentation.
236 236 return false;
237 237 } else {
238 238 event.stop();
239 239 this.completer.startCompletion();
240 240 return true;
241 241 }
242 242 }
243 243
244 244 // keyboard event wasn't one of those unique to code cells, let's see
245 245 // if it's one of the generic ones (i.e. check edit mode shortcuts)
246 246 return Cell.prototype.handle_codemirror_keyevent.apply(this, [editor, event]);
247 247 };
248 248
249 249 // Kernel related calls.
250 250
251 251 CodeCell.prototype.set_kernel = function (kernel) {
252 252 this.kernel = kernel;
253 253 };
254 254
255 255 /**
256 256 * Execute current code cell to the kernel
257 257 * @method execute
258 258 */
259 259 CodeCell.prototype.execute = function () {
260 260 this.output_area.clear_output();
261 261
262 262 // Clear widget area
263 263 this.widget_subarea.html('');
264 264 this.widget_subarea.height('');
265 265 this.widget_area.height('');
266 266 this.widget_area.hide();
267 267
268 268 this.set_input_prompt('*');
269 269 this.element.addClass("running");
270 270 if (this.last_msg_id) {
271 271 this.kernel.clear_callbacks_for_msg(this.last_msg_id);
272 272 }
273 273 var callbacks = this.get_callbacks();
274 274
275 275 var old_msg_id = this.last_msg_id;
276 276 this.last_msg_id = this.kernel.execute(this.get_text(), callbacks, {silent: false, store_history: true});
277 277 if (old_msg_id) {
278 278 delete CodeCell.msg_cells[old_msg_id];
279 279 }
280 280 CodeCell.msg_cells[this.last_msg_id] = this;
281 281 };
282 282
283 283 /**
284 284 * Construct the default callbacks for
285 285 * @method get_callbacks
286 286 */
287 287 CodeCell.prototype.get_callbacks = function () {
288 288 return {
289 289 shell : {
290 290 reply : $.proxy(this._handle_execute_reply, this),
291 291 payload : {
292 292 set_next_input : $.proxy(this._handle_set_next_input, this),
293 293 page : $.proxy(this._open_with_pager, this)
294 294 }
295 295 },
296 296 iopub : {
297 297 output : $.proxy(this.output_area.handle_output, this.output_area),
298 298 clear_output : $.proxy(this.output_area.handle_clear_output, this.output_area),
299 299 },
300 300 input : $.proxy(this._handle_input_request, this)
301 301 };
302 302 };
303 303
304 304 CodeCell.prototype._open_with_pager = function (payload) {
305 305 this.events.trigger('open_with_text.Pager', payload);
306 306 };
307 307
308 308 /**
309 309 * @method _handle_execute_reply
310 310 * @private
311 311 */
312 312 CodeCell.prototype._handle_execute_reply = function (msg) {
313 313 this.set_input_prompt(msg.content.execution_count);
314 314 this.element.removeClass("running");
315 315 this.events.trigger('set_dirty.Notebook', {value: true});
316 316 };
317 317
318 318 /**
319 319 * @method _handle_set_next_input
320 320 * @private
321 321 */
322 322 CodeCell.prototype._handle_set_next_input = function (payload) {
323 323 var data = {'cell': this, 'text': payload.text};
324 324 this.events.trigger('set_next_input.Notebook', data);
325 325 };
326 326
327 327 /**
328 328 * @method _handle_input_request
329 329 * @private
330 330 */
331 331 CodeCell.prototype._handle_input_request = function (msg) {
332 332 this.output_area.append_raw_input(msg);
333 333 };
334 334
335 335
336 336 // Basic cell manipulation.
337 337
338 338 CodeCell.prototype.select = function () {
339 339 var cont = Cell.prototype.select.apply(this);
340 340 if (cont) {
341 341 this.code_mirror.refresh();
342 342 this.auto_highlight();
343 343 }
344 344 return cont;
345 345 };
346 346
347 347 CodeCell.prototype.render = function () {
348 348 var cont = Cell.prototype.render.apply(this);
349 349 // Always execute, even if we are already in the rendered state
350 350 return cont;
351 351 };
352 352
353 353 CodeCell.prototype.unrender = function () {
354 354 // CodeCell is always rendered
355 355 return false;
356 356 };
357 357
358 358 CodeCell.prototype.select_all = function () {
359 359 var start = {line: 0, ch: 0};
360 360 var nlines = this.code_mirror.lineCount();
361 361 var last_line = this.code_mirror.getLine(nlines-1);
362 362 var end = {line: nlines-1, ch: last_line.length};
363 363 this.code_mirror.setSelection(start, end);
364 364 };
365 365
366 366
367 367 CodeCell.prototype.collapse_output = function () {
368 368 this.collapsed = true;
369 369 this.output_area.collapse();
370 370 };
371 371
372 372
373 373 CodeCell.prototype.expand_output = function () {
374 374 this.collapsed = false;
375 375 this.output_area.expand();
376 376 this.output_area.unscroll_area();
377 377 };
378 378
379 379 CodeCell.prototype.scroll_output = function () {
380 380 this.output_area.expand();
381 381 this.output_area.scroll_if_long();
382 382 };
383 383
384 384 CodeCell.prototype.toggle_output = function () {
385 385 this.collapsed = Boolean(1 - this.collapsed);
386 386 this.output_area.toggle_output();
387 387 };
388 388
389 389 CodeCell.prototype.toggle_output_scroll = function () {
390 390 this.output_area.toggle_scroll();
391 391 };
392 392
393 393
394 394 CodeCell.input_prompt_classical = function (prompt_value, lines_number) {
395 395 var ns;
396 396 if (prompt_value === undefined) {
397 397 ns = "&nbsp;";
398 398 } else {
399 399 ns = encodeURIComponent(prompt_value);
400 400 }
401 401 return 'In&nbsp;[' + ns + ']:';
402 402 };
403 403
404 404 CodeCell.input_prompt_continuation = function (prompt_value, lines_number) {
405 405 var html = [CodeCell.input_prompt_classical(prompt_value, lines_number)];
406 406 for(var i=1; i < lines_number; i++) {
407 407 html.push(['...:']);
408 408 }
409 409 return html.join('<br/>');
410 410 };
411 411
412 412 CodeCell.input_prompt_function = CodeCell.input_prompt_classical;
413 413
414 414
415 415 CodeCell.prototype.set_input_prompt = function (number) {
416 416 var nline = 1;
417 417 if (this.code_mirror !== undefined) {
418 418 nline = this.code_mirror.lineCount();
419 419 }
420 420 this.input_prompt_number = number;
421 421 var prompt_html = CodeCell.input_prompt_function(this.input_prompt_number, nline);
422 422 // This HTML call is okay because the user contents are escaped.
423 423 this.element.find('div.input_prompt').html(prompt_html);
424 424 };
425 425
426 426
427 427 CodeCell.prototype.clear_input = function () {
428 428 this.code_mirror.setValue('');
429 429 };
430 430
431 431
432 432 CodeCell.prototype.get_text = function () {
433 433 return this.code_mirror.getValue();
434 434 };
435 435
436 436
437 437 CodeCell.prototype.set_text = function (code) {
438 438 return this.code_mirror.setValue(code);
439 439 };
440 440
441 441
442 442 CodeCell.prototype.clear_output = function (wait) {
443 443 this.output_area.clear_output(wait);
444 444 this.set_input_prompt();
445 445 };
446 446
447 447
448 448 // JSON serialization
449 449
450 450 CodeCell.prototype.fromJSON = function (data) {
451 451 Cell.prototype.fromJSON.apply(this, arguments);
452 452 if (data.cell_type === 'code') {
453 453 if (data.input !== undefined) {
454 454 this.set_text(data.input);
455 455 // make this value the starting point, so that we can only undo
456 456 // to this state, instead of a blank cell
457 457 this.code_mirror.clearHistory();
458 458 this.auto_highlight();
459 459 }
460 460 if (data.prompt_number !== undefined) {
461 461 this.set_input_prompt(data.prompt_number);
462 462 } else {
463 463 this.set_input_prompt();
464 464 }
465 465 this.output_area.trusted = data.trusted || false;
466 466 this.output_area.fromJSON(data.outputs);
467 467 if (data.collapsed !== undefined) {
468 468 if (data.collapsed) {
469 469 this.collapse_output();
470 470 } else {
471 471 this.expand_output();
472 472 }
473 473 }
474 474 }
475 475 };
476 476
477 477
478 478 CodeCell.prototype.toJSON = function () {
479 479 var data = Cell.prototype.toJSON.apply(this);
480 480 data.input = this.get_text();
481 481 // is finite protect against undefined and '*' value
482 482 if (isFinite(this.input_prompt_number)) {
483 483 data.prompt_number = this.input_prompt_number;
484 484 }
485 485 var outputs = this.output_area.toJSON();
486 486 data.outputs = outputs;
487 487 data.language = 'python';
488 488 data.trusted = this.output_area.trusted;
489 489 data.collapsed = this.collapsed;
490 490 return data;
491 491 };
492 492
493 493 /**
494 494 * handle cell level logic when a cell is unselected
495 495 * @method unselect
496 496 * @return is the action being taken
497 497 */
498 498 CodeCell.prototype.unselect = function () {
499 499 var cont = Cell.prototype.unselect.apply(this);
500 500 if (cont) {
501 501 // When a code cell is usnelected, make sure that the corresponding
502 502 // tooltip and completer to that cell is closed.
503 503 this.tooltip.remove_and_cancel_tooltip(true);
504 504 if (this.completer !== null) {
505 505 this.completer.close();
506 506 }
507 507 }
508 508 return cont;
509 509 };
510 510
511 511 // Backwards compatability.
512 512 IPython.CodeCell = CodeCell;
513 513
514 return CodeCell;
514 return {'CodeCell': CodeCell};
515 515 });
@@ -1,387 +1,387 b''
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 4 define([
5 5 'base/js/namespace',
6 6 'jquery',
7 7 'base/js/utils',
8 8 'base/js/keyboard',
9 9 'notebook/js/contexthint',
10 10 ], function(IPython, $, utils, keyboard) {
11 11 "use strict";
12 12
13 13 // easier key mapping
14 14 var keycodes = keyboard.keycodes;
15 15
16 16 var prepend_n_prc = function(str, n) {
17 17 for( var i =0 ; i< n ; i++){
18 18 str = '%'+str ;
19 19 }
20 20 return str;
21 21 };
22 22
23 23 var _existing_completion = function(item, completion_array){
24 24 for( var i=0; i < completion_array.length; i++) {
25 25 if (completion_array[i].trim().substr(-item.length) == item) {
26 26 return true;
27 27 }
28 28 }
29 29 return false;
30 30 };
31 31
32 32 // what is the common start of all completions
33 33 function shared_start(B, drop_prct) {
34 34 if (B.length == 1) {
35 35 return B[0];
36 36 }
37 37 var A = [];
38 38 var common;
39 39 var min_lead_prct = 10;
40 40 for (var i = 0; i < B.length; i++) {
41 41 var str = B[i].str;
42 42 var localmin = 0;
43 43 if(drop_prct === true){
44 44 while ( str.substr(0, 1) == '%') {
45 45 localmin = localmin+1;
46 46 str = str.substring(1);
47 47 }
48 48 }
49 49 min_lead_prct = Math.min(min_lead_prct, localmin);
50 50 A.push(str);
51 51 }
52 52
53 53 if (A.length > 1) {
54 54 var tem1, tem2, s;
55 55 A = A.slice(0).sort();
56 56 tem1 = A[0];
57 57 s = tem1.length;
58 58 tem2 = A.pop();
59 59 while (s && tem2.indexOf(tem1) == -1) {
60 60 tem1 = tem1.substring(0, --s);
61 61 }
62 62 if (tem1 === "" || tem2.indexOf(tem1) !== 0) {
63 63 return {
64 64 str:prepend_n_prc('', min_lead_prct),
65 65 type: "computed",
66 66 from: B[0].from,
67 67 to: B[0].to
68 68 };
69 69 }
70 70 return {
71 71 str: prepend_n_prc(tem1, min_lead_prct),
72 72 type: "computed",
73 73 from: B[0].from,
74 74 to: B[0].to
75 75 };
76 76 }
77 77 return null;
78 78 }
79 79
80 80
81 81 var Completer = function (cell, events) {
82 82 this.cell = cell;
83 83 this.editor = cell.code_mirror;
84 84 var that = this;
85 85 events.on('status_busy.Kernel', function () {
86 86 that.skip_kernel_completion = true;
87 87 });
88 88 events.on('status_idle.Kernel', function () {
89 89 that.skip_kernel_completion = false;
90 90 });
91 91 };
92 92
93 93 Completer.prototype.startCompletion = function () {
94 94 // call for a 'first' completion, that will set the editor and do some
95 95 // special behavior like autopicking if only one completion available.
96 96 if (this.editor.somethingSelected()) return;
97 97 this.done = false;
98 98 // use to get focus back on opera
99 99 this.carry_on_completion(true);
100 100 };
101 101
102 102
103 103 // easy access for julia to monkeypatch
104 104 //
105 105 Completer.reinvoke_re = /[%0-9a-z._/\\:~-]/i;
106 106
107 107 Completer.prototype.reinvoke= function(pre_cursor, block, cursor){
108 108 return Completer.reinvoke_re.test(pre_cursor);
109 109 };
110 110
111 111 /**
112 112 *
113 113 * pass true as parameter if this is the first invocation of the completer
114 114 * this will prevent the completer to dissmiss itself if it is not on a
115 115 * word boundary like pressing tab after a space, and make it autopick the
116 116 * only choice if there is only one which prevent from popping the UI. as
117 117 * well as fast-forwarding the typing if all completion have a common
118 118 * shared start
119 119 **/
120 120 Completer.prototype.carry_on_completion = function (first_invocation) {
121 121 // Pass true as parameter if you want the completer to autopick when
122 122 // only one completion. This function is automatically reinvoked at
123 123 // each keystroke with first_invocation = false
124 124 var cur = this.editor.getCursor();
125 125 var line = this.editor.getLine(cur.line);
126 126 var pre_cursor = this.editor.getRange({
127 127 line: cur.line,
128 128 ch: cur.ch - 1
129 129 }, cur);
130 130
131 131 // we need to check that we are still on a word boundary
132 132 // because while typing the completer is still reinvoking itself
133 133 // so dismiss if we are on a "bad" caracter
134 134 if (!this.reinvoke(pre_cursor) && !first_invocation) {
135 135 this.close();
136 136 return;
137 137 }
138 138
139 139 this.autopick = false;
140 140 if (first_invocation) {
141 141 this.autopick = true;
142 142 }
143 143
144 144 // We want a single cursor position.
145 145 if (this.editor.somethingSelected()) {
146 146 return;
147 147 }
148 148
149 149 // one kernel completion came back, finish_completing will be called with the results
150 150 // we fork here and directly call finish completing if kernel is busy
151 151 var cursor_pos = utils.to_absolute_cursor_pos(this.editor, cur);
152 152 if (this.skip_kernel_completion) {
153 153 this.finish_completing({ content: {
154 154 matches: [],
155 155 cursor_start: cursor_pos,
156 156 cursor_end: cursor_pos,
157 157 }});
158 158 } else {
159 159 this.cell.kernel.complete(this.editor.getValue(), cursor_pos,
160 160 $.proxy(this.finish_completing, this)
161 161 );
162 162 }
163 163 };
164 164
165 165 Completer.prototype.finish_completing = function (msg) {
166 166 // let's build a function that wrap all that stuff into what is needed
167 167 // for the new completer:
168 168 var content = msg.content;
169 169 var start = content.cursor_start;
170 170 var end = content.cursor_end;
171 171 var matches = content.matches;
172 172
173 173 var cur = this.editor.getCursor();
174 174 if (end === null) {
175 175 // adapted message spec replies don't have cursor position info,
176 176 // interpret end=null as current position,
177 177 // and negative start relative to that
178 178 end = utils.to_absolute_cursor_pos(this.editor, cur);
179 179 if (start < 0) {
180 180 start = end + start;
181 181 }
182 182 }
183 183 var results = CodeMirror.contextHint(this.editor);
184 184 var filtered_results = [];
185 185 //remove results from context completion
186 186 //that are already in kernel completion
187 187 var i;
188 188 for (i=0; i < results.length; i++) {
189 189 if (!_existing_completion(results[i].str, matches)) {
190 190 filtered_results.push(results[i]);
191 191 }
192 192 }
193 193
194 194 // append the introspection result, in order, at at the beginning of
195 195 // the table and compute the replacement range from current cursor
196 196 // positon and matched_text length.
197 197 for (i = matches.length - 1; i >= 0; --i) {
198 198 filtered_results.unshift({
199 199 str: matches[i],
200 200 type: "introspection",
201 201 from: utils.from_absolute_cursor_pos(this.editor, start),
202 202 to: utils.from_absolute_cursor_pos(this.editor, end)
203 203 });
204 204 }
205 205
206 206 // one the 2 sources results have been merge, deal with it
207 207 this.raw_result = filtered_results;
208 208
209 209 // if empty result return
210 210 if (!this.raw_result || !this.raw_result.length) return;
211 211
212 212 // When there is only one completion, use it directly.
213 213 if (this.autopick && this.raw_result.length == 1) {
214 214 this.insert(this.raw_result[0]);
215 215 return;
216 216 }
217 217
218 218 if (this.raw_result.length == 1) {
219 219 // test if first and only completion totally matches
220 220 // what is typed, in this case dismiss
221 221 var str = this.raw_result[0].str;
222 222 var pre_cursor = this.editor.getRange({
223 223 line: cur.line,
224 224 ch: cur.ch - str.length
225 225 }, cur);
226 226 if (pre_cursor == str) {
227 227 this.close();
228 228 return;
229 229 }
230 230 }
231 231
232 232 if (!this.visible) {
233 233 this.complete = $('<div/>').addClass('completions');
234 234 this.complete.attr('id', 'complete');
235 235
236 236 // Currently webkit doesn't use the size attr correctly. See:
237 237 // https://code.google.com/p/chromium/issues/detail?id=4579
238 238 this.sel = $('<select/>')
239 239 .attr('tabindex', -1)
240 240 .attr('multiple', 'true');
241 241 this.complete.append(this.sel);
242 242 this.visible = true;
243 243 $('body').append(this.complete);
244 244
245 245 //build the container
246 246 var that = this;
247 247 this.sel.dblclick(function () {
248 248 that.pick();
249 249 });
250 250 this.sel.focus(function () {
251 251 that.editor.focus();
252 252 });
253 253 this._handle_keydown = function (cm, event) {
254 254 that.keydown(event);
255 255 };
256 256 this.editor.on('keydown', this._handle_keydown);
257 257 this._handle_keypress = function (cm, event) {
258 258 that.keypress(event);
259 259 };
260 260 this.editor.on('keypress', this._handle_keypress);
261 261 }
262 262 this.sel.attr('size', Math.min(10, this.raw_result.length));
263 263
264 264 // After everything is on the page, compute the postion.
265 265 // We put it above the code if it is too close to the bottom of the page.
266 266 var pos = this.editor.cursorCoords(
267 267 utils.from_absolute_cursor_pos(this.editor, start)
268 268 );
269 269 var left = pos.left-3;
270 270 var top;
271 271 var cheight = this.complete.height();
272 272 var wheight = $(window).height();
273 273 if (pos.bottom+cheight+5 > wheight) {
274 274 top = pos.top-cheight-4;
275 275 } else {
276 276 top = pos.bottom+1;
277 277 }
278 278 this.complete.css('left', left + 'px');
279 279 this.complete.css('top', top + 'px');
280 280
281 281 // Clear and fill the list.
282 282 this.sel.text('');
283 283 this.build_gui_list(this.raw_result);
284 284 return true;
285 285 };
286 286
287 287 Completer.prototype.insert = function (completion) {
288 288 this.editor.replaceRange(completion.str, completion.from, completion.to);
289 289 };
290 290
291 291 Completer.prototype.build_gui_list = function (completions) {
292 292 for (var i = 0; i < completions.length; ++i) {
293 293 var opt = $('<option/>').text(completions[i].str).addClass(completions[i].type);
294 294 this.sel.append(opt);
295 295 }
296 296 this.sel.children().first().attr('selected', 'true');
297 297 this.sel.scrollTop(0);
298 298 };
299 299
300 300 Completer.prototype.close = function () {
301 301 this.done = true;
302 302 $('#complete').remove();
303 303 this.editor.off('keydown', this._handle_keydown);
304 304 this.editor.off('keypress', this._handle_keypress);
305 305 this.visible = false;
306 306 };
307 307
308 308 Completer.prototype.pick = function () {
309 309 this.insert(this.raw_result[this.sel[0].selectedIndex]);
310 310 this.close();
311 311 };
312 312
313 313 Completer.prototype.keydown = function (event) {
314 314 var code = event.keyCode;
315 315 var that = this;
316 316
317 317 // Enter
318 318 if (code == keycodes.enter) {
319 319 CodeMirror.e_stop(event);
320 320 this.pick();
321 321 // Escape or backspace
322 322 } else if (code == keycodes.esc || code == keycodes.backspace) {
323 323 CodeMirror.e_stop(event);
324 324 this.close();
325 325 } else if (code == keycodes.tab) {
326 326 //all the fastforwarding operation,
327 327 //Check that shared start is not null which can append with prefixed completion
328 328 // like %pylab , pylab have no shred start, and ff will result in py<tab><tab>
329 329 // to erase py
330 330 var sh = shared_start(this.raw_result, true);
331 331 if (sh) {
332 332 this.insert(sh);
333 333 }
334 334 this.close();
335 335 //reinvoke self
336 336 setTimeout(function () {
337 337 that.carry_on_completion();
338 338 }, 50);
339 339 } else if (code == keycodes.up || code == keycodes.down) {
340 340 // need to do that to be able to move the arrow
341 341 // when on the first or last line ofo a code cell
342 342 CodeMirror.e_stop(event);
343 343
344 344 var options = this.sel.find('option');
345 345 var index = this.sel[0].selectedIndex;
346 346 if (code == keycodes.up) {
347 347 index--;
348 348 }
349 349 if (code == keycodes.down) {
350 350 index++;
351 351 }
352 352 index = Math.min(Math.max(index, 0), options.length-1);
353 353 this.sel[0].selectedIndex = index;
354 354 } else if (code == keycodes.left || code == keycodes.right) {
355 355 this.close();
356 356 }
357 357 };
358 358
359 359 Completer.prototype.keypress = function (event) {
360 360 // FIXME: This is a band-aid.
361 361 // on keypress, trigger insertion of a single character.
362 362 // This simulates the old behavior of completion as you type,
363 363 // before events were disconnected and CodeMirror stopped
364 364 // receiving events while the completer is focused.
365 365
366 366 var that = this;
367 367 var code = event.keyCode;
368 368
369 369 // don't handle keypress if it's not a character (arrows on FF)
370 370 // or ENTER/TAB
371 371 if (event.charCode === 0 ||
372 372 code == keycodes.tab ||
373 373 code == keycodes.enter
374 374 ) return;
375 375
376 376 this.close();
377 377 this.editor.focus();
378 378 setTimeout(function () {
379 379 that.carry_on_completion();
380 380 }, 50);
381 381 };
382 382
383 383 // For backwards compatability.
384 384 IPython.Completer = Completer;
385 385
386 return Completer;
386 return {'Completer': Completer};
387 387 });
@@ -1,98 +1,98 b''
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 4 // highly adapted for codemiror jshint
5 5 define([], function() {
6 6 "use strict";
7 7
8 8 var forEach = function(arr, f) {
9 9 for (var i = 0, e = arr.length; i < e; ++i) f(arr[i]);
10 10 };
11 11
12 12 var arrayContains = function(arr, item) {
13 13 if (!Array.prototype.indexOf) {
14 14 var i = arr.length;
15 15 while (i--) {
16 16 if (arr[i] === item) {
17 17 return true;
18 18 }
19 19 }
20 20 return false;
21 21 }
22 22 return arr.indexOf(item) != -1;
23 23 };
24 24
25 25 CodeMirror.contextHint = function (editor) {
26 26 // Find the token at the cursor
27 27 var cur = editor.getCursor(),
28 28 token = editor.getTokenAt(cur),
29 29 tprop = token;
30 30 // If it's not a 'word-style' token, ignore the token.
31 31 // If it is a property, find out what it is a property of.
32 32 var list = [];
33 33 var clist = getCompletions(token, editor);
34 34 for (var i = 0; i < clist.length; i++) {
35 35 list.push({
36 36 str: clist[i],
37 37 type: "context",
38 38 from: {
39 39 line: cur.line,
40 40 ch: token.start
41 41 },
42 42 to: {
43 43 line: cur.line,
44 44 ch: token.end
45 45 }
46 46 });
47 47 }
48 48 return list;
49 49 };
50 50
51 51 // find all 'words' of current cell
52 52 var getAllTokens = function (editor) {
53 53 var found = [];
54 54
55 55 // add to found if not already in it
56 56
57 57
58 58 function maybeAdd(str) {
59 59 if (!arrayContains(found, str)) found.push(str);
60 60 }
61 61
62 62 // loop through all token on all lines
63 63 var lineCount = editor.lineCount();
64 64 // loop on line
65 65 for (var l = 0; l < lineCount; l++) {
66 66 var line = editor.getLine(l);
67 67 //loop on char
68 68 for (var c = 1; c < line.length; c++) {
69 69 var tk = editor.getTokenAt({
70 70 line: l,
71 71 ch: c
72 72 });
73 73 // if token has a class, it has geat chances of beeing
74 74 // of interest. Add it to the list of possible completions.
75 75 // we could skip token of ClassName 'comment'
76 76 // or 'number' and 'operator'
77 77 if (tk.className !== null) {
78 78 maybeAdd(tk.string);
79 79 }
80 80 // jump to char after end of current token
81 81 c = tk.end;
82 82 }
83 83 }
84 84 return found;
85 85 };
86 86
87 87 var getCompletions = function(token, editor) {
88 88 var candidates = getAllTokens(editor);
89 89 // filter all token that have a common start (but nox exactly) the lenght of the current token
90 90 var lambda = function (x) {
91 91 return (x.indexOf(token.string) === 0 && x != token.string);
92 92 };
93 93 var filterd = candidates.filter(lambda);
94 94 return filterd;
95 95 };
96 96
97 return CodeMirror.contextHint;
97 return {'contextHint': CodeMirror.contextHint};
98 98 });
@@ -1,560 +1,559 b''
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 4 define([
5 5 'base/js/namespace',
6 6 'jquery',
7 7 'base/js/utils',
8 8 'base/js/keyboard',
9 9 ], function(IPython, $, utils, keyboard) {
10 10 "use strict";
11 11
12 12 var browser = utils.browser[0];
13 13 var platform = utils.platform;
14 14
15 15 // Main keyboard manager for the notebook
16 16 var keycodes = keyboard.keycodes;
17 17
18 18 var KeyboardManager = function (pager, events) {
19 19 this.mode = 'command';
20 20 this.enabled = true;
21 21 this.pager = pager;
22 22 this.quick_help = undefined;
23 23 this.notebook = undefined;
24 24 this.bind_events();
25 25 this.command_shortcuts = new keyboard.ShortcutManager(undefined, events);
26 26 this.command_shortcuts.add_shortcuts(this.get_default_common_shortcuts());
27 27 this.command_shortcuts.add_shortcuts(this.get_default_command_shortcuts());
28 28 this.edit_shortcuts = new keyboard.ShortcutManager(undefined, events);
29 29 this.edit_shortcuts.add_shortcuts(this.get_default_common_shortcuts());
30 30 this.edit_shortcuts.add_shortcuts(this.get_default_edit_shortcuts());
31 31 };
32 32
33 33 KeyboardManager.prototype.get_default_common_shortcuts = function() {
34 34 var that = this;
35 35 var shortcuts = {
36 36 'shift' : {
37 37 help : '',
38 38 help_index : '',
39 39 handler : function (event) {
40 40 // ignore shift keydown
41 41 return true;
42 42 }
43 43 },
44 44 'shift-enter' : {
45 45 help : 'run cell, select below',
46 46 help_index : 'ba',
47 47 handler : function (event) {
48 48 that.notebook.execute_cell_and_select_below();
49 49 return false;
50 50 }
51 51 },
52 52 'ctrl-enter' : {
53 53 help : 'run cell',
54 54 help_index : 'bb',
55 55 handler : function (event) {
56 56 that.notebook.execute_cell();
57 57 return false;
58 58 }
59 59 },
60 60 'alt-enter' : {
61 61 help : 'run cell, insert below',
62 62 help_index : 'bc',
63 63 handler : function (event) {
64 64 that.notebook.execute_cell_and_insert_below();
65 65 return false;
66 66 }
67 67 }
68 68 };
69 69
70 70 if (platform === 'MacOS') {
71 71 shortcuts['cmd-s'] =
72 72 {
73 73 help : 'save notebook',
74 74 help_index : 'fb',
75 75 handler : function (event) {
76 76 that.notebook.save_checkpoint();
77 77 event.preventDefault();
78 78 return false;
79 79 }
80 80 };
81 81 } else {
82 82 shortcuts['ctrl-s'] =
83 83 {
84 84 help : 'save notebook',
85 85 help_index : 'fb',
86 86 handler : function (event) {
87 87 that.notebook.save_checkpoint();
88 88 event.preventDefault();
89 89 return false;
90 90 }
91 91 };
92 92 }
93 93 return shortcuts;
94 94 };
95 95
96 96 KeyboardManager.prototype.get_default_edit_shortcuts = function() {
97 97 var that = this;
98 98 return {
99 99 'esc' : {
100 100 help : 'command mode',
101 101 help_index : 'aa',
102 102 handler : function (event) {
103 103 that.notebook.command_mode();
104 104 return false;
105 105 }
106 106 },
107 107 'ctrl-m' : {
108 108 help : 'command mode',
109 109 help_index : 'ab',
110 110 handler : function (event) {
111 111 that.notebook.command_mode();
112 112 return false;
113 113 }
114 114 },
115 115 'up' : {
116 116 help : '',
117 117 help_index : '',
118 118 handler : function (event) {
119 119 var index = that.notebook.get_selected_index();
120 120 var cell = that.notebook.get_cell(index);
121 121 if (cell && cell.at_top() && index !== 0) {
122 122 event.preventDefault();
123 123 that.notebook.command_mode();
124 124 that.notebook.select_prev();
125 125 that.notebook.edit_mode();
126 126 var cm = that.notebook.get_selected_cell().code_mirror;
127 127 cm.setCursor(cm.lastLine(), 0);
128 128 return false;
129 129 } else if (cell) {
130 130 var cm = cell.code_mirror;
131 131 cm.execCommand('goLineUp');
132 132 return false;
133 133 }
134 134 }
135 135 },
136 136 'down' : {
137 137 help : '',
138 138 help_index : '',
139 139 handler : function (event) {
140 140 var index = that.notebook.get_selected_index();
141 141 var cell = that.notebook.get_cell(index);
142 142 if (cell.at_bottom() && index !== (that.notebook.ncells()-1)) {
143 143 event.preventDefault();
144 144 that.notebook.command_mode();
145 145 that.notebook.select_next();
146 146 that.notebook.edit_mode();
147 147 var cm = that.notebook.get_selected_cell().code_mirror;
148 148 cm.setCursor(0, 0);
149 149 return false;
150 150 } else {
151 151 var cm = cell.code_mirror;
152 152 cm.execCommand('goLineDown');
153 153 return false;
154 154 }
155 155 }
156 156 },
157 157 'ctrl-shift--' : {
158 158 help : 'split cell',
159 159 help_index : 'ea',
160 160 handler : function (event) {
161 161 that.notebook.split_cell();
162 162 return false;
163 163 }
164 164 },
165 165 'ctrl-shift-subtract' : {
166 166 help : '',
167 167 help_index : 'eb',
168 168 handler : function (event) {
169 169 that.notebook.split_cell();
170 170 return false;
171 171 }
172 172 },
173 173 };
174 174 };
175 175
176 176 KeyboardManager.prototype.get_default_command_shortcuts = function() {
177 177 var that = this;
178 178 return {
179 179 'enter' : {
180 180 help : 'edit mode',
181 181 help_index : 'aa',
182 182 handler : function (event) {
183 183 that.notebook.edit_mode();
184 184 return false;
185 185 }
186 186 },
187 187 'up' : {
188 188 help : 'select previous cell',
189 189 help_index : 'da',
190 190 handler : function (event) {
191 191 var index = that.notebook.get_selected_index();
192 192 if (index !== 0 && index !== null) {
193 193 that.notebook.select_prev();
194 194 that.notebook.focus_cell();
195 195 }
196 196 return false;
197 197 }
198 198 },
199 199 'down' : {
200 200 help : 'select next cell',
201 201 help_index : 'db',
202 202 handler : function (event) {
203 203 var index = that.notebook.get_selected_index();
204 204 if (index !== (that.notebook.ncells()-1) && index !== null) {
205 205 that.notebook.select_next();
206 206 that.notebook.focus_cell();
207 207 }
208 208 return false;
209 209 }
210 210 },
211 211 'k' : {
212 212 help : 'select previous cell',
213 213 help_index : 'dc',
214 214 handler : function (event) {
215 215 var index = that.notebook.get_selected_index();
216 216 if (index !== 0 && index !== null) {
217 217 that.notebook.select_prev();
218 218 that.notebook.focus_cell();
219 219 }
220 220 return false;
221 221 }
222 222 },
223 223 'j' : {
224 224 help : 'select next cell',
225 225 help_index : 'dd',
226 226 handler : function (event) {
227 227 var index = that.notebook.get_selected_index();
228 228 if (index !== (that.notebook.ncells()-1) && index !== null) {
229 229 that.notebook.select_next();
230 230 that.notebook.focus_cell();
231 231 }
232 232 return false;
233 233 }
234 234 },
235 235 'x' : {
236 236 help : 'cut cell',
237 237 help_index : 'ee',
238 238 handler : function (event) {
239 239 that.notebook.cut_cell();
240 240 return false;
241 241 }
242 242 },
243 243 'c' : {
244 244 help : 'copy cell',
245 245 help_index : 'ef',
246 246 handler : function (event) {
247 247 that.notebook.copy_cell();
248 248 return false;
249 249 }
250 250 },
251 251 'shift-v' : {
252 252 help : 'paste cell above',
253 253 help_index : 'eg',
254 254 handler : function (event) {
255 255 that.notebook.paste_cell_above();
256 256 return false;
257 257 }
258 258 },
259 259 'v' : {
260 260 help : 'paste cell below',
261 261 help_index : 'eh',
262 262 handler : function (event) {
263 263 that.notebook.paste_cell_below();
264 264 return false;
265 265 }
266 266 },
267 267 'd' : {
268 268 help : 'delete cell (press twice)',
269 269 help_index : 'ej',
270 270 count: 2,
271 271 handler : function (event) {
272 272 that.notebook.delete_cell();
273 273 return false;
274 274 }
275 275 },
276 276 'a' : {
277 277 help : 'insert cell above',
278 278 help_index : 'ec',
279 279 handler : function (event) {
280 280 that.notebook.insert_cell_above();
281 281 that.notebook.select_prev();
282 282 that.notebook.focus_cell();
283 283 return false;
284 284 }
285 285 },
286 286 'b' : {
287 287 help : 'insert cell below',
288 288 help_index : 'ed',
289 289 handler : function (event) {
290 290 that.notebook.insert_cell_below();
291 291 that.notebook.select_next();
292 292 that.notebook.focus_cell();
293 293 return false;
294 294 }
295 295 },
296 296 'y' : {
297 297 help : 'to code',
298 298 help_index : 'ca',
299 299 handler : function (event) {
300 300 that.notebook.to_code();
301 301 return false;
302 302 }
303 303 },
304 304 'm' : {
305 305 help : 'to markdown',
306 306 help_index : 'cb',
307 307 handler : function (event) {
308 308 that.notebook.to_markdown();
309 309 return false;
310 310 }
311 311 },
312 312 'r' : {
313 313 help : 'to raw',
314 314 help_index : 'cc',
315 315 handler : function (event) {
316 316 that.notebook.to_raw();
317 317 return false;
318 318 }
319 319 },
320 320 '1' : {
321 321 help : 'to heading 1',
322 322 help_index : 'cd',
323 323 handler : function (event) {
324 324 that.notebook.to_heading(undefined, 1);
325 325 return false;
326 326 }
327 327 },
328 328 '2' : {
329 329 help : 'to heading 2',
330 330 help_index : 'ce',
331 331 handler : function (event) {
332 332 that.notebook.to_heading(undefined, 2);
333 333 return false;
334 334 }
335 335 },
336 336 '3' : {
337 337 help : 'to heading 3',
338 338 help_index : 'cf',
339 339 handler : function (event) {
340 340 that.notebook.to_heading(undefined, 3);
341 341 return false;
342 342 }
343 343 },
344 344 '4' : {
345 345 help : 'to heading 4',
346 346 help_index : 'cg',
347 347 handler : function (event) {
348 348 that.notebook.to_heading(undefined, 4);
349 349 return false;
350 350 }
351 351 },
352 352 '5' : {
353 353 help : 'to heading 5',
354 354 help_index : 'ch',
355 355 handler : function (event) {
356 356 that.notebook.to_heading(undefined, 5);
357 357 return false;
358 358 }
359 359 },
360 360 '6' : {
361 361 help : 'to heading 6',
362 362 help_index : 'ci',
363 363 handler : function (event) {
364 364 that.notebook.to_heading(undefined, 6);
365 365 return false;
366 366 }
367 367 },
368 368 'o' : {
369 369 help : 'toggle output',
370 370 help_index : 'gb',
371 371 handler : function (event) {
372 372 that.notebook.toggle_output();
373 373 return false;
374 374 }
375 375 },
376 376 'shift-o' : {
377 377 help : 'toggle output scrolling',
378 378 help_index : 'gc',
379 379 handler : function (event) {
380 380 that.notebook.toggle_output_scroll();
381 381 return false;
382 382 }
383 383 },
384 384 's' : {
385 385 help : 'save notebook',
386 386 help_index : 'fa',
387 387 handler : function (event) {
388 388 that.notebook.save_checkpoint();
389 389 return false;
390 390 }
391 391 },
392 392 'ctrl-j' : {
393 393 help : 'move cell down',
394 394 help_index : 'eb',
395 395 handler : function (event) {
396 396 that.notebook.move_cell_down();
397 397 return false;
398 398 }
399 399 },
400 400 'ctrl-k' : {
401 401 help : 'move cell up',
402 402 help_index : 'ea',
403 403 handler : function (event) {
404 404 that.notebook.move_cell_up();
405 405 return false;
406 406 }
407 407 },
408 408 'l' : {
409 409 help : 'toggle line numbers',
410 410 help_index : 'ga',
411 411 handler : function (event) {
412 412 that.notebook.cell_toggle_line_numbers();
413 413 return false;
414 414 }
415 415 },
416 416 'i' : {
417 417 help : 'interrupt kernel (press twice)',
418 418 help_index : 'ha',
419 419 count: 2,
420 420 handler : function (event) {
421 421 that.notebook.kernel.interrupt();
422 422 return false;
423 423 }
424 424 },
425 425 '0' : {
426 426 help : 'restart kernel (press twice)',
427 427 help_index : 'hb',
428 428 count: 2,
429 429 handler : function (event) {
430 430 that.notebook.restart_kernel();
431 431 return false;
432 432 }
433 433 },
434 434 'h' : {
435 435 help : 'keyboard shortcuts',
436 436 help_index : 'ge',
437 437 handler : function (event) {
438 438 that.quick_help.show_keyboard_shortcuts();
439 439 return false;
440 440 }
441 441 },
442 442 'z' : {
443 443 help : 'undo last delete',
444 444 help_index : 'ei',
445 445 handler : function (event) {
446 446 that.notebook.undelete_cell();
447 447 return false;
448 448 }
449 449 },
450 450 'shift-m' : {
451 451 help : 'merge cell below',
452 452 help_index : 'ek',
453 453 handler : function (event) {
454 454 that.notebook.merge_cell_below();
455 455 return false;
456 456 }
457 457 },
458 458 'q' : {
459 459 help : 'close pager',
460 460 help_index : 'gd',
461 461 handler : function (event) {
462 462 that.pager.collapse();
463 463 return false;
464 464 }
465 465 },
466 466 };
467 467 };
468 468
469 469 KeyboardManager.prototype.bind_events = function () {
470 470 var that = this;
471 471 $(document).keydown(function (event) {
472 472 return that.handle_keydown(event);
473 473 });
474 474 };
475 475
476 476 KeyboardManager.prototype.handle_keydown = function (event) {
477 477 var notebook = this.notebook;
478 478
479 479 if (event.which === keycodes.esc) {
480 480 // Intercept escape at highest level to avoid closing
481 481 // websocket connection with firefox
482 482 event.preventDefault();
483 483 }
484 484
485 485 if (!this.enabled) {
486 486 if (event.which === keycodes.esc) {
487 487 // ESC
488 488 notebook.command_mode();
489 489 return false;
490 490 }
491 491 return true;
492 492 }
493 493
494 494 if (this.mode === 'edit') {
495 495 return this.edit_shortcuts.call_handler(event);
496 496 } else if (this.mode === 'command') {
497 497 return this.command_shortcuts.call_handler(event);
498 498 }
499 499 return true;
500 500 };
501 501
502 502 KeyboardManager.prototype.edit_mode = function () {
503 503 this.last_mode = this.mode;
504 504 this.mode = 'edit';
505 505 };
506 506
507 507 KeyboardManager.prototype.command_mode = function () {
508 508 this.last_mode = this.mode;
509 509 this.mode = 'command';
510 510 };
511 511
512 512 KeyboardManager.prototype.enable = function () {
513 513 this.enabled = true;
514 514 };
515 515
516 516 KeyboardManager.prototype.disable = function () {
517 517 this.enabled = false;
518 518 };
519 519
520 520 KeyboardManager.prototype.register_events = function (e) {
521 521 var that = this;
522 522 var handle_focus = function () {
523 523 that.disable();
524 524 };
525 525 var handle_blur = function () {
526 526 that.enable();
527 527 };
528 528 e.on('focusin', handle_focus);
529 529 e.on('focusout', handle_blur);
530 530 // TODO: Very strange. The focusout event does not seem fire for the
531 531 // bootstrap textboxes on FF25&26... This works around that by
532 532 // registering focus and blur events recursively on all inputs within
533 533 // registered element.
534 534 e.find('input').blur(handle_blur);
535 535 e.on('DOMNodeInserted', function (event) {
536 536 var target = $(event.target);
537 537 if (target.is('input')) {
538 538 target.blur(handle_blur);
539 539 } else {
540 540 target.find('input').blur(handle_blur);
541 541 }
542 542 });
543 543 // There are times (raw_input) where we remove the element from the DOM before
544 544 // focusout is called. In this case we bind to the remove event of jQueryUI,
545 545 // which gets triggered upon removal, iff it is focused at the time.
546 546 // is_focused must be used to check for the case where an element within
547 547 // the element being removed is focused.
548 548 e.on('remove', function () {
549 549 if (utils.is_focused(e[0])) {
550 550 that.enable();
551 551 }
552 552 });
553 553 };
554 554
555 555 // For backwards compatability.
556 556 IPython.KeyboardManager = KeyboardManager;
557 557
558 return KeyboardManager;
559
558 return {'KeyboardManager': KeyboardManager};
560 559 });
@@ -1,58 +1,58 b''
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 4 define([
5 5 'base/js/namespace',
6 6 'jquery',
7 7 ], function(IPython, $) {
8 8 "use strict";
9 9
10 10 var LayoutManager = function () {
11 11 this.bind_events();
12 12 this.pager = undefined;
13 13 };
14 14
15 15 LayoutManager.prototype.bind_events = function () {
16 16 $(window).resize($.proxy(this.do_resize,this));
17 17 };
18 18
19 19 LayoutManager.prototype.app_height = function() {
20 20 var win = $(window);
21 21 var w = win.width();
22 22 var h = win.height();
23 23 var header_height;
24 24 if ($('div#header').css('display') === 'none') {
25 25 header_height = 0;
26 26 } else {
27 27 header_height = $('div#header').outerHeight(true);
28 28 }
29 29 var menubar_height;
30 30 if ($('div#menubar-container').css('display') === 'none') {
31 31 menubar_height = 0;
32 32 } else {
33 33 menubar_height = $('div#menubar-container').outerHeight(true);
34 34 }
35 35 return h-header_height-menubar_height; // content height
36 36 };
37 37
38 38 LayoutManager.prototype.do_resize = function () {
39 39 var app_height = this.app_height(); // content height
40 40
41 41 $('#ipython-main-app').height(app_height); // content+padding+border height
42 42 if (this.pager) {
43 43 var pager_height = this.pager.percentage_height*app_height;
44 44 var pager_splitter_height = $('div#pager_splitter').outerHeight(true);
45 45 $('div#pager').outerHeight(pager_height);
46 46 if (this.pager.expanded) {
47 47 $('div#notebook').outerHeight(app_height-pager_height-pager_splitter_height);
48 48 } else {
49 49 $('div#notebook').outerHeight(app_height-pager_splitter_height);
50 50 }
51 51 }
52 52 };
53 53
54 54 // Backwards compatability.
55 55 IPython.LayoutManager = LayoutManager;
56 56
57 return LayoutManager;
57 return {'LayoutManager': LayoutManager};
58 58 });
@@ -1,220 +1,220 b''
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 4 define([
5 5 'base/js/namespace',
6 6 'jquery',
7 7 'notebook/js/toolbar',
8 8 'notebook/js/celltoolbar',
9 9 ], function(IPython, $, Toolbar, CellToolbar) {
10 10 "use strict";
11 11
12 12 var MainToolBar = function (selector, layout_manager, notebook, events) {
13 13 Toolbar.apply(this, arguments);
14 14 this.events = events;
15 15 this.notebook = notebook;
16 16 this.construct();
17 17 this.add_celltype_list();
18 18 this.add_celltoolbar_list();
19 19 this.bind_events();
20 20 };
21 21
22 22 MainToolBar.prototype = new Toolbar();
23 23
24 24 MainToolBar.prototype.construct = function () {
25 25 this.add_buttons_group([
26 26 {
27 27 id : 'save_b',
28 28 label : 'Save and Checkpoint',
29 29 icon : 'icon-save',
30 30 callback : function () {
31 31 this.notebook.save_checkpoint();
32 32 }
33 33 }
34 34 ]);
35 35
36 36 this.add_buttons_group([
37 37 {
38 38 id : 'insert_below_b',
39 39 label : 'Insert Cell Below',
40 40 icon : 'icon-plus-sign',
41 41 callback : function () {
42 42 this.notebook.insert_cell_below('code');
43 43 this.notebook.select_next();
44 44 this.notebook.focus_cell();
45 45 }
46 46 }
47 47 ],'insert_above_below');
48 48
49 49 this.add_buttons_group([
50 50 {
51 51 id : 'cut_b',
52 52 label : 'Cut Cell',
53 53 icon : 'icon-cut',
54 54 callback : function () {
55 55 this.notebook.cut_cell();
56 56 }
57 57 },
58 58 {
59 59 id : 'copy_b',
60 60 label : 'Copy Cell',
61 61 icon : 'icon-copy',
62 62 callback : function () {
63 63 this.notebook.copy_cell();
64 64 }
65 65 },
66 66 {
67 67 id : 'paste_b',
68 68 label : 'Paste Cell Below',
69 69 icon : 'icon-paste',
70 70 callback : function () {
71 71 this.notebook.paste_cell_below();
72 72 }
73 73 }
74 74 ],'cut_copy_paste');
75 75
76 76 this.add_buttons_group([
77 77 {
78 78 id : 'move_up_b',
79 79 label : 'Move Cell Up',
80 80 icon : 'icon-arrow-up',
81 81 callback : function () {
82 82 this.notebook.move_cell_up();
83 83 }
84 84 },
85 85 {
86 86 id : 'move_down_b',
87 87 label : 'Move Cell Down',
88 88 icon : 'icon-arrow-down',
89 89 callback : function () {
90 90 this.notebook.move_cell_down();
91 91 }
92 92 }
93 93 ],'move_up_down');
94 94
95 95
96 96 this.add_buttons_group([
97 97 {
98 98 id : 'run_b',
99 99 label : 'Run Cell',
100 100 icon : 'icon-play',
101 101 callback : function () {
102 102 // emulate default shift-enter behavior
103 103 this.notebook.execute_cell_and_select_below();
104 104 }
105 105 },
106 106 {
107 107 id : 'interrupt_b',
108 108 label : 'Interrupt',
109 109 icon : 'icon-stop',
110 110 callback : function () {
111 111 this.notebook.session.interrupt_kernel();
112 112 }
113 113 },
114 114 {
115 115 id : 'repeat_b',
116 116 label : 'Restart Kernel',
117 117 icon : 'icon-repeat',
118 118 callback : function () {
119 119 this.notebook.restart_kernel();
120 120 }
121 121 }
122 122 ],'run_int');
123 123 };
124 124
125 125 MainToolBar.prototype.add_celltype_list = function () {
126 126 this.element
127 127 .append($('<select/>')
128 128 .attr('id','cell_type')
129 129 .addClass('form-control select-xs')
130 130 // .addClass('ui-widget-content')
131 131 .append($('<option/>').attr('value','code').text('Code'))
132 132 .append($('<option/>').attr('value','markdown').text('Markdown'))
133 133 .append($('<option/>').attr('value','raw').text('Raw NBConvert'))
134 134 .append($('<option/>').attr('value','heading1').text('Heading 1'))
135 135 .append($('<option/>').attr('value','heading2').text('Heading 2'))
136 136 .append($('<option/>').attr('value','heading3').text('Heading 3'))
137 137 .append($('<option/>').attr('value','heading4').text('Heading 4'))
138 138 .append($('<option/>').attr('value','heading5').text('Heading 5'))
139 139 .append($('<option/>').attr('value','heading6').text('Heading 6'))
140 140 );
141 141 };
142 142
143 143
144 144 MainToolBar.prototype.add_celltoolbar_list = function () {
145 145 var label = $('<span/>').addClass("navbar-text").text('Cell Toolbar:');
146 146 var select = $('<select/>')
147 147 // .addClass('ui-widget-content')
148 148 .attr('id', 'ctb_select')
149 149 .addClass('form-control select-xs')
150 150 .append($('<option/>').attr('value', '').text('None'));
151 151 this.element.append(label).append(select);
152 152 select.change(function() {
153 153 var val = $(this).val();
154 154 if (val ==='') {
155 155 CellToolbar.global_hide();
156 156 delete this.notebook.metadata.celltoolbar;
157 157 } else {
158 158 CellToolbar.global_show();
159 159 CellToolbar.activate_preset(val);
160 160 this.notebook.metadata.celltoolbar = val;
161 161 }
162 162 });
163 163 // Setup the currently registered presets.
164 164 var presets = CellToolbar.list_presets();
165 165 for (var i=0; i<presets.length; i++) {
166 166 var name = presets[i];
167 167 select.append($('<option/>').attr('value', name).text(name));
168 168 }
169 169 // Setup future preset registrations.
170 170 this.events.on('preset_added.CellToolbar', function (event, data) {
171 171 var name = data.name;
172 172 select.append($('<option/>').attr('value', name).text(name));
173 173 });
174 174 // Update select value when a preset is activated.
175 175 this.events.on('preset_activated.CellToolbar', function (event, data) {
176 176 if (select.val() !== data.name)
177 177 select.val(data.name);
178 178 });
179 179 };
180 180
181 181
182 182 MainToolBar.prototype.bind_events = function () {
183 183 var that = this;
184 184
185 185 this.element.find('#cell_type').change(function () {
186 186 var cell_type = $(this).val();
187 187 if (cell_type === 'code') {
188 188 this.notebook.to_code();
189 189 } else if (cell_type === 'markdown') {
190 190 this.notebook.to_markdown();
191 191 } else if (cell_type === 'raw') {
192 192 this.notebook.to_raw();
193 193 } else if (cell_type === 'heading1') {
194 194 this.notebook.to_heading(undefined, 1);
195 195 } else if (cell_type === 'heading2') {
196 196 this.notebook.to_heading(undefined, 2);
197 197 } else if (cell_type === 'heading3') {
198 198 this.notebook.to_heading(undefined, 3);
199 199 } else if (cell_type === 'heading4') {
200 200 this.notebook.to_heading(undefined, 4);
201 201 } else if (cell_type === 'heading5') {
202 202 this.notebook.to_heading(undefined, 5);
203 203 } else if (cell_type === 'heading6') {
204 204 this.notebook.to_heading(undefined, 6);
205 205 }
206 206 });
207 207 this.events.on('selected_cell_type_changed.Notebook', function (event, data) {
208 208 if (data.cell_type === 'heading') {
209 209 that.element.find('#cell_type').val(data.cell_type+data.level);
210 210 } else {
211 211 that.element.find('#cell_type').val(data.cell_type);
212 212 }
213 213 });
214 214 };
215 215
216 216 // Backwards compatability.
217 217 IPython.MainToolBar = MainToolBar;
218 218
219 return MainToolBar;
219 return {'MainToolBar': MainToolBar};
220 220 });
@@ -1,347 +1,347 b''
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 4 define([
5 5 'base/js/namespace',
6 6 'jquery',
7 7 'base/js/utils',
8 8 'notebook/js/tour',
9 9 'components/bootstrap-tour/build/js/bootstrap-tour.min',
10 10 ], function(IPython, $, utils, Tour) {
11 11 "use strict";
12 12
13 13 /**
14 14 * A MenuBar Class to generate the menubar of IPython notebook
15 15 * @Class MenuBar
16 16 *
17 17 * @constructor
18 18 *
19 19 *
20 20 * @param selector {string} selector for the menubar element in DOM
21 21 * @param {object} [options]
22 22 * @param [options.base_url] {String} String to use for the
23 23 * base project url. Default is to inspect
24 24 * $('body').data('baseUrl');
25 25 * does not support change for now is set through this option
26 26 */
27 27 var MenuBar = function (selector, options, notebook, layout_manager, events, save_widget, quick_help) {
28 28 options = options || {};
29 29 this.base_url = options.base_url || utils.get_body_data("baseUrl");
30 30 this.selector = selector;
31 31 this.notebook = notebook;
32 32 this.layout_manager = layout_manager;
33 33 this.events = events;
34 34 this.save_widget = save_widget;
35 35 this.quick_help = quick_help;
36 36
37 37 try {
38 38 this.tour = new Tour(notebook, events);
39 39 } catch (e) {
40 40 this.tour = undefined;
41 41 console.log("Failed to instantiate Notebook Tour", e);
42 42 }
43 43
44 44 if (this.selector !== undefined) {
45 45 this.element = $(selector);
46 46 this.style();
47 47 this.bind_events();
48 48 }
49 49 };
50 50
51 51 MenuBar.prototype.style = function () {
52 52 var that = this;
53 53 this.element.addClass('border-box-sizing');
54 54 this.element.find("li").click(function (event, ui) {
55 55 // The selected cell loses focus when the menu is entered, so we
56 56 // re-select it upon selection.
57 57 var i = that.notebook.get_selected_index();
58 58 that.notebook.select(i);
59 59 }
60 60 );
61 61 };
62 62
63 63 MenuBar.prototype._nbconvert = function (format, download) {
64 64 download = download || false;
65 65 var notebook_path = this.notebook.notebook_path;
66 66 var notebook_name = this.notebook.notebook_name;
67 67 if (this.notebook.dirty) {
68 68 this.notebook.save_notebook({async : false});
69 69 }
70 70 var url = utils.url_join_encode(
71 71 this.base_url,
72 72 'nbconvert',
73 73 format,
74 74 notebook_path,
75 75 notebook_name
76 76 ) + "?download=" + download.toString();
77 77
78 78 window.open(url);
79 79 };
80 80
81 81 MenuBar.prototype.bind_events = function () {
82 82 // File
83 83 var that = this;
84 84 this.element.find('#new_notebook').click(function () {
85 85 that.notebook.new_notebook();
86 86 });
87 87 this.element.find('#open_notebook').click(function () {
88 88 window.open(utils.url_join_encode(
89 89 that.notebook.base_url,
90 90 'tree',
91 91 that.notebook.notebook_path
92 92 ));
93 93 });
94 94 this.element.find('#copy_notebook').click(function () {
95 95 that.notebook.copy_notebook();
96 96 return false;
97 97 });
98 98 this.element.find('#download_ipynb').click(function () {
99 99 var base_url = that.notebook.base_url;
100 100 var notebook_path = that.notebook.notebook_path;
101 101 var notebook_name = that.notebook.notebook_name;
102 102 if (that.notebook.dirty) {
103 103 that.notebook.save_notebook({async : false});
104 104 }
105 105
106 106 var url = utils.url_join_encode(
107 107 base_url,
108 108 'files',
109 109 notebook_path,
110 110 notebook_name
111 111 );
112 112 window.location.assign(url);
113 113 });
114 114
115 115 this.element.find('#print_preview').click(function () {
116 116 that._nbconvert('html', false);
117 117 });
118 118
119 119 this.element.find('#download_py').click(function () {
120 120 that._nbconvert('python', true);
121 121 });
122 122
123 123 this.element.find('#download_html').click(function () {
124 124 that._nbconvert('html', true);
125 125 });
126 126
127 127 this.element.find('#download_rst').click(function () {
128 128 that._nbconvert('rst', true);
129 129 });
130 130
131 131 this.element.find('#download_pdf').click(function () {
132 132 that._nbconvert('pdf', true);
133 133 });
134 134
135 135 this.element.find('#rename_notebook').click(function () {
136 136 that.save_widget.rename_notebook();
137 137 });
138 138 this.element.find('#save_checkpoint').click(function () {
139 139 that.notebook.save_checkpoint();
140 140 });
141 141 this.element.find('#restore_checkpoint').click(function () {
142 142 });
143 143 this.element.find('#trust_notebook').click(function () {
144 144 that.notebook.trust_notebook();
145 145 });
146 146 this.events.on('trust_changed.Notebook', function (event, trusted) {
147 147 if (trusted) {
148 148 that.element.find('#trust_notebook')
149 149 .addClass("disabled")
150 150 .find("a").text("Trusted Notebook");
151 151 } else {
152 152 that.element.find('#trust_notebook')
153 153 .removeClass("disabled")
154 154 .find("a").text("Trust Notebook");
155 155 }
156 156 });
157 157 this.element.find('#kill_and_exit').click(function () {
158 158 that.notebook.session.delete();
159 159 setTimeout(function(){
160 160 // allow closing of new tabs in Chromium, impossible in FF
161 161 window.open('', '_self', '');
162 162 window.close();
163 163 }, 500);
164 164 });
165 165 // Edit
166 166 this.element.find('#cut_cell').click(function () {
167 167 that.notebook.cut_cell();
168 168 });
169 169 this.element.find('#copy_cell').click(function () {
170 170 that.notebook.copy_cell();
171 171 });
172 172 this.element.find('#delete_cell').click(function () {
173 173 that.notebook.delete_cell();
174 174 });
175 175 this.element.find('#undelete_cell').click(function () {
176 176 that.notebook.undelete_cell();
177 177 });
178 178 this.element.find('#split_cell').click(function () {
179 179 that.notebook.split_cell();
180 180 });
181 181 this.element.find('#merge_cell_above').click(function () {
182 182 that.notebook.merge_cell_above();
183 183 });
184 184 this.element.find('#merge_cell_below').click(function () {
185 185 that.notebook.merge_cell_below();
186 186 });
187 187 this.element.find('#move_cell_up').click(function () {
188 188 that.notebook.move_cell_up();
189 189 });
190 190 this.element.find('#move_cell_down').click(function () {
191 191 that.notebook.move_cell_down();
192 192 });
193 193 this.element.find('#edit_nb_metadata').click(function () {
194 194 that.notebook.edit_metadata();
195 195 });
196 196
197 197 // View
198 198 this.element.find('#toggle_header').click(function () {
199 199 $('div#header').toggle();
200 200 that.layout_manager.do_resize();
201 201 });
202 202 this.element.find('#toggle_toolbar').click(function () {
203 203 $('div#maintoolbar').toggle();
204 204 that.layout_manager.do_resize();
205 205 });
206 206 // Insert
207 207 this.element.find('#insert_cell_above').click(function () {
208 208 that.notebook.insert_cell_above('code');
209 209 that.notebook.select_prev();
210 210 });
211 211 this.element.find('#insert_cell_below').click(function () {
212 212 that.notebook.insert_cell_below('code');
213 213 that.notebook.select_next();
214 214 });
215 215 // Cell
216 216 this.element.find('#run_cell').click(function () {
217 217 that.notebook.execute_cell();
218 218 });
219 219 this.element.find('#run_cell_select_below').click(function () {
220 220 that.notebook.execute_cell_and_select_below();
221 221 });
222 222 this.element.find('#run_cell_insert_below').click(function () {
223 223 that.notebook.execute_cell_and_insert_below();
224 224 });
225 225 this.element.find('#run_all_cells').click(function () {
226 226 that.notebook.execute_all_cells();
227 227 });
228 228 this.element.find('#run_all_cells_above').click(function () {
229 229 that.notebook.execute_cells_above();
230 230 });
231 231 this.element.find('#run_all_cells_below').click(function () {
232 232 that.notebook.execute_cells_below();
233 233 });
234 234 this.element.find('#to_code').click(function () {
235 235 that.notebook.to_code();
236 236 });
237 237 this.element.find('#to_markdown').click(function () {
238 238 that.notebook.to_markdown();
239 239 });
240 240 this.element.find('#to_raw').click(function () {
241 241 that.notebook.to_raw();
242 242 });
243 243 this.element.find('#to_heading1').click(function () {
244 244 that.notebook.to_heading(undefined, 1);
245 245 });
246 246 this.element.find('#to_heading2').click(function () {
247 247 that.notebook.to_heading(undefined, 2);
248 248 });
249 249 this.element.find('#to_heading3').click(function () {
250 250 that.notebook.to_heading(undefined, 3);
251 251 });
252 252 this.element.find('#to_heading4').click(function () {
253 253 that.notebook.to_heading(undefined, 4);
254 254 });
255 255 this.element.find('#to_heading5').click(function () {
256 256 that.notebook.to_heading(undefined, 5);
257 257 });
258 258 this.element.find('#to_heading6').click(function () {
259 259 that.notebook.to_heading(undefined, 6);
260 260 });
261 261
262 262 this.element.find('#toggle_current_output').click(function () {
263 263 that.notebook.toggle_output();
264 264 });
265 265 this.element.find('#toggle_current_output_scroll').click(function () {
266 266 that.notebook.toggle_output_scroll();
267 267 });
268 268 this.element.find('#clear_current_output').click(function () {
269 269 that.notebook.clear_output();
270 270 });
271 271
272 272 this.element.find('#toggle_all_output').click(function () {
273 273 that.notebook.toggle_all_output();
274 274 });
275 275 this.element.find('#toggle_all_output_scroll').click(function () {
276 276 that.notebook.toggle_all_output_scroll();
277 277 });
278 278 this.element.find('#clear_all_output').click(function () {
279 279 that.notebook.clear_all_output();
280 280 });
281 281
282 282 // Kernel
283 283 this.element.find('#int_kernel').click(function () {
284 284 that.notebook.session.interrupt_kernel();
285 285 });
286 286 this.element.find('#restart_kernel').click(function () {
287 287 that.notebook.restart_kernel();
288 288 });
289 289 // Help
290 290 if (this.tour) {
291 291 this.element.find('#notebook_tour').click(function () {
292 292 that.tour.start();
293 293 });
294 294 } else {
295 295 this.element.find('#notebook_tour').addClass("disabled");
296 296 }
297 297 this.element.find('#keyboard_shortcuts').click(function () {
298 298 that.quick_help.show_keyboard_shortcuts();
299 299 });
300 300
301 301 this.update_restore_checkpoint(null);
302 302
303 303 this.events.on('checkpoints_listed.Notebook', function (event, data) {
304 304 that.update_restore_checkpoint(that.notebook.checkpoints);
305 305 });
306 306
307 307 this.events.on('checkpoint_created.Notebook', function (event, data) {
308 308 that.update_restore_checkpoint(that.notebook.checkpoints);
309 309 });
310 310 };
311 311
312 312 MenuBar.prototype.update_restore_checkpoint = function(checkpoints) {
313 313 var ul = this.element.find("#restore_checkpoint").find("ul");
314 314 ul.empty();
315 315 if (!checkpoints || checkpoints.length === 0) {
316 316 ul.append(
317 317 $("<li/>")
318 318 .addClass("disabled")
319 319 .append(
320 320 $("<a/>")
321 321 .text("No checkpoints")
322 322 )
323 323 );
324 324 return;
325 325 }
326 326
327 327 var that = this;
328 328 checkpoints.map(function (checkpoint) {
329 329 var d = new Date(checkpoint.last_modified);
330 330 ul.append(
331 331 $("<li/>").append(
332 332 $("<a/>")
333 333 .attr("href", "#")
334 334 .text(d.format("mmm dd HH:MM:ss"))
335 335 .click(function () {
336 336 that.notebook.restore_checkpoint_dialog(checkpoint);
337 337 })
338 338 )
339 339 );
340 340 });
341 341 };
342 342
343 343 // Backwards compatability.
344 344 IPython.MenuBar = MenuBar;
345 345
346 return MenuBar;
346 return {'MenuBar': MenuBar};
347 347 });
@@ -1,2481 +1,2481 b''
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 4 define([
5 5 'base/js/namespace',
6 6 'jquery',
7 7 'base/js/utils',
8 8 'base/js/dialog',
9 9 'notebook/js/textcell',
10 10 'notebook/js/codecell',
11 11 'services/sessions/js/session',
12 12 'notebook/js/celltoolbar',
13 13 'components/marked/lib/marked',
14 14 'notebook/js/mathjaxutils',
15 15 'base/js/keyboard',
16 16 'components/jquery-ui/ui/minified/jquery-ui.min',
17 17 'components/bootstrap/js/bootstrap.min',
18 18 ], function (
19 19 IPython,
20 20 $,
21 21 utils,
22 22 Dialog,
23 23 Cells,
24 24 CodeCell,
25 25 Session,
26 26 CellToolbar,
27 27 marked,
28 28 mathjaxutils,
29 29 keyboard
30 30 ) {
31 31
32 32 /**
33 33 * A notebook contains and manages cells.
34 34 *
35 35 * @class Notebook
36 36 * @constructor
37 37 * @param {String} selector A jQuery selector for the notebook's DOM element
38 38 * @param {Object} [options] A config object
39 39 * @param {Object} [events] An events object
40 40 */
41 41 var Notebook = function (selector, options, events, keyboard_manager, save_widget, config) {
42 42 this.config = config;
43 43 this.events = events;
44 44 this.keyboard_manager = keyboard_manager;
45 45 keyboard_manager.notebook = this;
46 46 this.save_widget = save_widget;
47 47 save_widget.notebook = this;
48 48
49 49 mathjaxutils.init();
50 50
51 51
52 52 window.marked = window.marked || marked;
53 53 if (marked) {
54 54 marked.setOptions({
55 55 gfm : true,
56 56 tables: true,
57 57 langPrefix: "language-",
58 58 highlight: function(code, lang) {
59 59 if (!lang) {
60 60 // no language, no highlight
61 61 return code;
62 62 }
63 63 var highlighted;
64 64 try {
65 65 highlighted = hljs.highlight(lang, code, false);
66 66 } catch(err) {
67 67 highlighted = hljs.highlightAuto(code);
68 68 }
69 69 return highlighted.value;
70 70 }
71 71 });
72 72 }
73 73
74 74 // Backwards compatability.
75 75 IPython.keyboard_manager = this.keyboard_manager;
76 76 IPython.save_widget = this.save_widget;
77 77 IPython.keyboard = this.keyboard;
78 78
79 79 this.options = options = options || {};
80 80 this.base_url = options.base_url;
81 81 this.notebook_path = options.notebook_path;
82 82 this.notebook_name = options.notebook_name;
83 83 this.element = $(selector);
84 84 this.element.scroll();
85 85 this.element.data("notebook", this);
86 86 this.next_prompt_number = 1;
87 87 this.session = null;
88 88 this.kernel = null;
89 89 this.clipboard = null;
90 90 this.undelete_backup = null;
91 91 this.undelete_index = null;
92 92 this.undelete_below = false;
93 93 this.paste_enabled = false;
94 94 // It is important to start out in command mode to match the intial mode
95 95 // of the KeyboardManager.
96 96 this.mode = 'command';
97 97 this.set_dirty(false);
98 98 this.metadata = {};
99 99 this._checkpoint_after_save = false;
100 100 this.last_checkpoint = null;
101 101 this.checkpoints = [];
102 102 this.autosave_interval = 0;
103 103 this.autosave_timer = null;
104 104 // autosave *at most* every two minutes
105 105 this.minimum_autosave_interval = 120000;
106 106 // single worksheet for now
107 107 this.worksheet_metadata = {};
108 108 this.notebook_name_blacklist_re = /[\/\\:]/;
109 109 this.nbformat = 3; // Increment this when changing the nbformat
110 110 this.nbformat_minor = 0; // Increment this when changing the nbformat
111 111 this.style();
112 112 this.create_elements();
113 113 this.bind_events();
114 114 this.save_notebook = function() { // don't allow save until notebook_loaded
115 115 this.save_notebook_error(null, null, "Load failed, save is disabled");
116 116 };
117 117 };
118 118
119 119 /**
120 120 * Tweak the notebook's CSS style.
121 121 *
122 122 * @method style
123 123 */
124 124 Notebook.prototype.style = function () {
125 125 $('div#notebook').addClass('border-box-sizing');
126 126 };
127 127
128 128 /**
129 129 * Create an HTML and CSS representation of the notebook.
130 130 *
131 131 * @method create_elements
132 132 */
133 133 Notebook.prototype.create_elements = function () {
134 134 var that = this;
135 135 this.element.attr('tabindex','-1');
136 136 this.container = $("<div/>").addClass("container").attr("id", "notebook-container");
137 137 // We add this end_space div to the end of the notebook div to:
138 138 // i) provide a margin between the last cell and the end of the notebook
139 139 // ii) to prevent the div from scrolling up when the last cell is being
140 140 // edited, but is too low on the page, which browsers will do automatically.
141 141 var end_space = $('<div/>').addClass('end_space');
142 142 end_space.dblclick(function (e) {
143 143 var ncells = that.ncells();
144 144 that.insert_cell_below('code',ncells-1);
145 145 });
146 146 this.element.append(this.container);
147 147 this.container.append(end_space);
148 148 };
149 149
150 150 /**
151 151 * Bind JavaScript events: key presses and custom IPython events.
152 152 *
153 153 * @method bind_events
154 154 */
155 155 Notebook.prototype.bind_events = function () {
156 156 var that = this;
157 157
158 158 this.events.on('set_next_input.Notebook', function (event, data) {
159 159 var index = that.find_cell_index(data.cell);
160 160 var new_cell = that.insert_cell_below('code',index);
161 161 new_cell.set_text(data.text);
162 162 that.dirty = true;
163 163 });
164 164
165 165 this.events.on('set_dirty.Notebook', function (event, data) {
166 166 that.dirty = data.value;
167 167 });
168 168
169 169 this.events.on('trust_changed.Notebook', function (event, data) {
170 170 that.trusted = data.value;
171 171 });
172 172
173 173 this.events.on('select.Cell', function (event, data) {
174 174 var index = that.find_cell_index(data.cell);
175 175 that.select(index);
176 176 });
177 177
178 178 this.events.on('edit_mode.Cell', function (event, data) {
179 179 that.handle_edit_mode(data.cell);
180 180 });
181 181
182 182 this.events.on('command_mode.Cell', function (event, data) {
183 183 that.handle_command_mode(data.cell);
184 184 });
185 185
186 186 this.events.on('status_autorestarting.Kernel', function () {
187 187 Dialog.modal({
188 188 title: "Kernel Restarting",
189 189 body: "The kernel appears to have died. It will restart automatically.",
190 190 buttons: {
191 191 OK : {
192 192 class : "btn-primary"
193 193 }
194 194 }
195 195 });
196 196 });
197 197
198 198 var collapse_time = function (time) {
199 199 var app_height = $('#ipython-main-app').height(); // content height
200 200 var splitter_height = $('div#pager_splitter').outerHeight(true);
201 201 var new_height = app_height - splitter_height;
202 202 that.element.animate({height : new_height + 'px'}, time);
203 203 };
204 204
205 205 this.element.bind('collapse_pager', function (event, extrap) {
206 206 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
207 207 collapse_time(time);
208 208 });
209 209
210 210 var expand_time = function (time) {
211 211 var app_height = $('#ipython-main-app').height(); // content height
212 212 var splitter_height = $('div#pager_splitter').outerHeight(true);
213 213 var pager_height = $('div#pager').outerHeight(true);
214 214 var new_height = app_height - pager_height - splitter_height;
215 215 that.element.animate({height : new_height + 'px'}, time);
216 216 };
217 217
218 218 this.element.bind('expand_pager', function (event, extrap) {
219 219 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
220 220 expand_time(time);
221 221 });
222 222
223 223 // Firefox 22 broke $(window).on("beforeunload")
224 224 // I'm not sure why or how.
225 225 window.onbeforeunload = function (e) {
226 226 // TODO: Make killing the kernel configurable.
227 227 var kill_kernel = false;
228 228 if (kill_kernel) {
229 229 that.session.kill_kernel();
230 230 }
231 231 // if we are autosaving, trigger an autosave on nav-away.
232 232 // still warn, because if we don't the autosave may fail.
233 233 if (that.dirty) {
234 234 if ( that.autosave_interval ) {
235 235 // schedule autosave in a timeout
236 236 // this gives you a chance to forcefully discard changes
237 237 // by reloading the page if you *really* want to.
238 238 // the timer doesn't start until you *dismiss* the dialog.
239 239 setTimeout(function () {
240 240 if (that.dirty) {
241 241 that.save_notebook();
242 242 }
243 243 }, 1000);
244 244 return "Autosave in progress, latest changes may be lost.";
245 245 } else {
246 246 return "Unsaved changes will be lost.";
247 247 }
248 248 }
249 249 // Null is the *only* return value that will make the browser not
250 250 // pop up the "don't leave" dialog.
251 251 return null;
252 252 };
253 253 };
254 254
255 255 /**
256 256 * Set the dirty flag, and trigger the set_dirty.Notebook event
257 257 *
258 258 * @method set_dirty
259 259 */
260 260 Notebook.prototype.set_dirty = function (value) {
261 261 if (value === undefined) {
262 262 value = true;
263 263 }
264 264 if (this.dirty == value) {
265 265 return;
266 266 }
267 267 this.events.trigger('set_dirty.Notebook', {value: value});
268 268 };
269 269
270 270 /**
271 271 * Scroll the top of the page to a given cell.
272 272 *
273 273 * @method scroll_to_cell
274 274 * @param {Number} cell_number An index of the cell to view
275 275 * @param {Number} time Animation time in milliseconds
276 276 * @return {Number} Pixel offset from the top of the container
277 277 */
278 278 Notebook.prototype.scroll_to_cell = function (cell_number, time) {
279 279 var cells = this.get_cells();
280 280 time = time || 0;
281 281 cell_number = Math.min(cells.length-1,cell_number);
282 282 cell_number = Math.max(0 ,cell_number);
283 283 var scroll_value = cells[cell_number].element.position().top-cells[0].element.position().top ;
284 284 this.element.animate({scrollTop:scroll_value}, time);
285 285 return scroll_value;
286 286 };
287 287
288 288 /**
289 289 * Scroll to the bottom of the page.
290 290 *
291 291 * @method scroll_to_bottom
292 292 */
293 293 Notebook.prototype.scroll_to_bottom = function () {
294 294 this.element.animate({scrollTop:this.element.get(0).scrollHeight}, 0);
295 295 };
296 296
297 297 /**
298 298 * Scroll to the top of the page.
299 299 *
300 300 * @method scroll_to_top
301 301 */
302 302 Notebook.prototype.scroll_to_top = function () {
303 303 this.element.animate({scrollTop:0}, 0);
304 304 };
305 305
306 306 // Edit Notebook metadata
307 307
308 308 Notebook.prototype.edit_metadata = function () {
309 309 var that = this;
310 310 Dialog.edit_metadata(this.metadata, function (md) {
311 311 that.metadata = md;
312 312 }, 'Notebook');
313 313 };
314 314
315 315 // Cell indexing, retrieval, etc.
316 316
317 317 /**
318 318 * Get all cell elements in the notebook.
319 319 *
320 320 * @method get_cell_elements
321 321 * @return {jQuery} A selector of all cell elements
322 322 */
323 323 Notebook.prototype.get_cell_elements = function () {
324 324 return this.container.children("div.cell");
325 325 };
326 326
327 327 /**
328 328 * Get a particular cell element.
329 329 *
330 330 * @method get_cell_element
331 331 * @param {Number} index An index of a cell to select
332 332 * @return {jQuery} A selector of the given cell.
333 333 */
334 334 Notebook.prototype.get_cell_element = function (index) {
335 335 var result = null;
336 336 var e = this.get_cell_elements().eq(index);
337 337 if (e.length !== 0) {
338 338 result = e;
339 339 }
340 340 return result;
341 341 };
342 342
343 343 /**
344 344 * Try to get a particular cell by msg_id.
345 345 *
346 346 * @method get_msg_cell
347 347 * @param {String} msg_id A message UUID
348 348 * @return {Cell} Cell or null if no cell was found.
349 349 */
350 350 Notebook.prototype.get_msg_cell = function (msg_id) {
351 351 return CodeCell.msg_cells[msg_id] || null;
352 352 };
353 353
354 354 /**
355 355 * Count the cells in this notebook.
356 356 *
357 357 * @method ncells
358 358 * @return {Number} The number of cells in this notebook
359 359 */
360 360 Notebook.prototype.ncells = function () {
361 361 return this.get_cell_elements().length;
362 362 };
363 363
364 364 /**
365 365 * Get all Cell objects in this notebook.
366 366 *
367 367 * @method get_cells
368 368 * @return {Array} This notebook's Cell objects
369 369 */
370 370 // TODO: we are often calling cells as cells()[i], which we should optimize
371 371 // to cells(i) or a new method.
372 372 Notebook.prototype.get_cells = function () {
373 373 return this.get_cell_elements().toArray().map(function (e) {
374 374 return $(e).data("cell");
375 375 });
376 376 };
377 377
378 378 /**
379 379 * Get a Cell object from this notebook.
380 380 *
381 381 * @method get_cell
382 382 * @param {Number} index An index of a cell to retrieve
383 383 * @return {Cell} A particular cell
384 384 */
385 385 Notebook.prototype.get_cell = function (index) {
386 386 var result = null;
387 387 var ce = this.get_cell_element(index);
388 388 if (ce !== null) {
389 389 result = ce.data('cell');
390 390 }
391 391 return result;
392 392 };
393 393
394 394 /**
395 395 * Get the cell below a given cell.
396 396 *
397 397 * @method get_next_cell
398 398 * @param {Cell} cell The provided cell
399 399 * @return {Cell} The next cell
400 400 */
401 401 Notebook.prototype.get_next_cell = function (cell) {
402 402 var result = null;
403 403 var index = this.find_cell_index(cell);
404 404 if (this.is_valid_cell_index(index+1)) {
405 405 result = this.get_cell(index+1);
406 406 }
407 407 return result;
408 408 };
409 409
410 410 /**
411 411 * Get the cell above a given cell.
412 412 *
413 413 * @method get_prev_cell
414 414 * @param {Cell} cell The provided cell
415 415 * @return {Cell} The previous cell
416 416 */
417 417 Notebook.prototype.get_prev_cell = function (cell) {
418 418 // TODO: off-by-one
419 419 // nb.get_prev_cell(nb.get_cell(1)) is null
420 420 var result = null;
421 421 var index = this.find_cell_index(cell);
422 422 if (index !== null && index > 1) {
423 423 result = this.get_cell(index-1);
424 424 }
425 425 return result;
426 426 };
427 427
428 428 /**
429 429 * Get the numeric index of a given cell.
430 430 *
431 431 * @method find_cell_index
432 432 * @param {Cell} cell The provided cell
433 433 * @return {Number} The cell's numeric index
434 434 */
435 435 Notebook.prototype.find_cell_index = function (cell) {
436 436 var result = null;
437 437 this.get_cell_elements().filter(function (index) {
438 438 if ($(this).data("cell") === cell) {
439 439 result = index;
440 440 }
441 441 });
442 442 return result;
443 443 };
444 444
445 445 /**
446 446 * Get a given index , or the selected index if none is provided.
447 447 *
448 448 * @method index_or_selected
449 449 * @param {Number} index A cell's index
450 450 * @return {Number} The given index, or selected index if none is provided.
451 451 */
452 452 Notebook.prototype.index_or_selected = function (index) {
453 453 var i;
454 454 if (index === undefined || index === null) {
455 455 i = this.get_selected_index();
456 456 if (i === null) {
457 457 i = 0;
458 458 }
459 459 } else {
460 460 i = index;
461 461 }
462 462 return i;
463 463 };
464 464
465 465 /**
466 466 * Get the currently selected cell.
467 467 * @method get_selected_cell
468 468 * @return {Cell} The selected cell
469 469 */
470 470 Notebook.prototype.get_selected_cell = function () {
471 471 var index = this.get_selected_index();
472 472 return this.get_cell(index);
473 473 };
474 474
475 475 /**
476 476 * Check whether a cell index is valid.
477 477 *
478 478 * @method is_valid_cell_index
479 479 * @param {Number} index A cell index
480 480 * @return True if the index is valid, false otherwise
481 481 */
482 482 Notebook.prototype.is_valid_cell_index = function (index) {
483 483 if (index !== null && index >= 0 && index < this.ncells()) {
484 484 return true;
485 485 } else {
486 486 return false;
487 487 }
488 488 };
489 489
490 490 /**
491 491 * Get the index of the currently selected cell.
492 492
493 493 * @method get_selected_index
494 494 * @return {Number} The selected cell's numeric index
495 495 */
496 496 Notebook.prototype.get_selected_index = function () {
497 497 var result = null;
498 498 this.get_cell_elements().filter(function (index) {
499 499 if ($(this).data("cell").selected === true) {
500 500 result = index;
501 501 }
502 502 });
503 503 return result;
504 504 };
505 505
506 506
507 507 // Cell selection.
508 508
509 509 /**
510 510 * Programmatically select a cell.
511 511 *
512 512 * @method select
513 513 * @param {Number} index A cell's index
514 514 * @return {Notebook} This notebook
515 515 */
516 516 Notebook.prototype.select = function (index) {
517 517 if (this.is_valid_cell_index(index)) {
518 518 var sindex = this.get_selected_index();
519 519 if (sindex !== null && index !== sindex) {
520 520 // If we are about to select a different cell, make sure we are
521 521 // first in command mode.
522 522 if (this.mode !== 'command') {
523 523 this.command_mode();
524 524 }
525 525 this.get_cell(sindex).unselect();
526 526 }
527 527 var cell = this.get_cell(index);
528 528 cell.select();
529 529 if (cell.cell_type === 'heading') {
530 530 this.events.trigger('selected_cell_type_changed.Notebook',
531 531 {'cell_type':cell.cell_type,level:cell.level}
532 532 );
533 533 } else {
534 534 this.events.trigger('selected_cell_type_changed.Notebook',
535 535 {'cell_type':cell.cell_type}
536 536 );
537 537 }
538 538 }
539 539 return this;
540 540 };
541 541
542 542 /**
543 543 * Programmatically select the next cell.
544 544 *
545 545 * @method select_next
546 546 * @return {Notebook} This notebook
547 547 */
548 548 Notebook.prototype.select_next = function () {
549 549 var index = this.get_selected_index();
550 550 this.select(index+1);
551 551 return this;
552 552 };
553 553
554 554 /**
555 555 * Programmatically select the previous cell.
556 556 *
557 557 * @method select_prev
558 558 * @return {Notebook} This notebook
559 559 */
560 560 Notebook.prototype.select_prev = function () {
561 561 var index = this.get_selected_index();
562 562 this.select(index-1);
563 563 return this;
564 564 };
565 565
566 566
567 567 // Edit/Command mode
568 568
569 569 /**
570 570 * Gets the index of the cell that is in edit mode.
571 571 *
572 572 * @method get_edit_index
573 573 *
574 574 * @return index {int}
575 575 **/
576 576 Notebook.prototype.get_edit_index = function () {
577 577 var result = null;
578 578 this.get_cell_elements().filter(function (index) {
579 579 if ($(this).data("cell").mode === 'edit') {
580 580 result = index;
581 581 }
582 582 });
583 583 return result;
584 584 };
585 585
586 586 /**
587 587 * Handle when a a cell blurs and the notebook should enter command mode.
588 588 *
589 589 * @method handle_command_mode
590 590 * @param [cell] {Cell} Cell to enter command mode on.
591 591 **/
592 592 Notebook.prototype.handle_command_mode = function (cell) {
593 593 if (this.mode !== 'command') {
594 594 cell.command_mode();
595 595 this.mode = 'command';
596 596 this.events.trigger('command_mode.Notebook');
597 597 this.keyboard_manager.command_mode();
598 598 }
599 599 };
600 600
601 601 /**
602 602 * Make the notebook enter command mode.
603 603 *
604 604 * @method command_mode
605 605 **/
606 606 Notebook.prototype.command_mode = function () {
607 607 var cell = this.get_cell(this.get_edit_index());
608 608 if (cell && this.mode !== 'command') {
609 609 // We don't call cell.command_mode, but rather call cell.focus_cell()
610 610 // which will blur and CM editor and trigger the call to
611 611 // handle_command_mode.
612 612 cell.focus_cell();
613 613 }
614 614 };
615 615
616 616 /**
617 617 * Handle when a cell fires it's edit_mode event.
618 618 *
619 619 * @method handle_edit_mode
620 620 * @param [cell] {Cell} Cell to enter edit mode on.
621 621 **/
622 622 Notebook.prototype.handle_edit_mode = function (cell) {
623 623 if (cell && this.mode !== 'edit') {
624 624 cell.edit_mode();
625 625 this.mode = 'edit';
626 626 this.events.trigger('edit_mode.Notebook');
627 627 this.keyboard_manager.edit_mode();
628 628 }
629 629 };
630 630
631 631 /**
632 632 * Make a cell enter edit mode.
633 633 *
634 634 * @method edit_mode
635 635 **/
636 636 Notebook.prototype.edit_mode = function () {
637 637 var cell = this.get_selected_cell();
638 638 if (cell && this.mode !== 'edit') {
639 639 cell.unrender();
640 640 cell.focus_editor();
641 641 }
642 642 };
643 643
644 644 /**
645 645 * Focus the currently selected cell.
646 646 *
647 647 * @method focus_cell
648 648 **/
649 649 Notebook.prototype.focus_cell = function () {
650 650 var cell = this.get_selected_cell();
651 651 if (cell === null) {return;} // No cell is selected
652 652 cell.focus_cell();
653 653 };
654 654
655 655 // Cell movement
656 656
657 657 /**
658 658 * Move given (or selected) cell up and select it.
659 659 *
660 660 * @method move_cell_up
661 661 * @param [index] {integer} cell index
662 662 * @return {Notebook} This notebook
663 663 **/
664 664 Notebook.prototype.move_cell_up = function (index) {
665 665 var i = this.index_or_selected(index);
666 666 if (this.is_valid_cell_index(i) && i > 0) {
667 667 var pivot = this.get_cell_element(i-1);
668 668 var tomove = this.get_cell_element(i);
669 669 if (pivot !== null && tomove !== null) {
670 670 tomove.detach();
671 671 pivot.before(tomove);
672 672 this.select(i-1);
673 673 var cell = this.get_selected_cell();
674 674 cell.focus_cell();
675 675 }
676 676 this.set_dirty(true);
677 677 }
678 678 return this;
679 679 };
680 680
681 681
682 682 /**
683 683 * Move given (or selected) cell down and select it
684 684 *
685 685 * @method move_cell_down
686 686 * @param [index] {integer} cell index
687 687 * @return {Notebook} This notebook
688 688 **/
689 689 Notebook.prototype.move_cell_down = function (index) {
690 690 var i = this.index_or_selected(index);
691 691 if (this.is_valid_cell_index(i) && this.is_valid_cell_index(i+1)) {
692 692 var pivot = this.get_cell_element(i+1);
693 693 var tomove = this.get_cell_element(i);
694 694 if (pivot !== null && tomove !== null) {
695 695 tomove.detach();
696 696 pivot.after(tomove);
697 697 this.select(i+1);
698 698 var cell = this.get_selected_cell();
699 699 cell.focus_cell();
700 700 }
701 701 }
702 702 this.set_dirty();
703 703 return this;
704 704 };
705 705
706 706
707 707 // Insertion, deletion.
708 708
709 709 /**
710 710 * Delete a cell from the notebook.
711 711 *
712 712 * @method delete_cell
713 713 * @param [index] A cell's numeric index
714 714 * @return {Notebook} This notebook
715 715 */
716 716 Notebook.prototype.delete_cell = function (index) {
717 717 var i = this.index_or_selected(index);
718 718 var cell = this.get_selected_cell();
719 719 this.undelete_backup = cell.toJSON();
720 720 $('#undelete_cell').removeClass('disabled');
721 721 if (this.is_valid_cell_index(i)) {
722 722 var old_ncells = this.ncells();
723 723 var ce = this.get_cell_element(i);
724 724 ce.remove();
725 725 if (i === 0) {
726 726 // Always make sure we have at least one cell.
727 727 if (old_ncells === 1) {
728 728 this.insert_cell_below('code');
729 729 }
730 730 this.select(0);
731 731 this.undelete_index = 0;
732 732 this.undelete_below = false;
733 733 } else if (i === old_ncells-1 && i !== 0) {
734 734 this.select(i-1);
735 735 this.undelete_index = i - 1;
736 736 this.undelete_below = true;
737 737 } else {
738 738 this.select(i);
739 739 this.undelete_index = i;
740 740 this.undelete_below = false;
741 741 }
742 742 this.events.trigger('delete.Cell', {'cell': cell, 'index': i});
743 743 this.set_dirty(true);
744 744 }
745 745 return this;
746 746 };
747 747
748 748 /**
749 749 * Restore the most recently deleted cell.
750 750 *
751 751 * @method undelete
752 752 */
753 753 Notebook.prototype.undelete_cell = function() {
754 754 if (this.undelete_backup !== null && this.undelete_index !== null) {
755 755 var current_index = this.get_selected_index();
756 756 if (this.undelete_index < current_index) {
757 757 current_index = current_index + 1;
758 758 }
759 759 if (this.undelete_index >= this.ncells()) {
760 760 this.select(this.ncells() - 1);
761 761 }
762 762 else {
763 763 this.select(this.undelete_index);
764 764 }
765 765 var cell_data = this.undelete_backup;
766 766 var new_cell = null;
767 767 if (this.undelete_below) {
768 768 new_cell = this.insert_cell_below(cell_data.cell_type);
769 769 } else {
770 770 new_cell = this.insert_cell_above(cell_data.cell_type);
771 771 }
772 772 new_cell.fromJSON(cell_data);
773 773 if (this.undelete_below) {
774 774 this.select(current_index+1);
775 775 } else {
776 776 this.select(current_index);
777 777 }
778 778 this.undelete_backup = null;
779 779 this.undelete_index = null;
780 780 }
781 781 $('#undelete_cell').addClass('disabled');
782 782 };
783 783
784 784 /**
785 785 * Insert a cell so that after insertion the cell is at given index.
786 786 *
787 787 * If cell type is not provided, it will default to the type of the
788 788 * currently active cell.
789 789 *
790 790 * Similar to insert_above, but index parameter is mandatory
791 791 *
792 792 * Index will be brought back into the accessible range [0,n]
793 793 *
794 794 * @method insert_cell_at_index
795 795 * @param [type] {string} in ['code','markdown','heading'], defaults to 'code'
796 796 * @param [index] {int} a valid index where to insert cell
797 797 *
798 798 * @return cell {cell|null} created cell or null
799 799 **/
800 800 Notebook.prototype.insert_cell_at_index = function(type, index){
801 801
802 802 var ncells = this.ncells();
803 803 index = Math.min(index,ncells);
804 804 index = Math.max(index,0);
805 805 var cell = null;
806 806 type = type || this.get_selected_cell().cell_type;
807 807
808 808 if (ncells === 0 || this.is_valid_cell_index(index) || index === ncells) {
809 809 if (type === 'code') {
810 810 cell = new CodeCell(this.kernel, this.options, this.events, this.config, this.keyboard_manager, this);
811 811 cell.set_input_prompt();
812 812 } else if (type === 'markdown') {
813 813 cell = new Cells.MarkdownCell(this.options, this.events, this.config, this.keyboard_manager, this);
814 814 } else if (type === 'raw') {
815 815 cell = new Cells.RawCell(this.options, this.events, this.config, this.keyboard_manager, this);
816 816 } else if (type === 'heading') {
817 817 cell = new Cells.HeadingCell(this.options, this.events, this.config, this.keyboard_manager, this);
818 818 }
819 819
820 820 if(this._insert_element_at_index(cell.element,index)) {
821 821 cell.render();
822 822 this.events.trigger('create.Cell', {'cell': cell, 'index': index});
823 823 cell.refresh();
824 824 // We used to select the cell after we refresh it, but there
825 825 // are now cases were this method is called where select is
826 826 // not appropriate. The selection logic should be handled by the
827 827 // caller of the the top level insert_cell methods.
828 828 this.set_dirty(true);
829 829 }
830 830 }
831 831 return cell;
832 832
833 833 };
834 834
835 835 /**
836 836 * Insert an element at given cell index.
837 837 *
838 838 * @method _insert_element_at_index
839 839 * @param element {dom element} a cell element
840 840 * @param [index] {int} a valid index where to inser cell
841 841 * @private
842 842 *
843 843 * return true if everything whent fine.
844 844 **/
845 845 Notebook.prototype._insert_element_at_index = function(element, index){
846 846 if (element === undefined){
847 847 return false;
848 848 }
849 849
850 850 var ncells = this.ncells();
851 851
852 852 if (ncells === 0) {
853 853 // special case append if empty
854 854 this.element.find('div.end_space').before(element);
855 855 } else if ( ncells === index ) {
856 856 // special case append it the end, but not empty
857 857 this.get_cell_element(index-1).after(element);
858 858 } else if (this.is_valid_cell_index(index)) {
859 859 // otherwise always somewhere to append to
860 860 this.get_cell_element(index).before(element);
861 861 } else {
862 862 return false;
863 863 }
864 864
865 865 if (this.undelete_index !== null && index <= this.undelete_index) {
866 866 this.undelete_index = this.undelete_index + 1;
867 867 this.set_dirty(true);
868 868 }
869 869 return true;
870 870 };
871 871
872 872 /**
873 873 * Insert a cell of given type above given index, or at top
874 874 * of notebook if index smaller than 0.
875 875 *
876 876 * default index value is the one of currently selected cell
877 877 *
878 878 * @method insert_cell_above
879 879 * @param [type] {string} cell type
880 880 * @param [index] {integer}
881 881 *
882 882 * @return handle to created cell or null
883 883 **/
884 884 Notebook.prototype.insert_cell_above = function (type, index) {
885 885 index = this.index_or_selected(index);
886 886 return this.insert_cell_at_index(type, index);
887 887 };
888 888
889 889 /**
890 890 * Insert a cell of given type below given index, or at bottom
891 891 * of notebook if index greater than number of cells
892 892 *
893 893 * default index value is the one of currently selected cell
894 894 *
895 895 * @method insert_cell_below
896 896 * @param [type] {string} cell type
897 897 * @param [index] {integer}
898 898 *
899 899 * @return handle to created cell or null
900 900 *
901 901 **/
902 902 Notebook.prototype.insert_cell_below = function (type, index) {
903 903 index = this.index_or_selected(index);
904 904 return this.insert_cell_at_index(type, index+1);
905 905 };
906 906
907 907
908 908 /**
909 909 * Insert cell at end of notebook
910 910 *
911 911 * @method insert_cell_at_bottom
912 912 * @param {String} type cell type
913 913 *
914 914 * @return the added cell; or null
915 915 **/
916 916 Notebook.prototype.insert_cell_at_bottom = function (type){
917 917 var len = this.ncells();
918 918 return this.insert_cell_below(type,len-1);
919 919 };
920 920
921 921 /**
922 922 * Turn a cell into a code cell.
923 923 *
924 924 * @method to_code
925 925 * @param {Number} [index] A cell's index
926 926 */
927 927 Notebook.prototype.to_code = function (index) {
928 928 var i = this.index_or_selected(index);
929 929 if (this.is_valid_cell_index(i)) {
930 930 var source_element = this.get_cell_element(i);
931 931 var source_cell = source_element.data("cell");
932 932 if (!(source_cell instanceof CodeCell)) {
933 933 var target_cell = this.insert_cell_below('code',i);
934 934 var text = source_cell.get_text();
935 935 if (text === source_cell.placeholder) {
936 936 text = '';
937 937 }
938 938 target_cell.set_text(text);
939 939 // make this value the starting point, so that we can only undo
940 940 // to this state, instead of a blank cell
941 941 target_cell.code_mirror.clearHistory();
942 942 source_element.remove();
943 943 this.select(i);
944 944 var cursor = source_cell.code_mirror.getCursor();
945 945 target_cell.code_mirror.setCursor(cursor);
946 946 this.set_dirty(true);
947 947 }
948 948 }
949 949 };
950 950
951 951 /**
952 952 * Turn a cell into a Markdown cell.
953 953 *
954 954 * @method to_markdown
955 955 * @param {Number} [index] A cell's index
956 956 */
957 957 Notebook.prototype.to_markdown = function (index) {
958 958 var i = this.index_or_selected(index);
959 959 if (this.is_valid_cell_index(i)) {
960 960 var source_element = this.get_cell_element(i);
961 961 var source_cell = source_element.data("cell");
962 962 if (!(source_cell instanceof Cells.MarkdownCell)) {
963 963 var target_cell = this.insert_cell_below('markdown',i);
964 964 var text = source_cell.get_text();
965 965 if (text === source_cell.placeholder) {
966 966 text = '';
967 967 }
968 968 // We must show the editor before setting its contents
969 969 target_cell.unrender();
970 970 target_cell.set_text(text);
971 971 // make this value the starting point, so that we can only undo
972 972 // to this state, instead of a blank cell
973 973 target_cell.code_mirror.clearHistory();
974 974 source_element.remove();
975 975 this.select(i);
976 976 if ((source_cell instanceof Cells.TextCell) && source_cell.rendered) {
977 977 target_cell.render();
978 978 }
979 979 var cursor = source_cell.code_mirror.getCursor();
980 980 target_cell.code_mirror.setCursor(cursor);
981 981 this.set_dirty(true);
982 982 }
983 983 }
984 984 };
985 985
986 986 /**
987 987 * Turn a cell into a raw text cell.
988 988 *
989 989 * @method to_raw
990 990 * @param {Number} [index] A cell's index
991 991 */
992 992 Notebook.prototype.to_raw = function (index) {
993 993 var i = this.index_or_selected(index);
994 994 if (this.is_valid_cell_index(i)) {
995 995 var source_element = this.get_cell_element(i);
996 996 var source_cell = source_element.data("cell");
997 997 var target_cell = null;
998 998 if (!(source_cell instanceof Cells.RawCell)) {
999 999 target_cell = this.insert_cell_below('raw',i);
1000 1000 var text = source_cell.get_text();
1001 1001 if (text === source_cell.placeholder) {
1002 1002 text = '';
1003 1003 }
1004 1004 // We must show the editor before setting its contents
1005 1005 target_cell.unrender();
1006 1006 target_cell.set_text(text);
1007 1007 // make this value the starting point, so that we can only undo
1008 1008 // to this state, instead of a blank cell
1009 1009 target_cell.code_mirror.clearHistory();
1010 1010 source_element.remove();
1011 1011 this.select(i);
1012 1012 var cursor = source_cell.code_mirror.getCursor();
1013 1013 target_cell.code_mirror.setCursor(cursor);
1014 1014 this.set_dirty(true);
1015 1015 }
1016 1016 }
1017 1017 };
1018 1018
1019 1019 /**
1020 1020 * Turn a cell into a heading cell.
1021 1021 *
1022 1022 * @method to_heading
1023 1023 * @param {Number} [index] A cell's index
1024 1024 * @param {Number} [level] A heading level (e.g., 1 becomes &lt;h1&gt;)
1025 1025 */
1026 1026 Notebook.prototype.to_heading = function (index, level) {
1027 1027 level = level || 1;
1028 1028 var i = this.index_or_selected(index);
1029 1029 if (this.is_valid_cell_index(i)) {
1030 1030 var source_element = this.get_cell_element(i);
1031 1031 var source_cell = source_element.data("cell");
1032 1032 var target_cell = null;
1033 1033 if (source_cell instanceof Cells.HeadingCell) {
1034 1034 source_cell.set_level(level);
1035 1035 } else {
1036 1036 target_cell = this.insert_cell_below('heading',i);
1037 1037 var text = source_cell.get_text();
1038 1038 if (text === source_cell.placeholder) {
1039 1039 text = '';
1040 1040 }
1041 1041 // We must show the editor before setting its contents
1042 1042 target_cell.set_level(level);
1043 1043 target_cell.unrender();
1044 1044 target_cell.set_text(text);
1045 1045 // make this value the starting point, so that we can only undo
1046 1046 // to this state, instead of a blank cell
1047 1047 target_cell.code_mirror.clearHistory();
1048 1048 source_element.remove();
1049 1049 this.select(i);
1050 1050 var cursor = source_cell.code_mirror.getCursor();
1051 1051 target_cell.code_mirror.setCursor(cursor);
1052 1052 if ((source_cell instanceof Cells.TextCell) && source_cell.rendered) {
1053 1053 target_cell.render();
1054 1054 }
1055 1055 }
1056 1056 this.set_dirty(true);
1057 1057 this.events.trigger('selected_cell_type_changed.Notebook',
1058 1058 {'cell_type':'heading',level:level}
1059 1059 );
1060 1060 }
1061 1061 };
1062 1062
1063 1063
1064 1064 // Cut/Copy/Paste
1065 1065
1066 1066 /**
1067 1067 * Enable UI elements for pasting cells.
1068 1068 *
1069 1069 * @method enable_paste
1070 1070 */
1071 1071 Notebook.prototype.enable_paste = function () {
1072 1072 var that = this;
1073 1073 if (!this.paste_enabled) {
1074 1074 $('#paste_cell_replace').removeClass('disabled')
1075 1075 .on('click', function () {that.paste_cell_replace();});
1076 1076 $('#paste_cell_above').removeClass('disabled')
1077 1077 .on('click', function () {that.paste_cell_above();});
1078 1078 $('#paste_cell_below').removeClass('disabled')
1079 1079 .on('click', function () {that.paste_cell_below();});
1080 1080 this.paste_enabled = true;
1081 1081 }
1082 1082 };
1083 1083
1084 1084 /**
1085 1085 * Disable UI elements for pasting cells.
1086 1086 *
1087 1087 * @method disable_paste
1088 1088 */
1089 1089 Notebook.prototype.disable_paste = function () {
1090 1090 if (this.paste_enabled) {
1091 1091 $('#paste_cell_replace').addClass('disabled').off('click');
1092 1092 $('#paste_cell_above').addClass('disabled').off('click');
1093 1093 $('#paste_cell_below').addClass('disabled').off('click');
1094 1094 this.paste_enabled = false;
1095 1095 }
1096 1096 };
1097 1097
1098 1098 /**
1099 1099 * Cut a cell.
1100 1100 *
1101 1101 * @method cut_cell
1102 1102 */
1103 1103 Notebook.prototype.cut_cell = function () {
1104 1104 this.copy_cell();
1105 1105 this.delete_cell();
1106 1106 };
1107 1107
1108 1108 /**
1109 1109 * Copy a cell.
1110 1110 *
1111 1111 * @method copy_cell
1112 1112 */
1113 1113 Notebook.prototype.copy_cell = function () {
1114 1114 var cell = this.get_selected_cell();
1115 1115 this.clipboard = cell.toJSON();
1116 1116 this.enable_paste();
1117 1117 };
1118 1118
1119 1119 /**
1120 1120 * Replace the selected cell with a cell in the clipboard.
1121 1121 *
1122 1122 * @method paste_cell_replace
1123 1123 */
1124 1124 Notebook.prototype.paste_cell_replace = function () {
1125 1125 if (this.clipboard !== null && this.paste_enabled) {
1126 1126 var cell_data = this.clipboard;
1127 1127 var new_cell = this.insert_cell_above(cell_data.cell_type);
1128 1128 new_cell.fromJSON(cell_data);
1129 1129 var old_cell = this.get_next_cell(new_cell);
1130 1130 this.delete_cell(this.find_cell_index(old_cell));
1131 1131 this.select(this.find_cell_index(new_cell));
1132 1132 }
1133 1133 };
1134 1134
1135 1135 /**
1136 1136 * Paste a cell from the clipboard above the selected cell.
1137 1137 *
1138 1138 * @method paste_cell_above
1139 1139 */
1140 1140 Notebook.prototype.paste_cell_above = function () {
1141 1141 if (this.clipboard !== null && this.paste_enabled) {
1142 1142 var cell_data = this.clipboard;
1143 1143 var new_cell = this.insert_cell_above(cell_data.cell_type);
1144 1144 new_cell.fromJSON(cell_data);
1145 1145 new_cell.focus_cell();
1146 1146 }
1147 1147 };
1148 1148
1149 1149 /**
1150 1150 * Paste a cell from the clipboard below the selected cell.
1151 1151 *
1152 1152 * @method paste_cell_below
1153 1153 */
1154 1154 Notebook.prototype.paste_cell_below = function () {
1155 1155 if (this.clipboard !== null && this.paste_enabled) {
1156 1156 var cell_data = this.clipboard;
1157 1157 var new_cell = this.insert_cell_below(cell_data.cell_type);
1158 1158 new_cell.fromJSON(cell_data);
1159 1159 new_cell.focus_cell();
1160 1160 }
1161 1161 };
1162 1162
1163 1163 // Split/merge
1164 1164
1165 1165 /**
1166 1166 * Split the selected cell into two, at the cursor.
1167 1167 *
1168 1168 * @method split_cell
1169 1169 */
1170 1170 Notebook.prototype.split_cell = function () {
1171 1171 var mdc = Cells.MarkdownCell;
1172 1172 var rc = Cells.RawCell;
1173 1173 var cell = this.get_selected_cell();
1174 1174 if (cell.is_splittable()) {
1175 1175 var texta = cell.get_pre_cursor();
1176 1176 var textb = cell.get_post_cursor();
1177 1177 if (cell instanceof CodeCell) {
1178 1178 // In this case the operations keep the notebook in its existing mode
1179 1179 // so we don't need to do any post-op mode changes.
1180 1180 cell.set_text(textb);
1181 1181 var new_cell = this.insert_cell_above('code');
1182 1182 new_cell.set_text(texta);
1183 1183 } else if ((cell instanceof mdc && !cell.rendered) || (cell instanceof rc)) {
1184 1184 // We know cell is !rendered so we can use set_text.
1185 1185 cell.set_text(textb);
1186 1186 var new_cell = this.insert_cell_above(cell.cell_type);
1187 1187 // Unrender the new cell so we can call set_text.
1188 1188 new_cell.unrender();
1189 1189 new_cell.set_text(texta);
1190 1190 }
1191 1191 }
1192 1192 };
1193 1193
1194 1194 /**
1195 1195 * Combine the selected cell into the cell above it.
1196 1196 *
1197 1197 * @method merge_cell_above
1198 1198 */
1199 1199 Notebook.prototype.merge_cell_above = function () {
1200 1200 var mdc = Cells.MarkdownCell;
1201 1201 var rc = Cells.RawCell;
1202 1202 var index = this.get_selected_index();
1203 1203 var cell = this.get_cell(index);
1204 1204 var render = cell.rendered;
1205 1205 if (!cell.is_mergeable()) {
1206 1206 return;
1207 1207 }
1208 1208 if (index > 0) {
1209 1209 var upper_cell = this.get_cell(index-1);
1210 1210 if (!upper_cell.is_mergeable()) {
1211 1211 return;
1212 1212 }
1213 1213 var upper_text = upper_cell.get_text();
1214 1214 var text = cell.get_text();
1215 1215 if (cell instanceof CodeCell) {
1216 1216 cell.set_text(upper_text+'\n'+text);
1217 1217 } else if ((cell instanceof mdc) || (cell instanceof rc)) {
1218 1218 cell.unrender(); // Must unrender before we set_text.
1219 1219 cell.set_text(upper_text+'\n\n'+text);
1220 1220 if (render) {
1221 1221 // The rendered state of the final cell should match
1222 1222 // that of the original selected cell;
1223 1223 cell.render();
1224 1224 }
1225 1225 }
1226 1226 this.delete_cell(index-1);
1227 1227 this.select(this.find_cell_index(cell));
1228 1228 }
1229 1229 };
1230 1230
1231 1231 /**
1232 1232 * Combine the selected cell into the cell below it.
1233 1233 *
1234 1234 * @method merge_cell_below
1235 1235 */
1236 1236 Notebook.prototype.merge_cell_below = function () {
1237 1237 var mdc = Cells.MarkdownCell;
1238 1238 var rc = Cells.RawCell;
1239 1239 var index = this.get_selected_index();
1240 1240 var cell = this.get_cell(index);
1241 1241 var render = cell.rendered;
1242 1242 if (!cell.is_mergeable()) {
1243 1243 return;
1244 1244 }
1245 1245 if (index < this.ncells()-1) {
1246 1246 var lower_cell = this.get_cell(index+1);
1247 1247 if (!lower_cell.is_mergeable()) {
1248 1248 return;
1249 1249 }
1250 1250 var lower_text = lower_cell.get_text();
1251 1251 var text = cell.get_text();
1252 1252 if (cell instanceof CodeCell) {
1253 1253 cell.set_text(text+'\n'+lower_text);
1254 1254 } else if ((cell instanceof mdc) || (cell instanceof rc)) {
1255 1255 cell.unrender(); // Must unrender before we set_text.
1256 1256 cell.set_text(text+'\n\n'+lower_text);
1257 1257 if (render) {
1258 1258 // The rendered state of the final cell should match
1259 1259 // that of the original selected cell;
1260 1260 cell.render();
1261 1261 }
1262 1262 }
1263 1263 this.delete_cell(index+1);
1264 1264 this.select(this.find_cell_index(cell));
1265 1265 }
1266 1266 };
1267 1267
1268 1268
1269 1269 // Cell collapsing and output clearing
1270 1270
1271 1271 /**
1272 1272 * Hide a cell's output.
1273 1273 *
1274 1274 * @method collapse_output
1275 1275 * @param {Number} index A cell's numeric index
1276 1276 */
1277 1277 Notebook.prototype.collapse_output = function (index) {
1278 1278 var i = this.index_or_selected(index);
1279 1279 var cell = this.get_cell(i);
1280 1280 if (cell !== null && (cell instanceof CodeCell)) {
1281 1281 cell.collapse_output();
1282 1282 this.set_dirty(true);
1283 1283 }
1284 1284 };
1285 1285
1286 1286 /**
1287 1287 * Hide each code cell's output area.
1288 1288 *
1289 1289 * @method collapse_all_output
1290 1290 */
1291 1291 Notebook.prototype.collapse_all_output = function () {
1292 1292 $.map(this.get_cells(), function (cell, i) {
1293 1293 if (cell instanceof CodeCell) {
1294 1294 cell.collapse_output();
1295 1295 }
1296 1296 });
1297 1297 // this should not be set if the `collapse` key is removed from nbformat
1298 1298 this.set_dirty(true);
1299 1299 };
1300 1300
1301 1301 /**
1302 1302 * Show a cell's output.
1303 1303 *
1304 1304 * @method expand_output
1305 1305 * @param {Number} index A cell's numeric index
1306 1306 */
1307 1307 Notebook.prototype.expand_output = function (index) {
1308 1308 var i = this.index_or_selected(index);
1309 1309 var cell = this.get_cell(i);
1310 1310 if (cell !== null && (cell instanceof CodeCell)) {
1311 1311 cell.expand_output();
1312 1312 this.set_dirty(true);
1313 1313 }
1314 1314 };
1315 1315
1316 1316 /**
1317 1317 * Expand each code cell's output area, and remove scrollbars.
1318 1318 *
1319 1319 * @method expand_all_output
1320 1320 */
1321 1321 Notebook.prototype.expand_all_output = function () {
1322 1322 $.map(this.get_cells(), function (cell, i) {
1323 1323 if (cell instanceof CodeCell) {
1324 1324 cell.expand_output();
1325 1325 }
1326 1326 });
1327 1327 // this should not be set if the `collapse` key is removed from nbformat
1328 1328 this.set_dirty(true);
1329 1329 };
1330 1330
1331 1331 /**
1332 1332 * Clear the selected CodeCell's output area.
1333 1333 *
1334 1334 * @method clear_output
1335 1335 * @param {Number} index A cell's numeric index
1336 1336 */
1337 1337 Notebook.prototype.clear_output = function (index) {
1338 1338 var i = this.index_or_selected(index);
1339 1339 var cell = this.get_cell(i);
1340 1340 if (cell !== null && (cell instanceof CodeCell)) {
1341 1341 cell.clear_output();
1342 1342 this.set_dirty(true);
1343 1343 }
1344 1344 };
1345 1345
1346 1346 /**
1347 1347 * Clear each code cell's output area.
1348 1348 *
1349 1349 * @method clear_all_output
1350 1350 */
1351 1351 Notebook.prototype.clear_all_output = function () {
1352 1352 $.map(this.get_cells(), function (cell, i) {
1353 1353 if (cell instanceof CodeCell) {
1354 1354 cell.clear_output();
1355 1355 }
1356 1356 });
1357 1357 this.set_dirty(true);
1358 1358 };
1359 1359
1360 1360 /**
1361 1361 * Scroll the selected CodeCell's output area.
1362 1362 *
1363 1363 * @method scroll_output
1364 1364 * @param {Number} index A cell's numeric index
1365 1365 */
1366 1366 Notebook.prototype.scroll_output = function (index) {
1367 1367 var i = this.index_or_selected(index);
1368 1368 var cell = this.get_cell(i);
1369 1369 if (cell !== null && (cell instanceof CodeCell)) {
1370 1370 cell.scroll_output();
1371 1371 this.set_dirty(true);
1372 1372 }
1373 1373 };
1374 1374
1375 1375 /**
1376 1376 * Expand each code cell's output area, and add a scrollbar for long output.
1377 1377 *
1378 1378 * @method scroll_all_output
1379 1379 */
1380 1380 Notebook.prototype.scroll_all_output = function () {
1381 1381 $.map(this.get_cells(), function (cell, i) {
1382 1382 if (cell instanceof CodeCell) {
1383 1383 cell.scroll_output();
1384 1384 }
1385 1385 });
1386 1386 // this should not be set if the `collapse` key is removed from nbformat
1387 1387 this.set_dirty(true);
1388 1388 };
1389 1389
1390 1390 /** Toggle whether a cell's output is collapsed or expanded.
1391 1391 *
1392 1392 * @method toggle_output
1393 1393 * @param {Number} index A cell's numeric index
1394 1394 */
1395 1395 Notebook.prototype.toggle_output = function (index) {
1396 1396 var i = this.index_or_selected(index);
1397 1397 var cell = this.get_cell(i);
1398 1398 if (cell !== null && (cell instanceof CodeCell)) {
1399 1399 cell.toggle_output();
1400 1400 this.set_dirty(true);
1401 1401 }
1402 1402 };
1403 1403
1404 1404 /**
1405 1405 * Hide/show the output of all cells.
1406 1406 *
1407 1407 * @method toggle_all_output
1408 1408 */
1409 1409 Notebook.prototype.toggle_all_output = function () {
1410 1410 $.map(this.get_cells(), function (cell, i) {
1411 1411 if (cell instanceof CodeCell) {
1412 1412 cell.toggle_output();
1413 1413 }
1414 1414 });
1415 1415 // this should not be set if the `collapse` key is removed from nbformat
1416 1416 this.set_dirty(true);
1417 1417 };
1418 1418
1419 1419 /**
1420 1420 * Toggle a scrollbar for long cell outputs.
1421 1421 *
1422 1422 * @method toggle_output_scroll
1423 1423 * @param {Number} index A cell's numeric index
1424 1424 */
1425 1425 Notebook.prototype.toggle_output_scroll = function (index) {
1426 1426 var i = this.index_or_selected(index);
1427 1427 var cell = this.get_cell(i);
1428 1428 if (cell !== null && (cell instanceof CodeCell)) {
1429 1429 cell.toggle_output_scroll();
1430 1430 this.set_dirty(true);
1431 1431 }
1432 1432 };
1433 1433
1434 1434 /**
1435 1435 * Toggle the scrolling of long output on all cells.
1436 1436 *
1437 1437 * @method toggle_all_output_scrolling
1438 1438 */
1439 1439 Notebook.prototype.toggle_all_output_scroll = function () {
1440 1440 $.map(this.get_cells(), function (cell, i) {
1441 1441 if (cell instanceof CodeCell) {
1442 1442 cell.toggle_output_scroll();
1443 1443 }
1444 1444 });
1445 1445 // this should not be set if the `collapse` key is removed from nbformat
1446 1446 this.set_dirty(true);
1447 1447 };
1448 1448
1449 1449 // Other cell functions: line numbers, ...
1450 1450
1451 1451 /**
1452 1452 * Toggle line numbers in the selected cell's input area.
1453 1453 *
1454 1454 * @method cell_toggle_line_numbers
1455 1455 */
1456 1456 Notebook.prototype.cell_toggle_line_numbers = function() {
1457 1457 this.get_selected_cell().toggle_line_numbers();
1458 1458 };
1459 1459
1460 1460 // Session related things
1461 1461
1462 1462 /**
1463 1463 * Start a new session and set it on each code cell.
1464 1464 *
1465 1465 * @method start_session
1466 1466 */
1467 1467 Notebook.prototype.start_session = function () {
1468 1468 this.session = new Session(this, this.options);
1469 1469 this.session.start($.proxy(this._session_started, this));
1470 1470 };
1471 1471
1472 1472
1473 1473 /**
1474 1474 * Once a session is started, link the code cells to the kernel and pass the
1475 1475 * comm manager to the widget manager
1476 1476 *
1477 1477 */
1478 1478 Notebook.prototype._session_started = function(){
1479 1479 this.kernel = this.session.kernel;
1480 1480 var ncells = this.ncells();
1481 1481 for (var i=0; i<ncells; i++) {
1482 1482 var cell = this.get_cell(i);
1483 1483 if (cell instanceof CodeCell) {
1484 1484 cell.set_kernel(this.session.kernel);
1485 1485 }
1486 1486 }
1487 1487 };
1488 1488
1489 1489 /**
1490 1490 * Prompt the user to restart the IPython kernel.
1491 1491 *
1492 1492 * @method restart_kernel
1493 1493 */
1494 1494 Notebook.prototype.restart_kernel = function () {
1495 1495 var that = this;
1496 1496 Dialog.modal({
1497 1497 title : "Restart kernel or continue running?",
1498 1498 body : $("<p/>").text(
1499 1499 'Do you want to restart the current kernel? You will lose all variables defined in it.'
1500 1500 ),
1501 1501 buttons : {
1502 1502 "Continue running" : {},
1503 1503 "Restart" : {
1504 1504 "class" : "btn-danger",
1505 1505 "click" : function() {
1506 1506 that.session.restart_kernel();
1507 1507 }
1508 1508 }
1509 1509 }
1510 1510 });
1511 1511 };
1512 1512
1513 1513 /**
1514 1514 * Execute or render cell outputs and go into command mode.
1515 1515 *
1516 1516 * @method execute_cell
1517 1517 */
1518 1518 Notebook.prototype.execute_cell = function () {
1519 1519 // mode = shift, ctrl, alt
1520 1520 var cell = this.get_selected_cell();
1521 1521 var cell_index = this.find_cell_index(cell);
1522 1522
1523 1523 cell.execute();
1524 1524 this.command_mode();
1525 1525 this.set_dirty(true);
1526 1526 };
1527 1527
1528 1528 /**
1529 1529 * Execute or render cell outputs and insert a new cell below.
1530 1530 *
1531 1531 * @method execute_cell_and_insert_below
1532 1532 */
1533 1533 Notebook.prototype.execute_cell_and_insert_below = function () {
1534 1534 var cell = this.get_selected_cell();
1535 1535 var cell_index = this.find_cell_index(cell);
1536 1536
1537 1537 cell.execute();
1538 1538
1539 1539 // If we are at the end always insert a new cell and return
1540 1540 if (cell_index === (this.ncells()-1)) {
1541 1541 this.command_mode();
1542 1542 this.insert_cell_below();
1543 1543 this.select(cell_index+1);
1544 1544 this.edit_mode();
1545 1545 this.scroll_to_bottom();
1546 1546 this.set_dirty(true);
1547 1547 return;
1548 1548 }
1549 1549
1550 1550 this.command_mode();
1551 1551 this.insert_cell_below();
1552 1552 this.select(cell_index+1);
1553 1553 this.edit_mode();
1554 1554 this.set_dirty(true);
1555 1555 };
1556 1556
1557 1557 /**
1558 1558 * Execute or render cell outputs and select the next cell.
1559 1559 *
1560 1560 * @method execute_cell_and_select_below
1561 1561 */
1562 1562 Notebook.prototype.execute_cell_and_select_below = function () {
1563 1563
1564 1564 var cell = this.get_selected_cell();
1565 1565 var cell_index = this.find_cell_index(cell);
1566 1566
1567 1567 cell.execute();
1568 1568
1569 1569 // If we are at the end always insert a new cell and return
1570 1570 if (cell_index === (this.ncells()-1)) {
1571 1571 this.command_mode();
1572 1572 this.insert_cell_below();
1573 1573 this.select(cell_index+1);
1574 1574 this.edit_mode();
1575 1575 this.scroll_to_bottom();
1576 1576 this.set_dirty(true);
1577 1577 return;
1578 1578 }
1579 1579
1580 1580 this.command_mode();
1581 1581 this.select(cell_index+1);
1582 1582 this.focus_cell();
1583 1583 this.set_dirty(true);
1584 1584 };
1585 1585
1586 1586 /**
1587 1587 * Execute all cells below the selected cell.
1588 1588 *
1589 1589 * @method execute_cells_below
1590 1590 */
1591 1591 Notebook.prototype.execute_cells_below = function () {
1592 1592 this.execute_cell_range(this.get_selected_index(), this.ncells());
1593 1593 this.scroll_to_bottom();
1594 1594 };
1595 1595
1596 1596 /**
1597 1597 * Execute all cells above the selected cell.
1598 1598 *
1599 1599 * @method execute_cells_above
1600 1600 */
1601 1601 Notebook.prototype.execute_cells_above = function () {
1602 1602 this.execute_cell_range(0, this.get_selected_index());
1603 1603 };
1604 1604
1605 1605 /**
1606 1606 * Execute all cells.
1607 1607 *
1608 1608 * @method execute_all_cells
1609 1609 */
1610 1610 Notebook.prototype.execute_all_cells = function () {
1611 1611 this.execute_cell_range(0, this.ncells());
1612 1612 this.scroll_to_bottom();
1613 1613 };
1614 1614
1615 1615 /**
1616 1616 * Execute a contiguous range of cells.
1617 1617 *
1618 1618 * @method execute_cell_range
1619 1619 * @param {Number} start Index of the first cell to execute (inclusive)
1620 1620 * @param {Number} end Index of the last cell to execute (exclusive)
1621 1621 */
1622 1622 Notebook.prototype.execute_cell_range = function (start, end) {
1623 1623 this.command_mode();
1624 1624 for (var i=start; i<end; i++) {
1625 1625 this.select(i);
1626 1626 this.execute_cell();
1627 1627 }
1628 1628 };
1629 1629
1630 1630 // Persistance and loading
1631 1631
1632 1632 /**
1633 1633 * Getter method for this notebook's name.
1634 1634 *
1635 1635 * @method get_notebook_name
1636 1636 * @return {String} This notebook's name (excluding file extension)
1637 1637 */
1638 1638 Notebook.prototype.get_notebook_name = function () {
1639 1639 var nbname = this.notebook_name.substring(0,this.notebook_name.length-6);
1640 1640 return nbname;
1641 1641 };
1642 1642
1643 1643 /**
1644 1644 * Setter method for this notebook's name.
1645 1645 *
1646 1646 * @method set_notebook_name
1647 1647 * @param {String} name A new name for this notebook
1648 1648 */
1649 1649 Notebook.prototype.set_notebook_name = function (name) {
1650 1650 this.notebook_name = name;
1651 1651 };
1652 1652
1653 1653 /**
1654 1654 * Check that a notebook's name is valid.
1655 1655 *
1656 1656 * @method test_notebook_name
1657 1657 * @param {String} nbname A name for this notebook
1658 1658 * @return {Boolean} True if the name is valid, false if invalid
1659 1659 */
1660 1660 Notebook.prototype.test_notebook_name = function (nbname) {
1661 1661 nbname = nbname || '';
1662 1662 if (nbname.length>0 && !this.notebook_name_blacklist_re.test(nbname)) {
1663 1663 return true;
1664 1664 } else {
1665 1665 return false;
1666 1666 }
1667 1667 };
1668 1668
1669 1669 /**
1670 1670 * Load a notebook from JSON (.ipynb).
1671 1671 *
1672 1672 * This currently handles one worksheet: others are deleted.
1673 1673 *
1674 1674 * @method fromJSON
1675 1675 * @param {Object} data JSON representation of a notebook
1676 1676 */
1677 1677 Notebook.prototype.fromJSON = function (data) {
1678 1678 var content = data.content;
1679 1679 var ncells = this.ncells();
1680 1680 var i;
1681 1681 for (i=0; i<ncells; i++) {
1682 1682 // Always delete cell 0 as they get renumbered as they are deleted.
1683 1683 this.delete_cell(0);
1684 1684 }
1685 1685 // Save the metadata and name.
1686 1686 this.metadata = content.metadata;
1687 1687 this.notebook_name = data.name;
1688 1688 var trusted = true;
1689 1689 // Only handle 1 worksheet for now.
1690 1690 var worksheet = content.worksheets[0];
1691 1691 if (worksheet !== undefined) {
1692 1692 if (worksheet.metadata) {
1693 1693 this.worksheet_metadata = worksheet.metadata;
1694 1694 }
1695 1695 var new_cells = worksheet.cells;
1696 1696 ncells = new_cells.length;
1697 1697 var cell_data = null;
1698 1698 var new_cell = null;
1699 1699 for (i=0; i<ncells; i++) {
1700 1700 cell_data = new_cells[i];
1701 1701 // VERSIONHACK: plaintext -> raw
1702 1702 // handle never-released plaintext name for raw cells
1703 1703 if (cell_data.cell_type === 'plaintext'){
1704 1704 cell_data.cell_type = 'raw';
1705 1705 }
1706 1706
1707 1707 new_cell = this.insert_cell_at_index(cell_data.cell_type, i);
1708 1708 new_cell.fromJSON(cell_data);
1709 1709 if (new_cell.cell_type == 'code' && !new_cell.output_area.trusted) {
1710 1710 trusted = false;
1711 1711 }
1712 1712 }
1713 1713 }
1714 1714 if (trusted != this.trusted) {
1715 1715 this.trusted = trusted;
1716 1716 this.events.trigger("trust_changed.Notebook", trusted);
1717 1717 }
1718 1718 if (content.worksheets.length > 1) {
1719 1719 Dialog.modal({
1720 1720 title : "Multiple worksheets",
1721 1721 body : "This notebook has " + data.worksheets.length + " worksheets, " +
1722 1722 "but this version of IPython can only handle the first. " +
1723 1723 "If you save this notebook, worksheets after the first will be lost.",
1724 1724 buttons : {
1725 1725 OK : {
1726 1726 class : "btn-danger"
1727 1727 }
1728 1728 }
1729 1729 });
1730 1730 }
1731 1731 };
1732 1732
1733 1733 /**
1734 1734 * Dump this notebook into a JSON-friendly object.
1735 1735 *
1736 1736 * @method toJSON
1737 1737 * @return {Object} A JSON-friendly representation of this notebook.
1738 1738 */
1739 1739 Notebook.prototype.toJSON = function () {
1740 1740 var cells = this.get_cells();
1741 1741 var ncells = cells.length;
1742 1742 var cell_array = new Array(ncells);
1743 1743 var trusted = true;
1744 1744 for (var i=0; i<ncells; i++) {
1745 1745 var cell = cells[i];
1746 1746 if (cell.cell_type == 'code' && !cell.output_area.trusted) {
1747 1747 trusted = false;
1748 1748 }
1749 1749 cell_array[i] = cell.toJSON();
1750 1750 }
1751 1751 var data = {
1752 1752 // Only handle 1 worksheet for now.
1753 1753 worksheets : [{
1754 1754 cells: cell_array,
1755 1755 metadata: this.worksheet_metadata
1756 1756 }],
1757 1757 metadata : this.metadata
1758 1758 };
1759 1759 if (trusted != this.trusted) {
1760 1760 this.trusted = trusted;
1761 1761 this.events.trigger("trust_changed.Notebook", trusted);
1762 1762 }
1763 1763 return data;
1764 1764 };
1765 1765
1766 1766 /**
1767 1767 * Start an autosave timer, for periodically saving the notebook.
1768 1768 *
1769 1769 * @method set_autosave_interval
1770 1770 * @param {Integer} interval the autosave interval in milliseconds
1771 1771 */
1772 1772 Notebook.prototype.set_autosave_interval = function (interval) {
1773 1773 var that = this;
1774 1774 // clear previous interval, so we don't get simultaneous timers
1775 1775 if (this.autosave_timer) {
1776 1776 clearInterval(this.autosave_timer);
1777 1777 }
1778 1778
1779 1779 this.autosave_interval = this.minimum_autosave_interval = interval;
1780 1780 if (interval) {
1781 1781 this.autosave_timer = setInterval(function() {
1782 1782 if (that.dirty) {
1783 1783 that.save_notebook();
1784 1784 }
1785 1785 }, interval);
1786 1786 this.events.trigger("autosave_enabled.Notebook", interval);
1787 1787 } else {
1788 1788 this.autosave_timer = null;
1789 1789 this.events.trigger("autosave_disabled.Notebook");
1790 1790 }
1791 1791 };
1792 1792
1793 1793 /**
1794 1794 * Save this notebook on the server. This becomes a notebook instance's
1795 1795 * .save_notebook method *after* the entire notebook has been loaded.
1796 1796 *
1797 1797 * @method save_notebook
1798 1798 */
1799 1799 Notebook.prototype.save_notebook = function (extra_settings) {
1800 1800 // Create a JSON model to be sent to the server.
1801 1801 var model = {};
1802 1802 model.name = this.notebook_name;
1803 1803 model.path = this.notebook_path;
1804 1804 model.content = this.toJSON();
1805 1805 model.content.nbformat = this.nbformat;
1806 1806 model.content.nbformat_minor = this.nbformat_minor;
1807 1807 // time the ajax call for autosave tuning purposes.
1808 1808 var start = new Date().getTime();
1809 1809 // We do the call with settings so we can set cache to false.
1810 1810 var settings = {
1811 1811 processData : false,
1812 1812 cache : false,
1813 1813 type : "PUT",
1814 1814 data : JSON.stringify(model),
1815 1815 headers : {'Content-Type': 'application/json'},
1816 1816 success : $.proxy(this.save_notebook_success, this, start),
1817 1817 error : $.proxy(this.save_notebook_error, this)
1818 1818 };
1819 1819 if (extra_settings) {
1820 1820 for (var key in extra_settings) {
1821 1821 settings[key] = extra_settings[key];
1822 1822 }
1823 1823 }
1824 1824 this.events.trigger('notebook_saving.Notebook');
1825 1825 var url = utils.url_join_encode(
1826 1826 this.base_url,
1827 1827 'api/notebooks',
1828 1828 this.notebook_path,
1829 1829 this.notebook_name
1830 1830 );
1831 1831 $.ajax(url, settings);
1832 1832 };
1833 1833
1834 1834 /**
1835 1835 * Success callback for saving a notebook.
1836 1836 *
1837 1837 * @method save_notebook_success
1838 1838 * @param {Integer} start the time when the save request started
1839 1839 * @param {Object} data JSON representation of a notebook
1840 1840 * @param {String} status Description of response status
1841 1841 * @param {jqXHR} xhr jQuery Ajax object
1842 1842 */
1843 1843 Notebook.prototype.save_notebook_success = function (start, data, status, xhr) {
1844 1844 this.set_dirty(false);
1845 1845 this.events.trigger('notebook_saved.Notebook');
1846 1846 this._update_autosave_interval(start);
1847 1847 if (this._checkpoint_after_save) {
1848 1848 this.create_checkpoint();
1849 1849 this._checkpoint_after_save = false;
1850 1850 }
1851 1851 };
1852 1852
1853 1853 /**
1854 1854 * update the autosave interval based on how long the last save took
1855 1855 *
1856 1856 * @method _update_autosave_interval
1857 1857 * @param {Integer} timestamp when the save request started
1858 1858 */
1859 1859 Notebook.prototype._update_autosave_interval = function (start) {
1860 1860 var duration = (new Date().getTime() - start);
1861 1861 if (this.autosave_interval) {
1862 1862 // new save interval: higher of 10x save duration or parameter (default 30 seconds)
1863 1863 var interval = Math.max(10 * duration, this.minimum_autosave_interval);
1864 1864 // round to 10 seconds, otherwise we will be setting a new interval too often
1865 1865 interval = 10000 * Math.round(interval / 10000);
1866 1866 // set new interval, if it's changed
1867 1867 if (interval != this.autosave_interval) {
1868 1868 this.set_autosave_interval(interval);
1869 1869 }
1870 1870 }
1871 1871 };
1872 1872
1873 1873 /**
1874 1874 * Failure callback for saving a notebook.
1875 1875 *
1876 1876 * @method save_notebook_error
1877 1877 * @param {jqXHR} xhr jQuery Ajax object
1878 1878 * @param {String} status Description of response status
1879 1879 * @param {String} error HTTP error message
1880 1880 */
1881 1881 Notebook.prototype.save_notebook_error = function (xhr, status, error) {
1882 1882 this.events.trigger('notebook_save_failed.Notebook', [xhr, status, error]);
1883 1883 };
1884 1884
1885 1885 /**
1886 1886 * Explicitly trust the output of this notebook.
1887 1887 *
1888 1888 * @method trust_notebook
1889 1889 */
1890 1890 Notebook.prototype.trust_notebook = function (extra_settings) {
1891 1891 var body = $("<div>").append($("<p>")
1892 1892 .text("A trusted IPython notebook may execute hidden malicious code ")
1893 1893 .append($("<strong>")
1894 1894 .append(
1895 1895 $("<em>").text("when you open it")
1896 1896 )
1897 1897 ).append(".").append(
1898 1898 " Selecting trust will immediately reload this notebook in a trusted state."
1899 1899 ).append(
1900 1900 " For more information, see the "
1901 1901 ).append($("<a>").attr("href", "http://ipython.org/ipython-doc/2/notebook/security.html")
1902 1902 .text("IPython security documentation")
1903 1903 ).append(".")
1904 1904 );
1905 1905
1906 1906 var nb = this;
1907 1907 Dialog.modal({
1908 1908 title: "Trust this notebook?",
1909 1909 body: body,
1910 1910
1911 1911 buttons: {
1912 1912 Cancel : {},
1913 1913 Trust : {
1914 1914 class : "btn-danger",
1915 1915 click : function () {
1916 1916 var cells = nb.get_cells();
1917 1917 for (var i = 0; i < cells.length; i++) {
1918 1918 var cell = cells[i];
1919 1919 if (cell.cell_type == 'code') {
1920 1920 cell.output_area.trusted = true;
1921 1921 }
1922 1922 }
1923 1923 this.events.on('notebook_saved.Notebook', function () {
1924 1924 window.location.reload();
1925 1925 });
1926 1926 nb.save_notebook();
1927 1927 }
1928 1928 }
1929 1929 }
1930 1930 });
1931 1931 };
1932 1932
1933 1933 Notebook.prototype.new_notebook = function(){
1934 1934 var path = this.notebook_path;
1935 1935 var base_url = this.base_url;
1936 1936 var settings = {
1937 1937 processData : false,
1938 1938 cache : false,
1939 1939 type : "POST",
1940 1940 dataType : "json",
1941 1941 async : false,
1942 1942 success : function (data, status, xhr){
1943 1943 var notebook_name = data.name;
1944 1944 window.open(
1945 1945 utils.url_join_encode(
1946 1946 base_url,
1947 1947 'notebooks',
1948 1948 path,
1949 1949 notebook_name
1950 1950 ),
1951 1951 '_blank'
1952 1952 );
1953 1953 },
1954 1954 error : utils.log_ajax_error,
1955 1955 };
1956 1956 var url = utils.url_join_encode(
1957 1957 base_url,
1958 1958 'api/notebooks',
1959 1959 path
1960 1960 );
1961 1961 $.ajax(url,settings);
1962 1962 };
1963 1963
1964 1964
1965 1965 Notebook.prototype.copy_notebook = function(){
1966 1966 var path = this.notebook_path;
1967 1967 var base_url = this.base_url;
1968 1968 var settings = {
1969 1969 processData : false,
1970 1970 cache : false,
1971 1971 type : "POST",
1972 1972 dataType : "json",
1973 1973 data : JSON.stringify({copy_from : this.notebook_name}),
1974 1974 async : false,
1975 1975 success : function (data, status, xhr) {
1976 1976 window.open(utils.url_join_encode(
1977 1977 base_url,
1978 1978 'notebooks',
1979 1979 data.path,
1980 1980 data.name
1981 1981 ), '_blank');
1982 1982 },
1983 1983 error : utils.log_ajax_error,
1984 1984 };
1985 1985 var url = utils.url_join_encode(
1986 1986 base_url,
1987 1987 'api/notebooks',
1988 1988 path
1989 1989 );
1990 1990 $.ajax(url,settings);
1991 1991 };
1992 1992
1993 1993 Notebook.prototype.rename = function (nbname) {
1994 1994 var that = this;
1995 1995 if (!nbname.match(/\.ipynb$/)) {
1996 1996 nbname = nbname + ".ipynb";
1997 1997 }
1998 1998 var data = {name: nbname};
1999 1999 var settings = {
2000 2000 processData : false,
2001 2001 cache : false,
2002 2002 type : "PATCH",
2003 2003 data : JSON.stringify(data),
2004 2004 dataType: "json",
2005 2005 headers : {'Content-Type': 'application/json'},
2006 2006 success : $.proxy(that.rename_success, this),
2007 2007 error : $.proxy(that.rename_error, this)
2008 2008 };
2009 2009 this.events.trigger('rename_notebook.Notebook', data);
2010 2010 var url = utils.url_join_encode(
2011 2011 this.base_url,
2012 2012 'api/notebooks',
2013 2013 this.notebook_path,
2014 2014 this.notebook_name
2015 2015 );
2016 2016 $.ajax(url, settings);
2017 2017 };
2018 2018
2019 2019 Notebook.prototype.delete = function () {
2020 2020 var that = this;
2021 2021 var settings = {
2022 2022 processData : false,
2023 2023 cache : false,
2024 2024 type : "DELETE",
2025 2025 dataType: "json",
2026 2026 error : utils.log_ajax_error,
2027 2027 };
2028 2028 var url = utils.url_join_encode(
2029 2029 this.base_url,
2030 2030 'api/notebooks',
2031 2031 this.notebook_path,
2032 2032 this.notebook_name
2033 2033 );
2034 2034 $.ajax(url, settings);
2035 2035 };
2036 2036
2037 2037
2038 2038 Notebook.prototype.rename_success = function (json, status, xhr) {
2039 2039 var name = this.notebook_name = json.name;
2040 2040 var path = json.path;
2041 2041 this.session.rename_notebook(name, path);
2042 2042 this.events.trigger('notebook_renamed.Notebook', json);
2043 2043 };
2044 2044
2045 2045 Notebook.prototype.rename_error = function (xhr, status, error) {
2046 2046 var that = this;
2047 2047 var dialog = $('<div/>').append(
2048 2048 $("<p/>").addClass("rename-message")
2049 2049 .text('This notebook name already exists.')
2050 2050 );
2051 2051 this.events.trigger('notebook_rename_failed.Notebook', [xhr, status, error]);
2052 2052 Dialog.modal({
2053 2053 title: "Notebook Rename Error!",
2054 2054 body: dialog,
2055 2055 buttons : {
2056 2056 "Cancel": {},
2057 2057 "OK": {
2058 2058 class: "btn-primary",
2059 2059 click: function () {
2060 2060 this.save_widget.rename_notebook();
2061 2061 }}
2062 2062 },
2063 2063 open : function (event, ui) {
2064 2064 var that = $(this);
2065 2065 // Upon ENTER, click the OK button.
2066 2066 that.find('input[type="text"]').keydown(function (event, ui) {
2067 2067 if (event.which === this.keyboard.keycodes.enter) {
2068 2068 that.find('.btn-primary').first().click();
2069 2069 }
2070 2070 });
2071 2071 that.find('input[type="text"]').focus();
2072 2072 }
2073 2073 });
2074 2074 };
2075 2075
2076 2076 /**
2077 2077 * Request a notebook's data from the server.
2078 2078 *
2079 2079 * @method load_notebook
2080 2080 * @param {String} notebook_name and path A notebook to load
2081 2081 */
2082 2082 Notebook.prototype.load_notebook = function (notebook_name, notebook_path) {
2083 2083 var that = this;
2084 2084 this.notebook_name = notebook_name;
2085 2085 this.notebook_path = notebook_path;
2086 2086 // We do the call with settings so we can set cache to false.
2087 2087 var settings = {
2088 2088 processData : false,
2089 2089 cache : false,
2090 2090 type : "GET",
2091 2091 dataType : "json",
2092 2092 success : $.proxy(this.load_notebook_success,this),
2093 2093 error : $.proxy(this.load_notebook_error,this),
2094 2094 };
2095 2095 this.events.trigger('notebook_loading.Notebook');
2096 2096 var url = utils.url_join_encode(
2097 2097 this.base_url,
2098 2098 'api/notebooks',
2099 2099 this.notebook_path,
2100 2100 this.notebook_name
2101 2101 );
2102 2102 $.ajax(url, settings);
2103 2103 };
2104 2104
2105 2105 /**
2106 2106 * Success callback for loading a notebook from the server.
2107 2107 *
2108 2108 * Load notebook data from the JSON response.
2109 2109 *
2110 2110 * @method load_notebook_success
2111 2111 * @param {Object} data JSON representation of a notebook
2112 2112 * @param {String} status Description of response status
2113 2113 * @param {jqXHR} xhr jQuery Ajax object
2114 2114 */
2115 2115 Notebook.prototype.load_notebook_success = function (data, status, xhr) {
2116 2116 this.fromJSON(data);
2117 2117 if (this.ncells() === 0) {
2118 2118 this.insert_cell_below('code');
2119 2119 this.edit_mode(0);
2120 2120 } else {
2121 2121 this.select(0);
2122 2122 this.handle_command_mode(this.get_cell(0));
2123 2123 }
2124 2124 this.set_dirty(false);
2125 2125 this.scroll_to_top();
2126 2126 if (data.orig_nbformat !== undefined && data.nbformat !== data.orig_nbformat) {
2127 2127 var msg = "This notebook has been converted from an older " +
2128 2128 "notebook format (v"+data.orig_nbformat+") to the current notebook " +
2129 2129 "format (v"+data.nbformat+"). The next time you save this notebook, the " +
2130 2130 "newer notebook format will be used and older versions of IPython " +
2131 2131 "may not be able to read it. To keep the older version, close the " +
2132 2132 "notebook without saving it.";
2133 2133 Dialog.modal({
2134 2134 title : "Notebook converted",
2135 2135 body : msg,
2136 2136 buttons : {
2137 2137 OK : {
2138 2138 class : "btn-primary"
2139 2139 }
2140 2140 }
2141 2141 });
2142 2142 } else if (data.orig_nbformat_minor !== undefined && data.nbformat_minor !== data.orig_nbformat_minor) {
2143 2143 var that = this;
2144 2144 var orig_vs = 'v' + data.nbformat + '.' + data.orig_nbformat_minor;
2145 2145 var this_vs = 'v' + data.nbformat + '.' + this.nbformat_minor;
2146 2146 var msg = "This notebook is version " + orig_vs + ", but we only fully support up to " +
2147 2147 this_vs + ". You can still work with this notebook, but some features " +
2148 2148 "introduced in later notebook versions may not be available.";
2149 2149
2150 2150 Dialog.modal({
2151 2151 title : "Newer Notebook",
2152 2152 body : msg,
2153 2153 buttons : {
2154 2154 OK : {
2155 2155 class : "btn-danger"
2156 2156 }
2157 2157 }
2158 2158 });
2159 2159
2160 2160 }
2161 2161
2162 2162 // Create the session after the notebook is completely loaded to prevent
2163 2163 // code execution upon loading, which is a security risk.
2164 2164 if (this.session === null) {
2165 2165 this.start_session();
2166 2166 }
2167 2167 // load our checkpoint list
2168 2168 this.list_checkpoints();
2169 2169
2170 2170 // load toolbar state
2171 2171 if (this.metadata.celltoolbar) {
2172 2172 CellToolbar.global_show();
2173 2173 CellToolbar.activate_preset(this.metadata.celltoolbar);
2174 2174 } else {
2175 2175 CellToolbar.global_hide();
2176 2176 }
2177 2177
2178 2178 // now that we're fully loaded, it is safe to restore save functionality
2179 2179 delete(this.save_notebook);
2180 2180 this.events.trigger('notebook_loaded.Notebook');
2181 2181 };
2182 2182
2183 2183 /**
2184 2184 * Failure callback for loading a notebook from the server.
2185 2185 *
2186 2186 * @method load_notebook_error
2187 2187 * @param {jqXHR} xhr jQuery Ajax object
2188 2188 * @param {String} status Description of response status
2189 2189 * @param {String} error HTTP error message
2190 2190 */
2191 2191 Notebook.prototype.load_notebook_error = function (xhr, status, error) {
2192 2192 this.events.trigger('notebook_load_failed.Notebook', [xhr, status, error]);
2193 2193 var msg;
2194 2194 if (xhr.status === 400) {
2195 2195 msg = error;
2196 2196 } else if (xhr.status === 500) {
2197 2197 msg = "An unknown error occurred while loading this notebook. " +
2198 2198 "This version can load notebook formats " +
2199 2199 "v" + this.nbformat + " or earlier.";
2200 2200 }
2201 2201 Dialog.modal({
2202 2202 title: "Error loading notebook",
2203 2203 body : msg,
2204 2204 buttons : {
2205 2205 "OK": {}
2206 2206 }
2207 2207 });
2208 2208 };
2209 2209
2210 2210 /********************* checkpoint-related *********************/
2211 2211
2212 2212 /**
2213 2213 * Save the notebook then immediately create a checkpoint.
2214 2214 *
2215 2215 * @method save_checkpoint
2216 2216 */
2217 2217 Notebook.prototype.save_checkpoint = function () {
2218 2218 this._checkpoint_after_save = true;
2219 2219 this.save_notebook();
2220 2220 };
2221 2221
2222 2222 /**
2223 2223 * Add a checkpoint for this notebook.
2224 2224 * for use as a callback from checkpoint creation.
2225 2225 *
2226 2226 * @method add_checkpoint
2227 2227 */
2228 2228 Notebook.prototype.add_checkpoint = function (checkpoint) {
2229 2229 var found = false;
2230 2230 for (var i = 0; i < this.checkpoints.length; i++) {
2231 2231 var existing = this.checkpoints[i];
2232 2232 if (existing.id == checkpoint.id) {
2233 2233 found = true;
2234 2234 this.checkpoints[i] = checkpoint;
2235 2235 break;
2236 2236 }
2237 2237 }
2238 2238 if (!found) {
2239 2239 this.checkpoints.push(checkpoint);
2240 2240 }
2241 2241 this.last_checkpoint = this.checkpoints[this.checkpoints.length - 1];
2242 2242 };
2243 2243
2244 2244 /**
2245 2245 * List checkpoints for this notebook.
2246 2246 *
2247 2247 * @method list_checkpoints
2248 2248 */
2249 2249 Notebook.prototype.list_checkpoints = function () {
2250 2250 var url = utils.url_join_encode(
2251 2251 this.base_url,
2252 2252 'api/notebooks',
2253 2253 this.notebook_path,
2254 2254 this.notebook_name,
2255 2255 'checkpoints'
2256 2256 );
2257 2257 $.get(url).done(
2258 2258 $.proxy(this.list_checkpoints_success, this)
2259 2259 ).fail(
2260 2260 $.proxy(this.list_checkpoints_error, this)
2261 2261 );
2262 2262 };
2263 2263
2264 2264 /**
2265 2265 * Success callback for listing checkpoints.
2266 2266 *
2267 2267 * @method list_checkpoint_success
2268 2268 * @param {Object} data JSON representation of a checkpoint
2269 2269 * @param {String} status Description of response status
2270 2270 * @param {jqXHR} xhr jQuery Ajax object
2271 2271 */
2272 2272 Notebook.prototype.list_checkpoints_success = function (data, status, xhr) {
2273 2273 data = $.parseJSON(data);
2274 2274 this.checkpoints = data;
2275 2275 if (data.length) {
2276 2276 this.last_checkpoint = data[data.length - 1];
2277 2277 } else {
2278 2278 this.last_checkpoint = null;
2279 2279 }
2280 2280 this.events.trigger('checkpoints_listed.Notebook', [data]);
2281 2281 };
2282 2282
2283 2283 /**
2284 2284 * Failure callback for listing a checkpoint.
2285 2285 *
2286 2286 * @method list_checkpoint_error
2287 2287 * @param {jqXHR} xhr jQuery Ajax object
2288 2288 * @param {String} status Description of response status
2289 2289 * @param {String} error_msg HTTP error message
2290 2290 */
2291 2291 Notebook.prototype.list_checkpoints_error = function (xhr, status, error_msg) {
2292 2292 this.events.trigger('list_checkpoints_failed.Notebook');
2293 2293 };
2294 2294
2295 2295 /**
2296 2296 * Create a checkpoint of this notebook on the server from the most recent save.
2297 2297 *
2298 2298 * @method create_checkpoint
2299 2299 */
2300 2300 Notebook.prototype.create_checkpoint = function () {
2301 2301 var url = utils.url_join_encode(
2302 2302 this.base_url,
2303 2303 'api/notebooks',
2304 2304 this.notebook_path,
2305 2305 this.notebook_name,
2306 2306 'checkpoints'
2307 2307 );
2308 2308 $.post(url).done(
2309 2309 $.proxy(this.create_checkpoint_success, this)
2310 2310 ).fail(
2311 2311 $.proxy(this.create_checkpoint_error, this)
2312 2312 );
2313 2313 };
2314 2314
2315 2315 /**
2316 2316 * Success callback for creating a checkpoint.
2317 2317 *
2318 2318 * @method create_checkpoint_success
2319 2319 * @param {Object} data JSON representation of a checkpoint
2320 2320 * @param {String} status Description of response status
2321 2321 * @param {jqXHR} xhr jQuery Ajax object
2322 2322 */
2323 2323 Notebook.prototype.create_checkpoint_success = function (data, status, xhr) {
2324 2324 data = $.parseJSON(data);
2325 2325 this.add_checkpoint(data);
2326 2326 this.events.trigger('checkpoint_created.Notebook', data);
2327 2327 };
2328 2328
2329 2329 /**
2330 2330 * Failure callback for creating a checkpoint.
2331 2331 *
2332 2332 * @method create_checkpoint_error
2333 2333 * @param {jqXHR} xhr jQuery Ajax object
2334 2334 * @param {String} status Description of response status
2335 2335 * @param {String} error_msg HTTP error message
2336 2336 */
2337 2337 Notebook.prototype.create_checkpoint_error = function (xhr, status, error_msg) {
2338 2338 this.events.trigger('checkpoint_failed.Notebook');
2339 2339 };
2340 2340
2341 2341 Notebook.prototype.restore_checkpoint_dialog = function (checkpoint) {
2342 2342 var that = this;
2343 2343 checkpoint = checkpoint || this.last_checkpoint;
2344 2344 if ( ! checkpoint ) {
2345 2345 console.log("restore dialog, but no checkpoint to restore to!");
2346 2346 return;
2347 2347 }
2348 2348 var body = $('<div/>').append(
2349 2349 $('<p/>').addClass("p-space").text(
2350 2350 "Are you sure you want to revert the notebook to " +
2351 2351 "the latest checkpoint?"
2352 2352 ).append(
2353 2353 $("<strong/>").text(
2354 2354 " This cannot be undone."
2355 2355 )
2356 2356 )
2357 2357 ).append(
2358 2358 $('<p/>').addClass("p-space").text("The checkpoint was last updated at:")
2359 2359 ).append(
2360 2360 $('<p/>').addClass("p-space").text(
2361 2361 Date(checkpoint.last_modified)
2362 2362 ).css("text-align", "center")
2363 2363 );
2364 2364
2365 2365 Dialog.modal({
2366 2366 title : "Revert notebook to checkpoint",
2367 2367 body : body,
2368 2368 buttons : {
2369 2369 Revert : {
2370 2370 class : "btn-danger",
2371 2371 click : function () {
2372 2372 that.restore_checkpoint(checkpoint.id);
2373 2373 }
2374 2374 },
2375 2375 Cancel : {}
2376 2376 }
2377 2377 });
2378 2378 };
2379 2379
2380 2380 /**
2381 2381 * Restore the notebook to a checkpoint state.
2382 2382 *
2383 2383 * @method restore_checkpoint
2384 2384 * @param {String} checkpoint ID
2385 2385 */
2386 2386 Notebook.prototype.restore_checkpoint = function (checkpoint) {
2387 2387 this.events.trigger('notebook_restoring.Notebook', checkpoint);
2388 2388 var url = utils.url_join_encode(
2389 2389 this.base_url,
2390 2390 'api/notebooks',
2391 2391 this.notebook_path,
2392 2392 this.notebook_name,
2393 2393 'checkpoints',
2394 2394 checkpoint
2395 2395 );
2396 2396 $.post(url).done(
2397 2397 $.proxy(this.restore_checkpoint_success, this)
2398 2398 ).fail(
2399 2399 $.proxy(this.restore_checkpoint_error, this)
2400 2400 );
2401 2401 };
2402 2402
2403 2403 /**
2404 2404 * Success callback for restoring a notebook to a checkpoint.
2405 2405 *
2406 2406 * @method restore_checkpoint_success
2407 2407 * @param {Object} data (ignored, should be empty)
2408 2408 * @param {String} status Description of response status
2409 2409 * @param {jqXHR} xhr jQuery Ajax object
2410 2410 */
2411 2411 Notebook.prototype.restore_checkpoint_success = function (data, status, xhr) {
2412 2412 this.events.trigger('checkpoint_restored.Notebook');
2413 2413 this.load_notebook(this.notebook_name, this.notebook_path);
2414 2414 };
2415 2415
2416 2416 /**
2417 2417 * Failure callback for restoring a notebook to a checkpoint.
2418 2418 *
2419 2419 * @method restore_checkpoint_error
2420 2420 * @param {jqXHR} xhr jQuery Ajax object
2421 2421 * @param {String} status Description of response status
2422 2422 * @param {String} error_msg HTTP error message
2423 2423 */
2424 2424 Notebook.prototype.restore_checkpoint_error = function (xhr, status, error_msg) {
2425 2425 this.events.trigger('checkpoint_restore_failed.Notebook');
2426 2426 };
2427 2427
2428 2428 /**
2429 2429 * Delete a notebook checkpoint.
2430 2430 *
2431 2431 * @method delete_checkpoint
2432 2432 * @param {String} checkpoint ID
2433 2433 */
2434 2434 Notebook.prototype.delete_checkpoint = function (checkpoint) {
2435 2435 this.events.trigger('notebook_restoring.Notebook', checkpoint);
2436 2436 var url = utils.url_join_encode(
2437 2437 this.base_url,
2438 2438 'api/notebooks',
2439 2439 this.notebook_path,
2440 2440 this.notebook_name,
2441 2441 'checkpoints',
2442 2442 checkpoint
2443 2443 );
2444 2444 $.ajax(url, {
2445 2445 type: 'DELETE',
2446 2446 success: $.proxy(this.delete_checkpoint_success, this),
2447 2447 error: $.proxy(this.delete_checkpoint_error, this)
2448 2448 });
2449 2449 };
2450 2450
2451 2451 /**
2452 2452 * Success callback for deleting a notebook checkpoint
2453 2453 *
2454 2454 * @method delete_checkpoint_success
2455 2455 * @param {Object} data (ignored, should be empty)
2456 2456 * @param {String} status Description of response status
2457 2457 * @param {jqXHR} xhr jQuery Ajax object
2458 2458 */
2459 2459 Notebook.prototype.delete_checkpoint_success = function (data, status, xhr) {
2460 2460 this.events.trigger('checkpoint_deleted.Notebook', data);
2461 2461 this.load_notebook(this.notebook_name, this.notebook_path);
2462 2462 };
2463 2463
2464 2464 /**
2465 2465 * Failure callback for deleting a notebook checkpoint.
2466 2466 *
2467 2467 * @method delete_checkpoint_error
2468 2468 * @param {jqXHR} xhr jQuery Ajax object
2469 2469 * @param {String} status Description of response status
2470 2470 * @param {String} error_msg HTTP error message
2471 2471 */
2472 2472 Notebook.prototype.delete_checkpoint_error = function (xhr, status, error_msg) {
2473 2473 this.events.trigger('checkpoint_delete_failed.Notebook');
2474 2474 };
2475 2475
2476 2476
2477 2477 // For backwards compatability.
2478 2478 IPython.Notebook = Notebook;
2479 2479
2480 return Notebook;
2480 return {'Notebook': Notebook};
2481 2481 });
@@ -1,233 +1,233 b''
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 4 define([
5 5 'base/js/namespace',
6 6 'jquery',
7 7 'base/js/utils',
8 8 'base/js/dialog',
9 9 'notebook/js/notificationwidget',
10 10 ], function(IPython, $, utils, Dialog, NotificationWidget) {
11 11 "use strict";
12 12
13 13 var NotificationArea = function (selector, events, save_widget, notebook) {
14 14 this.selector = selector;
15 15 this.events = events;
16 16 this.save_widget = save_widget;
17 17 this.notebook = notebook;
18 18 if (this.selector !== undefined) {
19 19 this.element = $(selector);
20 20 }
21 21 this.widget_dict = {};
22 22 };
23 23
24 24 NotificationArea.prototype.temp_message = function (msg, timeout, css_class) {
25 25 var uuid = utils.uuid();
26 26 if( css_class == 'danger') {css_class = 'ui-state-error';}
27 27 if( css_class == 'warning') {css_class = 'ui-state-highlight';}
28 28 var tdiv = $('<div>')
29 29 .attr('id',uuid)
30 30 .addClass('notification_widget ui-widget ui-widget-content ui-corner-all')
31 31 .addClass('border-box-sizing')
32 32 .addClass(css_class)
33 33 .hide()
34 34 .text(msg);
35 35
36 36 $(this.selector).append(tdiv);
37 37 var tmout = Math.max(1500,(timeout||1500));
38 38 tdiv.fadeIn(100);
39 39
40 40 setTimeout(function () {
41 41 tdiv.fadeOut(100, function () {tdiv.remove();});
42 42 }, tmout);
43 43 };
44 44
45 45 NotificationArea.prototype.widget = function(name) {
46 46 if(this.widget_dict[name] === undefined) {
47 47 return this.new_notification_widget(name);
48 48 }
49 49 return this.get_widget(name);
50 50 };
51 51
52 52 NotificationArea.prototype.get_widget = function(name) {
53 53 if(this.widget_dict[name] === undefined) {
54 54 throw('no widgets with this name');
55 55 }
56 56 return this.widget_dict[name];
57 57 };
58 58
59 59 NotificationArea.prototype.new_notification_widget = function(name) {
60 60 if(this.widget_dict[name] !== undefined) {
61 61 throw('widget with that name already exists ! ');
62 62 }
63 63 var div = $('<div/>').attr('id','notification_'+name);
64 64 $(this.selector).append(div);
65 65 this.widget_dict[name] = new NotificationWidget('#notification_'+name);
66 66 return this.widget_dict[name];
67 67 };
68 68
69 69 NotificationArea.prototype.init_notification_widgets = function() {
70 70 var that = this;
71 71 var knw = this.new_notification_widget('kernel');
72 72 var $kernel_ind_icon = $("#kernel_indicator_icon");
73 73 var $modal_ind_icon = $("#modal_indicator_icon");
74 74
75 75 // Command/Edit mode
76 76 this.events.on('edit_mode.Notebook',function () {
77 77 that.save_widget.update_document_title();
78 78 $modal_ind_icon.attr('class','edit_mode_icon').attr('title','Edit Mode');
79 79 });
80 80
81 81 this.events.on('command_mode.Notebook',function () {
82 82 that.save_widget.update_document_title();
83 83 $modal_ind_icon.attr('class','command_mode_icon').attr('title','Command Mode');
84 84 });
85 85
86 86 // Implicitly start off in Command mode, switching to Edit mode will trigger event
87 87 $modal_ind_icon.attr('class','command-mode_icon').attr('title','Command Mode');
88 88
89 89 // Kernel events
90 90 this.events.on('status_idle.Kernel',function () {
91 91 that.save_widget.update_document_title();
92 92 $kernel_ind_icon.attr('class','kernel_idle_icon').attr('title','Kernel Idle');
93 93 });
94 94
95 95 this.events.on('status_busy.Kernel',function () {
96 96 window.document.title='(Busy) '+window.document.title;
97 97 $kernel_ind_icon.attr('class','kernel_busy_icon').attr('title','Kernel Busy');
98 98 });
99 99
100 100 this.events.on('status_restarting.Kernel',function () {
101 101 that.save_widget.update_document_title();
102 102 knw.set_message("Restarting kernel", 2000);
103 103 });
104 104
105 105 this.events.on('status_interrupting.Kernel',function () {
106 106 knw.set_message("Interrupting kernel", 2000);
107 107 });
108 108
109 109 // Start the kernel indicator in the busy state, and send a kernel_info request.
110 110 // When the kernel_info reply arrives, the kernel is idle.
111 111 $kernel_ind_icon.attr('class','kernel_busy_icon').attr('title','Kernel Busy');
112 112
113 113 this.events.on('status_started.Kernel', function (evt, data) {
114 114 data.kernel.kernel_info(function () {
115 115 that.events.trigger('status_idle.Kernel');
116 116 });
117 117 });
118 118
119 119 this.events.on('status_dead.Kernel',function () {
120 120 var msg = 'The kernel has died, and the automatic restart has failed.' +
121 121 ' It is possible the kernel cannot be restarted.' +
122 122 ' If you are not able to restart the kernel, you will still be able to save' +
123 123 ' the notebook, but running code will no longer work until the notebook' +
124 124 ' is reopened.';
125 125
126 126 Dialog.modal({
127 127 title: "Dead kernel",
128 128 body : msg,
129 129 buttons : {
130 130 "Manual Restart": {
131 131 class: "btn-danger",
132 132 click: function () {
133 133 that.events.trigger('status_restarting.Kernel');
134 134 that.notebook.start_kernel();
135 135 }
136 136 },
137 137 "Don't restart": {}
138 138 }
139 139 });
140 140 });
141 141
142 142 this.events.on('websocket_closed.Kernel', function (event, data) {
143 143 var kernel = data.kernel;
144 144 var ws_url = data.ws_url;
145 145 var early = data.early;
146 146 var msg;
147 147 if (!early) {
148 148 knw.set_message('Reconnecting WebSockets', 1000);
149 149 setTimeout(function () {
150 150 kernel.start_channels();
151 151 }, 5000);
152 152 return;
153 153 }
154 154 console.log('WebSocket connection failed: ', ws_url);
155 155 msg = "A WebSocket connection could not be established." +
156 156 " You will NOT be able to run code. Check your" +
157 157 " network connection or notebook server configuration.";
158 158 Dialog.modal({
159 159 title: "WebSocket connection failed",
160 160 body: msg,
161 161 buttons : {
162 162 "OK": {},
163 163 "Reconnect": {
164 164 click: function () {
165 165 knw.set_message('Reconnecting WebSockets', 1000);
166 166 setTimeout(function () {
167 167 kernel.start_channels();
168 168 }, 5000);
169 169 }
170 170 }
171 171 }
172 172 });
173 173 });
174 174
175 175
176 176 var nnw = this.new_notification_widget('notebook');
177 177
178 178 // Notebook events
179 179 this.events.on('notebook_loading.Notebook', function () {
180 180 nnw.set_message("Loading notebook",500);
181 181 });
182 182 this.events.on('notebook_loaded.Notebook', function () {
183 183 nnw.set_message("Notebook loaded",500);
184 184 });
185 185 this.events.on('notebook_saving.Notebook', function () {
186 186 nnw.set_message("Saving notebook",500);
187 187 });
188 188 this.events.on('notebook_saved.Notebook', function () {
189 189 nnw.set_message("Notebook saved",2000);
190 190 });
191 191 this.events.on('notebook_save_failed.Notebook', function (evt, xhr, status, data) {
192 192 nnw.set_message(data || "Notebook save failed");
193 193 });
194 194
195 195 // Checkpoint events
196 196 this.events.on('checkpoint_created.Notebook', function (evt, data) {
197 197 var msg = "Checkpoint created";
198 198 if (data.last_modified) {
199 199 var d = new Date(data.last_modified);
200 200 msg = msg + ": " + d.format("HH:MM:ss");
201 201 }
202 202 nnw.set_message(msg, 2000);
203 203 });
204 204 this.events.on('checkpoint_failed.Notebook', function () {
205 205 nnw.set_message("Checkpoint failed");
206 206 });
207 207 this.events.on('checkpoint_deleted.Notebook', function () {
208 208 nnw.set_message("Checkpoint deleted", 500);
209 209 });
210 210 this.events.on('checkpoint_delete_failed.Notebook', function () {
211 211 nnw.set_message("Checkpoint delete failed");
212 212 });
213 213 this.events.on('checkpoint_restoring.Notebook', function () {
214 214 nnw.set_message("Restoring to checkpoint...", 500);
215 215 });
216 216 this.events.on('checkpoint_restore_failed.Notebook', function () {
217 217 nnw.set_message("Checkpoint restore failed");
218 218 });
219 219
220 220 // Autosave events
221 221 this.events.on('autosave_disabled.Notebook', function () {
222 222 nnw.set_message("Autosave disabled", 2000);
223 223 });
224 224 this.events.on('autosave_enabled.Notebook', function (evt, interval) {
225 225 nnw.set_message("Saving every " + interval / 1000 + "s", 1000);
226 226 });
227 227
228 228 };
229 229
230 230 IPython.NotificationArea = NotificationArea;
231 231
232 return NotificationArea;
232 return {'NotificationArea': NotificationArea};
233 233 });
@@ -1,77 +1,77 b''
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 4 define([
5 5 'base/js/namespace',
6 6 'jquery',
7 7 ], function(IPython, $) {
8 8 "use strict";
9 9
10 10 var NotificationWidget = function (selector) {
11 11 this.selector = selector;
12 12 this.timeout = null;
13 13 this.busy = false;
14 14 if (this.selector !== undefined) {
15 15 this.element = $(selector);
16 16 this.style();
17 17 }
18 18 this.element.button();
19 19 this.element.hide();
20 20 var that = this;
21 21
22 22 this.inner = $('<span/>');
23 23 this.element.append(this.inner);
24 24
25 25 };
26 26
27 27 NotificationWidget.prototype.style = function () {
28 28 this.element.addClass('notification_widget pull-right');
29 29 this.element.addClass('border-box-sizing');
30 30 };
31 31
32 32 // msg : message to display
33 33 // timeout : time in ms before diseapearing
34 34 //
35 35 // if timeout <= 0
36 36 // click_callback : function called if user click on notification
37 37 // could return false to prevent the notification to be dismissed
38 38 NotificationWidget.prototype.set_message = function (msg, timeout, click_callback, opts) {
39 39 opts = opts || {};
40 40 var callback = click_callback || function() {return false;};
41 41 var that = this;
42 42 this.inner.attr('class', opts.icon);
43 43 this.inner.attr('title', opts.title);
44 44 this.inner.text(msg);
45 45 this.element.fadeIn(100);
46 46 if (this.timeout !== null) {
47 47 clearTimeout(this.timeout);
48 48 this.timeout = null;
49 49 }
50 50 if (timeout !== undefined && timeout >=0) {
51 51 this.timeout = setTimeout(function () {
52 52 that.element.fadeOut(100, function () {that.inner.text('');});
53 53 that.timeout = null;
54 54 }, timeout);
55 55 } else {
56 56 this.element.click(function() {
57 57 if( callback() !== false ) {
58 58 that.element.fadeOut(100, function () {that.inner.text('');});
59 59 that.element.unbind('click');
60 60 }
61 61 if (that.timeout !== undefined) {
62 62 that.timeout = undefined;
63 63 clearTimeout(that.timeout);
64 64 }
65 65 });
66 66 }
67 67 };
68 68
69 69 NotificationWidget.prototype.get_message = function () {
70 70 return this.inner.html();
71 71 };
72 72
73 73 // For backwards compatability.
74 74 IPython.NotificationWidget = NotificationWidget;
75 75
76 return NotificationWidget;
76 return {'NotificationWidget': NotificationWidget};
77 77 });
@@ -1,992 +1,992 b''
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 4 define([
5 5 'base/js/namespace',
6 6 'jquery',
7 7 'base/js/utils',
8 8 'base/js/security',
9 9 'base/js/keyboard',
10 10 'notebook/js/mathjaxutils',
11 11 ], function(IPython, $, utils, security, keyboard, mathjaxutils) {
12 12 "use strict";
13 13
14 14 /**
15 15 * @class OutputArea
16 16 *
17 17 * @constructor
18 18 */
19 19
20 20 var OutputArea = function (selector, prompt_area, events, keyboard_manager) {
21 21 this.selector = selector;
22 22 this.events = events;
23 23 this.keyboard_manager = keyboard_manager;
24 24 this.wrapper = $(selector);
25 25 this.outputs = [];
26 26 this.collapsed = false;
27 27 this.scrolled = false;
28 28 this.trusted = true;
29 29 this.clear_queued = null;
30 30 if (prompt_area === undefined) {
31 31 this.prompt_area = true;
32 32 } else {
33 33 this.prompt_area = prompt_area;
34 34 }
35 35 this.create_elements();
36 36 this.style();
37 37 this.bind_events();
38 38 };
39 39
40 40
41 41 /**
42 42 * Class prototypes
43 43 **/
44 44
45 45 OutputArea.prototype.create_elements = function () {
46 46 this.element = $("<div/>");
47 47 this.collapse_button = $("<div/>");
48 48 this.prompt_overlay = $("<div/>");
49 49 this.wrapper.append(this.prompt_overlay);
50 50 this.wrapper.append(this.element);
51 51 this.wrapper.append(this.collapse_button);
52 52 };
53 53
54 54
55 55 OutputArea.prototype.style = function () {
56 56 this.collapse_button.hide();
57 57 this.prompt_overlay.hide();
58 58
59 59 this.wrapper.addClass('output_wrapper');
60 60 this.element.addClass('output');
61 61
62 62 this.collapse_button.addClass("btn btn-default output_collapsed");
63 63 this.collapse_button.attr('title', 'click to expand output');
64 64 this.collapse_button.text('. . .');
65 65
66 66 this.prompt_overlay.addClass('out_prompt_overlay prompt');
67 67 this.prompt_overlay.attr('title', 'click to expand output; double click to hide output');
68 68
69 69 this.collapse();
70 70 };
71 71
72 72 /**
73 73 * Should the OutputArea scroll?
74 74 * Returns whether the height (in lines) exceeds a threshold.
75 75 *
76 76 * @private
77 77 * @method _should_scroll
78 78 * @param [lines=100]{Integer}
79 79 * @return {Bool}
80 80 *
81 81 */
82 82 OutputArea.prototype._should_scroll = function (lines) {
83 83 if (lines <=0 ){ return }
84 84 if (!lines) {
85 85 lines = 100;
86 86 }
87 87 // line-height from http://stackoverflow.com/questions/1185151
88 88 var fontSize = this.element.css('font-size');
89 89 var lineHeight = Math.floor(parseInt(fontSize.replace('px','')) * 1.5);
90 90
91 91 return (this.element.height() > lines * lineHeight);
92 92 };
93 93
94 94
95 95 OutputArea.prototype.bind_events = function () {
96 96 var that = this;
97 97 this.prompt_overlay.dblclick(function () { that.toggle_output(); });
98 98 this.prompt_overlay.click(function () { that.toggle_scroll(); });
99 99
100 100 this.element.resize(function () {
101 101 // FIXME: Firefox on Linux misbehaves, so automatic scrolling is disabled
102 102 if ( utils.browser[0] === "Firefox" ) {
103 103 return;
104 104 }
105 105 // maybe scroll output,
106 106 // if it's grown large enough and hasn't already been scrolled.
107 107 if ( !that.scrolled && that._should_scroll(OutputArea.auto_scroll_threshold)) {
108 108 that.scroll_area();
109 109 }
110 110 });
111 111 this.collapse_button.click(function () {
112 112 that.expand();
113 113 });
114 114 };
115 115
116 116
117 117 OutputArea.prototype.collapse = function () {
118 118 if (!this.collapsed) {
119 119 this.element.hide();
120 120 this.prompt_overlay.hide();
121 121 if (this.element.html()){
122 122 this.collapse_button.show();
123 123 }
124 124 this.collapsed = true;
125 125 }
126 126 };
127 127
128 128
129 129 OutputArea.prototype.expand = function () {
130 130 if (this.collapsed) {
131 131 this.collapse_button.hide();
132 132 this.element.show();
133 133 this.prompt_overlay.show();
134 134 this.collapsed = false;
135 135 }
136 136 };
137 137
138 138
139 139 OutputArea.prototype.toggle_output = function () {
140 140 if (this.collapsed) {
141 141 this.expand();
142 142 } else {
143 143 this.collapse();
144 144 }
145 145 };
146 146
147 147
148 148 OutputArea.prototype.scroll_area = function () {
149 149 this.element.addClass('output_scroll');
150 150 this.prompt_overlay.attr('title', 'click to unscroll output; double click to hide');
151 151 this.scrolled = true;
152 152 };
153 153
154 154
155 155 OutputArea.prototype.unscroll_area = function () {
156 156 this.element.removeClass('output_scroll');
157 157 this.prompt_overlay.attr('title', 'click to scroll output; double click to hide');
158 158 this.scrolled = false;
159 159 };
160 160
161 161 /**
162 162 *
163 163 * Scroll OutputArea if height supperior than a threshold (in lines).
164 164 *
165 165 * Threshold is a maximum number of lines. If unspecified, defaults to
166 166 * OutputArea.minimum_scroll_threshold.
167 167 *
168 168 * Negative threshold will prevent the OutputArea from ever scrolling.
169 169 *
170 170 * @method scroll_if_long
171 171 *
172 172 * @param [lines=20]{Number} Default to 20 if not set,
173 173 * behavior undefined for value of `0`.
174 174 *
175 175 **/
176 176 OutputArea.prototype.scroll_if_long = function (lines) {
177 177 var n = lines | OutputArea.minimum_scroll_threshold;
178 178 if(n <= 0){
179 179 return
180 180 }
181 181
182 182 if (this._should_scroll(n)) {
183 183 // only allow scrolling long-enough output
184 184 this.scroll_area();
185 185 }
186 186 };
187 187
188 188
189 189 OutputArea.prototype.toggle_scroll = function () {
190 190 if (this.scrolled) {
191 191 this.unscroll_area();
192 192 } else {
193 193 // only allow scrolling long-enough output
194 194 this.scroll_if_long();
195 195 }
196 196 };
197 197
198 198
199 199 // typeset with MathJax if MathJax is available
200 200 OutputArea.prototype.typeset = function () {
201 201 if (window.MathJax){
202 202 MathJax.Hub.Queue(["Typeset",MathJax.Hub]);
203 203 }
204 204 };
205 205
206 206
207 207 OutputArea.prototype.handle_output = function (msg) {
208 208 var json = {};
209 209 var msg_type = json.output_type = msg.header.msg_type;
210 210 var content = msg.content;
211 211 if (msg_type === "stream") {
212 212 json.text = content.data;
213 213 json.stream = content.name;
214 214 } else if (msg_type === "display_data") {
215 215 json = content.data;
216 216 json.output_type = msg_type;
217 217 json.metadata = content.metadata;
218 218 } else if (msg_type === "execute_result") {
219 219 json = content.data;
220 220 json.output_type = msg_type;
221 221 json.metadata = content.metadata;
222 222 json.prompt_number = content.execution_count;
223 223 } else if (msg_type === "error") {
224 224 json.ename = content.ename;
225 225 json.evalue = content.evalue;
226 226 json.traceback = content.traceback;
227 227 } else {
228 228 console.log("unhandled output message", msg);
229 229 return;
230 230 }
231 231 this.append_output(json);
232 232 };
233 233
234 234
235 235 OutputArea.prototype.rename_keys = function (data, key_map) {
236 236 var remapped = {};
237 237 for (var key in data) {
238 238 var new_key = key_map[key] || key;
239 239 remapped[new_key] = data[key];
240 240 }
241 241 return remapped;
242 242 };
243 243
244 244
245 245 OutputArea.output_types = [
246 246 'application/javascript',
247 247 'text/html',
248 248 'text/markdown',
249 249 'text/latex',
250 250 'image/svg+xml',
251 251 'image/png',
252 252 'image/jpeg',
253 253 'application/pdf',
254 254 'text/plain'
255 255 ];
256 256
257 257 OutputArea.prototype.validate_output = function (json) {
258 258 // scrub invalid outputs
259 259 // TODO: right now everything is a string, but JSON really shouldn't be.
260 260 // nbformat 4 will fix that.
261 261 $.map(OutputArea.output_types, function(key){
262 262 if (json[key] !== undefined && typeof json[key] !== 'string') {
263 263 console.log("Invalid type for " + key, json[key]);
264 264 delete json[key];
265 265 }
266 266 });
267 267 return json;
268 268 };
269 269
270 270 OutputArea.prototype.append_output = function (json) {
271 271 this.expand();
272 272
273 273 // validate output data types
274 274 json = this.validate_output(json);
275 275
276 276 // Clear the output if clear is queued.
277 277 var needs_height_reset = false;
278 278 if (this.clear_queued) {
279 279 this.clear_output(false);
280 280 needs_height_reset = true;
281 281 }
282 282
283 283 if (json.output_type === 'execute_result') {
284 284 this.append_execute_result(json);
285 285 } else if (json.output_type === 'error') {
286 286 this.append_error(json);
287 287 } else if (json.output_type === 'stream') {
288 288 this.append_stream(json);
289 289 }
290 290
291 291 // We must release the animation fixed height in a callback since Gecko
292 292 // (FireFox) doesn't render the image immediately as the data is
293 293 // available.
294 294 var that = this;
295 295 var handle_appended = function ($el) {
296 296 // Only reset the height to automatic if the height is currently
297 297 // fixed (done by wait=True flag on clear_output).
298 298 if (needs_height_reset) {
299 299 that.element.height('');
300 300 }
301 301 that.element.trigger('resize');
302 302 };
303 303 if (json.output_type === 'display_data') {
304 304 this.append_display_data(json, handle_appended);
305 305 } else {
306 306 handle_appended();
307 307 }
308 308
309 309 this.outputs.push(json);
310 310 };
311 311
312 312
313 313 OutputArea.prototype.create_output_area = function () {
314 314 var oa = $("<div/>").addClass("output_area");
315 315 if (this.prompt_area) {
316 316 oa.append($('<div/>').addClass('prompt'));
317 317 }
318 318 return oa;
319 319 };
320 320
321 321
322 322 function _get_metadata_key(metadata, key, mime) {
323 323 var mime_md = metadata[mime];
324 324 // mime-specific higher priority
325 325 if (mime_md && mime_md[key] !== undefined) {
326 326 return mime_md[key];
327 327 }
328 328 // fallback on global
329 329 return metadata[key];
330 330 }
331 331
332 332 OutputArea.prototype.create_output_subarea = function(md, classes, mime) {
333 333 var subarea = $('<div/>').addClass('output_subarea').addClass(classes);
334 334 if (_get_metadata_key(md, 'isolated', mime)) {
335 335 // Create an iframe to isolate the subarea from the rest of the
336 336 // document
337 337 var iframe = $('<iframe/>').addClass('box-flex1');
338 338 iframe.css({'height':1, 'width':'100%', 'display':'block'});
339 339 iframe.attr('frameborder', 0);
340 340 iframe.attr('scrolling', 'auto');
341 341
342 342 // Once the iframe is loaded, the subarea is dynamically inserted
343 343 iframe.on('load', function() {
344 344 // Workaround needed by Firefox, to properly render svg inside
345 345 // iframes, see http://stackoverflow.com/questions/10177190/
346 346 // svg-dynamically-added-to-iframe-does-not-render-correctly
347 347 this.contentDocument.open();
348 348
349 349 // Insert the subarea into the iframe
350 350 // We must directly write the html. When using Jquery's append
351 351 // method, javascript is evaluated in the parent document and
352 352 // not in the iframe document. At this point, subarea doesn't
353 353 // contain any user content.
354 354 this.contentDocument.write(subarea.html());
355 355
356 356 this.contentDocument.close();
357 357
358 358 var body = this.contentDocument.body;
359 359 // Adjust the iframe height automatically
360 360 iframe.height(body.scrollHeight + 'px');
361 361 });
362 362
363 363 // Elements should be appended to the inner subarea and not to the
364 364 // iframe
365 365 iframe.append = function(that) {
366 366 subarea.append(that);
367 367 };
368 368
369 369 return iframe;
370 370 } else {
371 371 return subarea;
372 372 }
373 373 }
374 374
375 375
376 376 OutputArea.prototype._append_javascript_error = function (err, element) {
377 377 // display a message when a javascript error occurs in display output
378 378 var msg = "Javascript error adding output!"
379 379 if ( element === undefined ) return;
380 380 element
381 381 .append($('<div/>').text(msg).addClass('js-error'))
382 382 .append($('<div/>').text(err.toString()).addClass('js-error'))
383 383 .append($('<div/>').text('See your browser Javascript console for more details.').addClass('js-error'));
384 384 };
385 385
386 386 OutputArea.prototype._safe_append = function (toinsert) {
387 387 // safely append an item to the document
388 388 // this is an object created by user code,
389 389 // and may have errors, which should not be raised
390 390 // under any circumstances.
391 391 try {
392 392 this.element.append(toinsert);
393 393 } catch(err) {
394 394 console.log(err);
395 395 // Create an actual output_area and output_subarea, which creates
396 396 // the prompt area and the proper indentation.
397 397 var toinsert = this.create_output_area();
398 398 var subarea = $('<div/>').addClass('output_subarea');
399 399 toinsert.append(subarea);
400 400 this._append_javascript_error(err, subarea);
401 401 this.element.append(toinsert);
402 402 }
403 403 };
404 404
405 405
406 406 OutputArea.prototype.append_execute_result = function (json) {
407 407 var n = json.prompt_number || ' ';
408 408 var toinsert = this.create_output_area();
409 409 if (this.prompt_area) {
410 410 toinsert.find('div.prompt').addClass('output_prompt').text('Out[' + n + ']:');
411 411 }
412 412 var inserted = this.append_mime_type(json, toinsert);
413 413 if (inserted) {
414 414 inserted.addClass('output_result');
415 415 }
416 416 this._safe_append(toinsert);
417 417 // If we just output latex, typeset it.
418 418 if ((json['text/latex'] !== undefined) ||
419 419 (json['text/html'] !== undefined) ||
420 420 (json['text/markdown'] !== undefined)) {
421 421 this.typeset();
422 422 }
423 423 };
424 424
425 425
426 426 OutputArea.prototype.append_error = function (json) {
427 427 var tb = json.traceback;
428 428 if (tb !== undefined && tb.length > 0) {
429 429 var s = '';
430 430 var len = tb.length;
431 431 for (var i=0; i<len; i++) {
432 432 s = s + tb[i] + '\n';
433 433 }
434 434 s = s + '\n';
435 435 var toinsert = this.create_output_area();
436 436 var append_text = OutputArea.append_map['text/plain'];
437 437 if (append_text) {
438 438 append_text.apply(this, [s, {}, toinsert]).addClass('output_error');
439 439 }
440 440 this._safe_append(toinsert);
441 441 }
442 442 };
443 443
444 444
445 445 OutputArea.prototype.append_stream = function (json) {
446 446 // temporary fix: if stream undefined (json file written prior to this patch),
447 447 // default to most likely stdout:
448 448 if (json.stream === undefined){
449 449 json.stream = 'stdout';
450 450 }
451 451 var text = json.text;
452 452 var subclass = "output_"+json.stream;
453 453 if (this.outputs.length > 0){
454 454 // have at least one output to consider
455 455 var last = this.outputs[this.outputs.length-1];
456 456 if (last.output_type == 'stream' && json.stream == last.stream){
457 457 // latest output was in the same stream,
458 458 // so append directly into its pre tag
459 459 // escape ANSI & HTML specials:
460 460 var pre = this.element.find('div.'+subclass).last().find('pre');
461 461 var html = utils.fixCarriageReturn(
462 462 pre.html() + utils.fixConsole(text));
463 463 // The only user content injected with this HTML call is
464 464 // escaped by the fixConsole() method.
465 465 pre.html(html);
466 466 return;
467 467 }
468 468 }
469 469
470 470 if (!text.replace("\r", "")) {
471 471 // text is nothing (empty string, \r, etc.)
472 472 // so don't append any elements, which might add undesirable space
473 473 return;
474 474 }
475 475
476 476 // If we got here, attach a new div
477 477 var toinsert = this.create_output_area();
478 478 var append_text = OutputArea.append_map['text/plain'];
479 479 if (append_text) {
480 480 append_text.apply(this, [text, {}, toinsert]).addClass("output_stream " + subclass);
481 481 }
482 482 this._safe_append(toinsert);
483 483 };
484 484
485 485
486 486 OutputArea.prototype.append_display_data = function (json, handle_inserted) {
487 487 var toinsert = this.create_output_area();
488 488 if (this.append_mime_type(json, toinsert, handle_inserted)) {
489 489 this._safe_append(toinsert);
490 490 // If we just output latex, typeset it.
491 491 if ((json['text/latex'] !== undefined) ||
492 492 (json['text/html'] !== undefined) ||
493 493 (json['text/markdown'] !== undefined)) {
494 494 this.typeset();
495 495 }
496 496 }
497 497 };
498 498
499 499
500 500 OutputArea.safe_outputs = {
501 501 'text/plain' : true,
502 502 'text/latex' : true,
503 503 'image/png' : true,
504 504 'image/jpeg' : true
505 505 };
506 506
507 507 OutputArea.prototype.append_mime_type = function (json, element, handle_inserted) {
508 508 for (var i=0; i < OutputArea.display_order.length; i++) {
509 509 var type = OutputArea.display_order[i];
510 510 var append = OutputArea.append_map[type];
511 511 if ((json[type] !== undefined) && append) {
512 512 var value = json[type];
513 513 if (!this.trusted && !OutputArea.safe_outputs[type]) {
514 514 // not trusted, sanitize HTML
515 515 if (type==='text/html' || type==='text/svg') {
516 516 value = security.sanitize_html(value);
517 517 } else {
518 518 // don't display if we don't know how to sanitize it
519 519 console.log("Ignoring untrusted " + type + " output.");
520 520 continue;
521 521 }
522 522 }
523 523 var md = json.metadata || {};
524 524 var toinsert = append.apply(this, [value, md, element, handle_inserted]);
525 525 // Since only the png and jpeg mime types call the inserted
526 526 // callback, if the mime type is something other we must call the
527 527 // inserted callback only when the element is actually inserted
528 528 // into the DOM. Use a timeout of 0 to do this.
529 529 if (['image/png', 'image/jpeg'].indexOf(type) < 0 && handle_inserted !== undefined) {
530 530 setTimeout(handle_inserted, 0);
531 531 }
532 532 this.events.trigger('output_appended.OutputArea', [type, value, md, toinsert]);
533 533 return toinsert;
534 534 }
535 535 }
536 536 return null;
537 537 };
538 538
539 539
540 540 var append_html = function (html, md, element) {
541 541 var type = 'text/html';
542 542 var toinsert = this.create_output_subarea(md, "output_html rendered_html", type);
543 543 this.keyboard_manager.register_events(toinsert);
544 544 toinsert.append(html);
545 545 element.append(toinsert);
546 546 return toinsert;
547 547 };
548 548
549 549
550 550 var append_markdown = function(markdown, md, element) {
551 551 var type = 'text/markdown';
552 552 var toinsert = this.create_output_subarea(md, "output_markdown", type);
553 553 var text_and_math = mathjaxutils.remove_math(markdown);
554 554 var text = text_and_math[0];
555 555 var math = text_and_math[1];
556 556 var html = marked.parser(marked.lexer(text));
557 557 html = mathjaxutils.replace_math(html, math);
558 558 toinsert.append(html);
559 559 element.append(toinsert);
560 560 return toinsert;
561 561 };
562 562
563 563
564 564 var append_javascript = function (js, md, element) {
565 565 // We just eval the JS code, element appears in the local scope.
566 566 var type = 'application/javascript';
567 567 var toinsert = this.create_output_subarea(md, "output_javascript", type);
568 568 this.keyboard_manager.register_events(toinsert);
569 569 element.append(toinsert);
570 570
571 571 // Fix for ipython/issues/5293, make sure `element` is the area which
572 572 // output can be inserted into at the time of JS execution.
573 573 element = toinsert;
574 574 try {
575 575 eval(js);
576 576 } catch(err) {
577 577 console.log(err);
578 578 this._append_javascript_error(err, toinsert);
579 579 }
580 580 return toinsert;
581 581 };
582 582
583 583
584 584 var append_text = function (data, md, element) {
585 585 var type = 'text/plain';
586 586 var toinsert = this.create_output_subarea(md, "output_text", type);
587 587 // escape ANSI & HTML specials in plaintext:
588 588 data = utils.fixConsole(data);
589 589 data = utils.fixCarriageReturn(data);
590 590 data = utils.autoLinkUrls(data);
591 591 // The only user content injected with this HTML call is
592 592 // escaped by the fixConsole() method.
593 593 toinsert.append($("<pre/>").html(data));
594 594 element.append(toinsert);
595 595 return toinsert;
596 596 };
597 597
598 598
599 599 var append_svg = function (svg_html, md, element) {
600 600 var type = 'image/svg+xml';
601 601 var toinsert = this.create_output_subarea(md, "output_svg", type);
602 602
603 603 // Get the svg element from within the HTML.
604 604 var svg = $('<div />').html(svg_html).find('svg');
605 605 var svg_area = $('<div />');
606 606 var width = svg.attr('width');
607 607 var height = svg.attr('height');
608 608 svg
609 609 .width('100%')
610 610 .height('100%');
611 611 svg_area
612 612 .width(width)
613 613 .height(height);
614 614
615 615 // The jQuery resize handlers don't seem to work on the svg element.
616 616 // When the svg renders completely, measure it's size and set the parent
617 617 // div to that size. Then set the svg to 100% the size of the parent
618 618 // div and make the parent div resizable.
619 619 this._dblclick_to_reset_size(svg_area, true, false);
620 620
621 621 svg_area.append(svg);
622 622 toinsert.append(svg_area);
623 623 element.append(toinsert);
624 624
625 625 return toinsert;
626 626 };
627 627
628 628 OutputArea.prototype._dblclick_to_reset_size = function (img, immediately, resize_parent) {
629 629 // Add a resize handler to an element
630 630 //
631 631 // img: jQuery element
632 632 // immediately: bool=False
633 633 // Wait for the element to load before creating the handle.
634 634 // resize_parent: bool=True
635 635 // Should the parent of the element be resized when the element is
636 636 // reset (by double click).
637 637 var callback = function (){
638 638 var h0 = img.height();
639 639 var w0 = img.width();
640 640 if (!(h0 && w0)) {
641 641 // zero size, don't make it resizable
642 642 return;
643 643 }
644 644 img.resizable({
645 645 aspectRatio: true,
646 646 autoHide: true
647 647 });
648 648 img.dblclick(function () {
649 649 // resize wrapper & image together for some reason:
650 650 img.height(h0);
651 651 img.width(w0);
652 652 if (resize_parent === undefined || resize_parent) {
653 653 img.parent().height(h0);
654 654 img.parent().width(w0);
655 655 }
656 656 });
657 657 };
658 658
659 659 if (immediately) {
660 660 callback();
661 661 } else {
662 662 img.on("load", callback);
663 663 }
664 664 };
665 665
666 666 var set_width_height = function (img, md, mime) {
667 667 // set width and height of an img element from metadata
668 668 var height = _get_metadata_key(md, 'height', mime);
669 669 if (height !== undefined) img.attr('height', height);
670 670 var width = _get_metadata_key(md, 'width', mime);
671 671 if (width !== undefined) img.attr('width', width);
672 672 };
673 673
674 674 var append_png = function (png, md, element, handle_inserted) {
675 675 var type = 'image/png';
676 676 var toinsert = this.create_output_subarea(md, "output_png", type);
677 677 var img = $("<img/>");
678 678 if (handle_inserted !== undefined) {
679 679 img.on('load', function(){
680 680 handle_inserted(img);
681 681 });
682 682 }
683 683 img[0].src = 'data:image/png;base64,'+ png;
684 684 set_width_height(img, md, 'image/png');
685 685 this._dblclick_to_reset_size(img);
686 686 toinsert.append(img);
687 687 element.append(toinsert);
688 688 return toinsert;
689 689 };
690 690
691 691
692 692 var append_jpeg = function (jpeg, md, element, handle_inserted) {
693 693 var type = 'image/jpeg';
694 694 var toinsert = this.create_output_subarea(md, "output_jpeg", type);
695 695 var img = $("<img/>");
696 696 if (handle_inserted !== undefined) {
697 697 img.on('load', function(){
698 698 handle_inserted(img);
699 699 });
700 700 }
701 701 img[0].src = 'data:image/jpeg;base64,'+ jpeg;
702 702 set_width_height(img, md, 'image/jpeg');
703 703 this._dblclick_to_reset_size(img);
704 704 toinsert.append(img);
705 705 element.append(toinsert);
706 706 return toinsert;
707 707 };
708 708
709 709
710 710 var append_pdf = function (pdf, md, element) {
711 711 var type = 'application/pdf';
712 712 var toinsert = this.create_output_subarea(md, "output_pdf", type);
713 713 var a = $('<a/>').attr('href', 'data:application/pdf;base64,'+pdf);
714 714 a.attr('target', '_blank');
715 715 a.text('View PDF')
716 716 toinsert.append(a);
717 717 element.append(toinsert);
718 718 return toinsert;
719 719 }
720 720
721 721 var append_latex = function (latex, md, element) {
722 722 // This method cannot do the typesetting because the latex first has to
723 723 // be on the page.
724 724 var type = 'text/latex';
725 725 var toinsert = this.create_output_subarea(md, "output_latex", type);
726 726 toinsert.append(latex);
727 727 element.append(toinsert);
728 728 return toinsert;
729 729 };
730 730
731 731
732 732 OutputArea.prototype.append_raw_input = function (msg) {
733 733 var that = this;
734 734 this.expand();
735 735 var content = msg.content;
736 736 var area = this.create_output_area();
737 737
738 738 // disable any other raw_inputs, if they are left around
739 739 $("div.output_subarea.raw_input_container").remove();
740 740
741 741 var input_type = content.password ? 'password' : 'text';
742 742
743 743 area.append(
744 744 $("<div/>")
745 745 .addClass("box-flex1 output_subarea raw_input_container")
746 746 .append(
747 747 $("<span/>")
748 748 .addClass("raw_input_prompt")
749 749 .text(content.prompt)
750 750 )
751 751 .append(
752 752 $("<input/>")
753 753 .addClass("raw_input")
754 754 .attr('type', input_type)
755 755 .attr("size", 47)
756 756 .keydown(function (event, ui) {
757 757 // make sure we submit on enter,
758 758 // and don't re-execute the *cell* on shift-enter
759 759 if (event.which === keyboard.keycodes.enter) {
760 760 that._submit_raw_input();
761 761 return false;
762 762 }
763 763 })
764 764 )
765 765 );
766 766
767 767 this.element.append(area);
768 768 var raw_input = area.find('input.raw_input');
769 769 // Register events that enable/disable the keyboard manager while raw
770 770 // input is focused.
771 771 this.keyboard_manager.register_events(raw_input);
772 772 // Note, the following line used to read raw_input.focus().focus().
773 773 // This seemed to be needed otherwise only the cell would be focused.
774 774 // But with the modal UI, this seems to work fine with one call to focus().
775 775 raw_input.focus();
776 776 }
777 777
778 778 OutputArea.prototype._submit_raw_input = function (evt) {
779 779 var container = this.element.find("div.raw_input_container");
780 780 var theprompt = container.find("span.raw_input_prompt");
781 781 var theinput = container.find("input.raw_input");
782 782 var value = theinput.val();
783 783 var echo = value;
784 784 // don't echo if it's a password
785 785 if (theinput.attr('type') == 'password') {
786 786 echo = '········';
787 787 }
788 788 var content = {
789 789 output_type : 'stream',
790 790 stream : 'stdout',
791 791 text : theprompt.text() + echo + '\n'
792 792 }
793 793 // remove form container
794 794 container.parent().remove();
795 795 // replace with plaintext version in stdout
796 796 this.append_output(content, false);
797 797 this.events.trigger('send_input_reply.Kernel', value);
798 798 }
799 799
800 800
801 801 OutputArea.prototype.handle_clear_output = function (msg) {
802 802 // msg spec v4 had stdout, stderr, display keys
803 803 // v4.1 replaced these with just wait
804 804 // The default behavior is the same (stdout=stderr=display=True, wait=False),
805 805 // so v4 messages will still be properly handled,
806 806 // except for the rarely used clearing less than all output.
807 807 this.clear_output(msg.content.wait || false);
808 808 };
809 809
810 810
811 811 OutputArea.prototype.clear_output = function(wait) {
812 812 if (wait) {
813 813
814 814 // If a clear is queued, clear before adding another to the queue.
815 815 if (this.clear_queued) {
816 816 this.clear_output(false);
817 817 };
818 818
819 819 this.clear_queued = true;
820 820 } else {
821 821
822 822 // Fix the output div's height if the clear_output is waiting for
823 823 // new output (it is being used in an animation).
824 824 if (this.clear_queued) {
825 825 var height = this.element.height();
826 826 this.element.height(height);
827 827 this.clear_queued = false;
828 828 }
829 829
830 830 // Clear all
831 831 // Remove load event handlers from img tags because we don't want
832 832 // them to fire if the image is never added to the page.
833 833 this.element.find('img').off('load');
834 834 this.element.html("");
835 835 this.outputs = [];
836 836 this.trusted = true;
837 837 this.unscroll_area();
838 838 return;
839 839 };
840 840 };
841 841
842 842
843 843 // JSON serialization
844 844
845 845 OutputArea.prototype.fromJSON = function (outputs) {
846 846 var len = outputs.length;
847 847 var data;
848 848
849 849 for (var i=0; i<len; i++) {
850 850 data = outputs[i];
851 851 var msg_type = data.output_type;
852 852 if (msg_type == "pyout") {
853 853 // pyout message has been renamed to execute_result,
854 854 // but the nbformat has not been updated,
855 855 // so transform back to pyout for json.
856 856 msg_type = data.output_type = "execute_result";
857 857 } else if (msg_type == "pyerr") {
858 858 // pyerr message has been renamed to error,
859 859 // but the nbformat has not been updated,
860 860 // so transform back to pyerr for json.
861 861 msg_type = data.output_type = "error";
862 862 }
863 863 if (msg_type === "display_data" || msg_type === "execute_result") {
864 864 // convert short keys to mime keys
865 865 // TODO: remove mapping of short keys when we update to nbformat 4
866 866 data = this.rename_keys(data, OutputArea.mime_map_r);
867 867 data.metadata = this.rename_keys(data.metadata, OutputArea.mime_map_r);
868 868 // msg spec JSON is an object, nbformat v3 JSON is a JSON string
869 869 if (data["application/json"] !== undefined && typeof data["application/json"] === 'string') {
870 870 data["application/json"] = JSON.parse(data["application/json"]);
871 871 }
872 872 }
873 873
874 874 this.append_output(data);
875 875 }
876 876 };
877 877
878 878
879 879 OutputArea.prototype.toJSON = function () {
880 880 var outputs = [];
881 881 var len = this.outputs.length;
882 882 var data;
883 883 for (var i=0; i<len; i++) {
884 884 data = this.outputs[i];
885 885 var msg_type = data.output_type;
886 886 if (msg_type === "display_data" || msg_type === "execute_result") {
887 887 // convert mime keys to short keys
888 888 data = this.rename_keys(data, OutputArea.mime_map);
889 889 data.metadata = this.rename_keys(data.metadata, OutputArea.mime_map);
890 890 // msg spec JSON is an object, nbformat v3 JSON is a JSON string
891 891 if (data.json !== undefined && typeof data.json !== 'string') {
892 892 data.json = JSON.stringify(data.json);
893 893 }
894 894 }
895 895 if (msg_type == "execute_result") {
896 896 // pyout message has been renamed to execute_result,
897 897 // but the nbformat has not been updated,
898 898 // so transform back to pyout for json.
899 899 data.output_type = "pyout";
900 900 } else if (msg_type == "error") {
901 901 // pyerr message has been renamed to error,
902 902 // but the nbformat has not been updated,
903 903 // so transform back to pyerr for json.
904 904 data.output_type = "pyerr";
905 905 }
906 906 outputs[i] = data;
907 907 }
908 908 return outputs;
909 909 };
910 910
911 911 /**
912 912 * Class properties
913 913 **/
914 914
915 915 /**
916 916 * Threshold to trigger autoscroll when the OutputArea is resized,
917 917 * typically when new outputs are added.
918 918 *
919 919 * Behavior is undefined if autoscroll is lower than minimum_scroll_threshold,
920 920 * unless it is < 0, in which case autoscroll will never be triggered
921 921 *
922 922 * @property auto_scroll_threshold
923 923 * @type Number
924 924 * @default 100
925 925 *
926 926 **/
927 927 OutputArea.auto_scroll_threshold = 100;
928 928
929 929 /**
930 930 * Lower limit (in lines) for OutputArea to be made scrollable. OutputAreas
931 931 * shorter than this are never scrolled.
932 932 *
933 933 * @property minimum_scroll_threshold
934 934 * @type Number
935 935 * @default 20
936 936 *
937 937 **/
938 938 OutputArea.minimum_scroll_threshold = 20;
939 939
940 940
941 941
942 942 OutputArea.mime_map = {
943 943 "text/plain" : "text",
944 944 "text/html" : "html",
945 945 "image/svg+xml" : "svg",
946 946 "image/png" : "png",
947 947 "image/jpeg" : "jpeg",
948 948 "text/latex" : "latex",
949 949 "application/json" : "json",
950 950 "application/javascript" : "javascript",
951 951 };
952 952
953 953 OutputArea.mime_map_r = {
954 954 "text" : "text/plain",
955 955 "html" : "text/html",
956 956 "svg" : "image/svg+xml",
957 957 "png" : "image/png",
958 958 "jpeg" : "image/jpeg",
959 959 "latex" : "text/latex",
960 960 "json" : "application/json",
961 961 "javascript" : "application/javascript",
962 962 };
963 963
964 964 OutputArea.display_order = [
965 965 'application/javascript',
966 966 'text/html',
967 967 'text/markdown',
968 968 'text/latex',
969 969 'image/svg+xml',
970 970 'image/png',
971 971 'image/jpeg',
972 972 'application/pdf',
973 973 'text/plain'
974 974 ];
975 975
976 976 OutputArea.append_map = {
977 977 "text/plain" : append_text,
978 978 "text/html" : append_html,
979 979 "text/markdown": append_markdown,
980 980 "image/svg+xml" : append_svg,
981 981 "image/png" : append_png,
982 982 "image/jpeg" : append_jpeg,
983 983 "text/latex" : append_latex,
984 984 "application/javascript" : append_javascript,
985 985 "application/pdf" : append_pdf
986 986 };
987 987
988 988 // For backwards compatability.
989 989 IPython.OutputArea = OutputArea;
990 990
991 return OutputArea;
991 return {'OutputArea': OutputArea};
992 992 });
@@ -1,178 +1,178 b''
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 4 define([
5 5 'base/js/namespace',
6 6 'jquery',
7 7 'base/js/utils',
8 8 ], function(IPython, $, utils) {
9 9 "use strict";
10 10
11 11 var Pager = function (pager_selector, pager_splitter_selector, layout_manager, events) {
12 12 this.events = events;
13 13 this.pager_element = $(pager_selector);
14 14 this.pager_button_area = $('#pager_button_area');
15 15 var that = this;
16 16 this.percentage_height = 0.40;
17 17 layout_manager.pager = this;
18 18 this.pager_splitter_element = $(pager_splitter_selector)
19 19 .draggable({
20 20 containment: 'window',
21 21 axis:'y',
22 22 helper: null ,
23 23 drag: function(event, ui) {
24 24 // recalculate the amount of space the pager should take
25 25 var pheight = ($(document.body).height()-event.clientY-4);
26 26 var downprct = pheight/layout_manager.app_height();
27 27 downprct = Math.min(0.9, downprct);
28 28 if (downprct < 0.1) {
29 29 that.percentage_height = 0.1;
30 30 that.collapse({'duration':0});
31 31 } else if (downprct > 0.2) {
32 32 that.percentage_height = downprct;
33 33 that.expand({'duration':0});
34 34 }
35 35 layout_manager.do_resize();
36 36 }
37 37 });
38 38 this.expanded = false;
39 39 this.style();
40 40 this.create_button_area();
41 41 this.bind_events();
42 42 };
43 43
44 44 Pager.prototype.create_button_area = function(){
45 45 var that = this;
46 46 this.pager_button_area.append(
47 47 $('<a>').attr('role', "button")
48 48 .attr('title',"Open the pager in an external window")
49 49 .addClass('ui-button')
50 50 .click(function(){that.detach();})
51 51 .attr('style','position: absolute; right: 20px;')
52 52 .append(
53 53 $('<span>').addClass("ui-icon ui-icon-extlink")
54 54 )
55 55 );
56 56 this.pager_button_area.append(
57 57 $('<a>').attr('role', "button")
58 58 .attr('title',"Close the pager")
59 59 .addClass('ui-button')
60 60 .click(function(){that.collapse();})
61 61 .attr('style','position: absolute; right: 5px;')
62 62 .append(
63 63 $('<span>').addClass("ui-icon ui-icon-close")
64 64 )
65 65 );
66 66 };
67 67
68 68 Pager.prototype.style = function () {
69 69 this.pager_splitter_element.addClass('border-box-sizing ui-widget ui-state-default');
70 70 this.pager_element.addClass('border-box-sizing');
71 71 this.pager_element.find(".container").addClass('border-box-sizing');
72 72 this.pager_splitter_element.attr('title', 'Click to Show/Hide pager area, drag to Resize');
73 73 };
74 74
75 75
76 76 Pager.prototype.bind_events = function () {
77 77 var that = this;
78 78
79 79 this.pager_element.bind('collapse_pager', function (event, extrap) {
80 80 var time = 'fast';
81 81 if (extrap && extrap.duration) {
82 82 time = extrap.duration;
83 83 }
84 84 that.pager_element.hide(time);
85 85 });
86 86
87 87 this.pager_element.bind('expand_pager', function (event, extrap) {
88 88 var time = 'fast';
89 89 if (extrap && extrap.duration) {
90 90 time = extrap.duration;
91 91 }
92 92 that.pager_element.show(time);
93 93 });
94 94
95 95 this.pager_splitter_element.hover(
96 96 function () {
97 97 that.pager_splitter_element.addClass('ui-state-hover');
98 98 },
99 99 function () {
100 100 that.pager_splitter_element.removeClass('ui-state-hover');
101 101 }
102 102 );
103 103
104 104 this.pager_splitter_element.click(function () {
105 105 that.toggle();
106 106 });
107 107
108 108 this.events.on('open_with_text.Pager', function (event, payload) {
109 109 // FIXME: support other mime types
110 110 if (payload.data['text/plain'] && payload.data['text/plain'] !== "") {
111 111 that.clear();
112 112 that.expand();
113 113 that.append_text(payload.data['text/plain']);
114 114 }
115 115 });
116 116 };
117 117
118 118
119 119 Pager.prototype.collapse = function (extrap) {
120 120 if (this.expanded === true) {
121 121 this.expanded = false;
122 122 this.pager_element.add($('div#notebook')).trigger('collapse_pager', extrap);
123 123 }
124 124 };
125 125
126 126
127 127 Pager.prototype.expand = function (extrap) {
128 128 if (this.expanded !== true) {
129 129 this.expanded = true;
130 130 this.pager_element.add($('div#notebook')).trigger('expand_pager', extrap);
131 131 }
132 132 };
133 133
134 134
135 135 Pager.prototype.toggle = function () {
136 136 if (this.expanded === true) {
137 137 this.collapse();
138 138 } else {
139 139 this.expand();
140 140 }
141 141 };
142 142
143 143
144 144 Pager.prototype.clear = function (text) {
145 145 this.pager_element.find(".container").empty();
146 146 };
147 147
148 148 Pager.prototype.detach = function(){
149 149 var w = window.open("","_blank");
150 150 $(w.document.head)
151 151 .append(
152 152 $('<link>')
153 153 .attr('rel',"stylesheet")
154 154 .attr('href',"/static/css/notebook.css")
155 155 .attr('type',"text/css")
156 156 )
157 157 .append(
158 158 $('<title>').text("IPython Pager")
159 159 );
160 160 var pager_body = $(w.document.body);
161 161 pager_body.css('overflow','scroll');
162 162
163 163 pager_body.append(this.pager_element.clone().children());
164 164 w.document.close();
165 165 this.collapse();
166 166 };
167 167
168 168 Pager.prototype.append_text = function (text) {
169 169 // The only user content injected with this HTML call is escaped by
170 170 // the fixConsole() method.
171 171 this.pager_element.find(".container").append($('<pre/>').html(utils.fixCarriageReturn(utils.fixConsole(text))));
172 172 };
173 173
174 174 // Backwards compatability.
175 175 IPython.Pager = Pager;
176 176
177 return Pager;
177 return {'Pager': Pager};
178 178 });
@@ -1,173 +1,173 b''
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 4 define([
5 5 'base/js/namespace',
6 6 'jquery',
7 7 'base/js/utils',
8 8 'base/js/dialog',
9 9 ], function(IPython, $, utils, Dialog) {
10 10 "use strict";
11 11 var platform = utils.platform;
12 12
13 13 var QuickHelp = function (selector, keyboard_manager, events) {
14 14 this.keyboard_manager = keyboard_manager;
15 15 keyboard_manager.quick_help = this;
16 16 this.events = events;
17 17 };
18 18
19 19 var cmd_ctrl = 'Ctrl-';
20 20 var platform_specific;
21 21
22 22 if (platform === 'MacOS') {
23 23 // Mac OS X specific
24 24 cmd_ctrl = 'Cmd-';
25 25 platform_specific = [
26 26 { shortcut: "Cmd-Up", help:"go to cell start" },
27 27 { shortcut: "Cmd-Down", help:"go to cell end" },
28 28 { shortcut: "Opt-Left", help:"go one word left" },
29 29 { shortcut: "Opt-Right", help:"go one word right" },
30 30 { shortcut: "Opt-Backspace", help:"del word before" },
31 31 { shortcut: "Opt-Delete", help:"del word after" },
32 32 ];
33 33 } else {
34 34 // PC specific
35 35 platform_specific = [
36 36 { shortcut: "Ctrl-Home", help:"go to cell start" },
37 37 { shortcut: "Ctrl-Up", help:"go to cell start" },
38 38 { shortcut: "Ctrl-End", help:"go to cell end" },
39 39 { shortcut: "Ctrl-Down", help:"go to cell end" },
40 40 { shortcut: "Ctrl-Left", help:"go one word left" },
41 41 { shortcut: "Ctrl-Right", help:"go one word right" },
42 42 { shortcut: "Ctrl-Backspace", help:"del word before" },
43 43 { shortcut: "Ctrl-Delete", help:"del word after" },
44 44 ];
45 45 }
46 46
47 47 var cm_shortcuts = [
48 48 { shortcut:"Tab", help:"code completion or indent" },
49 49 { shortcut:"Shift-Tab", help:"tooltip" },
50 50 { shortcut: cmd_ctrl + "]", help:"indent" },
51 51 { shortcut: cmd_ctrl + "[", help:"dedent" },
52 52 { shortcut: cmd_ctrl + "a", help:"select all" },
53 53 { shortcut: cmd_ctrl + "z", help:"undo" },
54 54 { shortcut: cmd_ctrl + "Shift-z", help:"redo" },
55 55 { shortcut: cmd_ctrl + "y", help:"redo" },
56 56 ].concat( platform_specific );
57 57
58 58
59 59
60 60
61 61
62 62
63 63 QuickHelp.prototype.show_keyboard_shortcuts = function () {
64 64 // toggles display of keyboard shortcut dialog
65 65 var that = this;
66 66 if ( this.force_rebuild ) {
67 67 this.shortcut_dialog.remove();
68 68 delete(this.shortcut_dialog);
69 69 this.force_rebuild = false;
70 70 }
71 71 if ( this.shortcut_dialog ){
72 72 // if dialog is already shown, close it
73 73 $(this.shortcut_dialog).modal("toggle");
74 74 return;
75 75 }
76 76 var command_shortcuts = keyboard_manager.command_shortcuts.help();
77 77 var edit_shortcuts = keyboard_manager.edit_shortcuts.help();
78 78 var help, shortcut;
79 79 var i, half, n;
80 80 var element = $('<div/>');
81 81
82 82 // The documentation
83 83 var doc = $('<div/>').addClass('alert alert-warning');
84 84 doc.append(
85 85 $('<button/>').addClass('close').attr('data-dismiss','alert').html('&times;')
86 86 ).append(
87 87 'The IPython Notebook has two different keyboard input modes. <b>Edit mode</b> '+
88 88 'allows you to type code/text into a cell and is indicated by a green cell '+
89 89 'border. <b>Command mode</b> binds the keyboard to notebook level actions '+
90 90 'and is indicated by a grey cell border.'
91 91 );
92 92 element.append(doc);
93 93
94 94 // Command mode
95 95 var cmd_div = this.build_command_help();
96 96 element.append(cmd_div);
97 97
98 98 // Edit mode
99 99 var edit_div = this.build_edit_help(cm_shortcuts);
100 100 element.append(edit_div);
101 101
102 102 this.shortcut_dialog = Dialog.modal({
103 103 title : "Keyboard shortcuts",
104 104 body : element,
105 105 destroy : false,
106 106 buttons : {
107 107 Close : {}
108 108 }
109 109 });
110 110 this.shortcut_dialog.addClass("modal_stretch");
111 111
112 112 this.events.on('rebuild.QuickHelp', function() { that.force_rebuild = true;});
113 113 };
114 114
115 115 QuickHelp.prototype.build_command_help = function () {
116 116 var command_shortcuts = keyboard_manager.command_shortcuts.help();
117 117 return build_div('<h4>Command Mode (press <code>Esc</code> to enable)</h4>', command_shortcuts);
118 118 };
119 119
120 120 var special_case = { pageup: "PageUp", pagedown: "Page Down", 'minus': '-' };
121 121 var prettify = function (s) {
122 122 s = s.replace(/-$/, 'minus'); // catch shortcuts using '-' key
123 123 var keys = s.split('-');
124 124 var k, i;
125 125 for (i=0; i < keys.length; i++) {
126 126 k = keys[i];
127 127 if ( k.length == 1 ) {
128 128 keys[i] = "<code><strong>" + k + "</strong></code>";
129 129 continue; // leave individual keys lower-cased
130 130 }
131 131 keys[i] = ( special_case[k] ? special_case[k] : k.charAt(0).toUpperCase() + k.slice(1) );
132 132 keys[i] = "<code><strong>" + keys[i] + "</strong></code>";
133 133 }
134 134 return keys.join('-');
135 135
136 136
137 137 };
138 138
139 139 QuickHelp.prototype.build_edit_help = function (cm_shortcuts) {
140 140 var edit_shortcuts = keyboard_manager.edit_shortcuts.help();
141 141 jQuery.merge(cm_shortcuts, edit_shortcuts);
142 142 return build_div('<h4>Edit Mode (press <code>Enter</code> to enable)</h4>', cm_shortcuts);
143 143 };
144 144
145 145 var build_one = function (s) {
146 146 var help = s.help;
147 147 var shortcut = prettify(s.shortcut);
148 148 return $('<div>').addClass('quickhelp').
149 149 append($('<span/>').addClass('shortcut_key').append($(shortcut))).
150 150 append($('<span/>').addClass('shortcut_descr').text(' : ' + help));
151 151
152 152 };
153 153
154 154 var build_div = function (title, shortcuts) {
155 155 var i, half, n;
156 156 var div = $('<div/>').append($(title));
157 157 var sub_div = $('<div/>').addClass('hbox');
158 158 var col1 = $('<div/>').addClass('box-flex1');
159 159 var col2 = $('<div/>').addClass('box-flex1');
160 160 n = shortcuts.length;
161 161 half = ~~(n/2); // Truncate :)
162 162 for (i=0; i<half; i++) { col1.append( build_one(shortcuts[i]) ); }
163 163 for (i=half; i<n; i++) { col2.append( build_one(shortcuts[i]) ); }
164 164 sub_div.append(col1).append(col2);
165 165 div.append(sub_div);
166 166 return div;
167 167 };
168 168
169 169 // Backwards compatability.
170 170 IPython.QuickHelp = QuickHelp;
171 171
172 return QuickHelp;
172 return {'QuickHelp': QuickHelp};
173 173 });
@@ -1,172 +1,172 b''
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 4 define([
5 5 'base/js/namespace',
6 6 'jquery',
7 7 'base/js/utils',
8 8 'base/js/dialog',
9 9 'base/js/keyboard',
10 10 'dateformat/date.format',
11 11 ], function(IPython, $, utils, Dialog, keyboard) {
12 12 "use strict";
13 13
14 14 var SaveWidget = function (selector, events) {
15 15 this.notebook = undefined;
16 16 this.selector = selector;
17 17 this.events = events;
18 18 if (this.selector !== undefined) {
19 19 this.element = $(selector);
20 20 this.style();
21 21 this.bind_events();
22 22 }
23 23 };
24 24
25 25 SaveWidget.prototype.style = function () {
26 26 };
27 27
28 28
29 29 SaveWidget.prototype.bind_events = function () {
30 30 var that = this;
31 31 this.element.find('span#notebook_name').click(function () {
32 32 that.rename_notebook();
33 33 });
34 34 this.element.find('span#notebook_name').hover(function () {
35 35 $(this).addClass("ui-state-hover");
36 36 }, function () {
37 37 $(this).removeClass("ui-state-hover");
38 38 });
39 39 this.events.on('notebook_loaded.Notebook', function () {
40 40 that.update_notebook_name();
41 41 that.update_document_title();
42 42 });
43 43 this.events.on('notebook_saved.Notebook', function () {
44 44 that.update_notebook_name();
45 45 that.update_document_title();
46 46 });
47 47 this.events.on('notebook_renamed.Notebook', function () {
48 48 that.update_notebook_name();
49 49 that.update_document_title();
50 50 that.update_address_bar();
51 51 });
52 52 this.events.on('notebook_save_failed.Notebook', function () {
53 53 that.set_save_status('Autosave Failed!');
54 54 });
55 55 this.events.on('checkpoints_listed.Notebook', function (event, data) {
56 56 that.set_last_checkpoint(data[0]);
57 57 });
58 58
59 59 this.events.on('checkpoint_created.Notebook', function (event, data) {
60 60 that.set_last_checkpoint(data);
61 61 });
62 62 this.events.on('set_dirty.Notebook', function (event, data) {
63 63 that.set_autosaved(data.value);
64 64 });
65 65 };
66 66
67 67
68 68 SaveWidget.prototype.rename_notebook = function () {
69 69 var that = this;
70 70 var dialog = $('<div/>').append(
71 71 $("<p/>").addClass("rename-message")
72 72 .text('Enter a new notebook name:')
73 73 ).append(
74 74 $("<br/>")
75 75 ).append(
76 76 $('<input/>').attr('type','text').attr('size','25').addClass('form-control')
77 77 .val(that.notebook.get_notebook_name())
78 78 );
79 79 Dialog.modal({
80 80 title: "Rename Notebook",
81 81 body: dialog,
82 82 buttons : {
83 83 "Cancel": {},
84 84 "OK": {
85 85 class: "btn-primary",
86 86 click: function () {
87 87 var new_name = $(this).find('input').val();
88 88 if (!that.notebook.test_notebook_name(new_name)) {
89 89 $(this).find('.rename-message').text(
90 90 "Invalid notebook name. Notebook names must "+
91 91 "have 1 or more characters and can contain any characters " +
92 92 "except :/\\. Please enter a new notebook name:"
93 93 );
94 94 return false;
95 95 } else {
96 96 that.notebook.rename(new_name);
97 97 }
98 98 }}
99 99 },
100 100 open : function (event, ui) {
101 101 var that = $(this);
102 102 // Upon ENTER, click the OK button.
103 103 that.find('input[type="text"]').keydown(function (event, ui) {
104 104 if (event.which === that.keyboard.keycodes.enter) {
105 105 that.find('.btn-primary').first().click();
106 106 return false;
107 107 }
108 108 });
109 109 that.find('input[type="text"]').focus().select();
110 110 }
111 111 });
112 112 };
113 113
114 114
115 115 SaveWidget.prototype.update_notebook_name = function () {
116 116 var nbname = this.notebook.get_notebook_name();
117 117 this.element.find('span#notebook_name').text(nbname);
118 118 };
119 119
120 120
121 121 SaveWidget.prototype.update_document_title = function () {
122 122 var nbname = this.notebook.get_notebook_name();
123 123 document.title = nbname;
124 124 };
125 125
126 126 SaveWidget.prototype.update_address_bar = function(){
127 127 var base_url = this.notebook.base_url;
128 128 var nbname = this.notebook.notebook_name;
129 129 var path = this.notebook.notebook_path;
130 130 var state = {path : path, name: nbname};
131 131 window.history.replaceState(state, "", utils.url_join_encode(
132 132 base_url,
133 133 "notebooks",
134 134 path,
135 135 nbname)
136 136 );
137 137 };
138 138
139 139
140 140 SaveWidget.prototype.set_save_status = function (msg) {
141 141 this.element.find('span#autosave_status').text(msg);
142 142 };
143 143
144 144 SaveWidget.prototype.set_checkpoint_status = function (msg) {
145 145 this.element.find('span#checkpoint_status').text(msg);
146 146 };
147 147
148 148 SaveWidget.prototype.set_last_checkpoint = function (checkpoint) {
149 149 if (!checkpoint) {
150 150 this.set_checkpoint_status("");
151 151 return;
152 152 }
153 153 var d = new Date(checkpoint.last_modified);
154 154 this.set_checkpoint_status(
155 155 "Last Checkpoint: " + d.format('mmm dd HH:MM')
156 156 );
157 157 };
158 158
159 159 SaveWidget.prototype.set_autosaved = function (dirty) {
160 160 if (dirty) {
161 161 this.set_save_status("(unsaved changes)");
162 162 } else {
163 163 this.set_save_status("(autosaved)");
164 164 }
165 165 };
166 166
167 167 // Backwards compatability.
168 168 IPython.SaveWidget = SaveWidget;
169 169
170 return SaveWidget;
170 return {'SaveWidget': SaveWidget};
171 171
172 172 });
@@ -1,101 +1,101 b''
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 4 define([
5 5 'base/js/namespace',
6 6 'jquery',
7 7 ], function(IPython, $) {
8 8 "use strict";
9 9
10 10 /**
11 11 * A generic toolbar on which one can add button
12 12 * @class ToolBar
13 13 * @constructor
14 14 * @param {Dom object} selector
15 15 */
16 16 var ToolBar = function (selector, layout_manager) {
17 17 this.selector = selector;
18 18 this.layout_manager = layout_manager;
19 19 if (this.selector !== undefined) {
20 20 this.element = $(selector);
21 21 this.style();
22 22 }
23 23 };
24 24
25 25 /**
26 26 * add a group of button into the current toolbar.
27 27 *
28 28 *
29 29 * @example
30 30 *
31 31 * IPython.toolbar.add_buttons_group([
32 32 * {
33 33 * label:'my button',
34 34 * icon:'icon-hdd',
35 35 * callback:function(){alert('hoho')},
36 36 * id : 'my_button_id', // this is optional
37 37 * },
38 38 * {
39 39 * label:'my second button',
40 40 * icon:'icon-play',
41 41 * callback:function(){alert('be carefull I cut')}
42 42 * }
43 43 * ],
44 44 * "my_button_group_id"
45 45 * )
46 46 *
47 47 * @method add_buttons_group
48 48 * @param list {List}
49 49 * List of button of the group, with the following paramter for each :
50 50 * @param list.label {string} text to show on button hover
51 51 * @param list.icon {string} icon to choose from [Font Awesome](http://fortawesome.github.io/Font-Awesome)
52 52 * @param list.callback {function} function to be called on button click
53 53 * @param [list.id] {String} id to give to the button
54 54 * @param [group_id] {String} optionnal id to give to the group
55 55 *
56 56 */
57 57 ToolBar.prototype.add_buttons_group = function (list, group_id) {
58 58 var btn_group = $('<div/>').addClass("btn-group");
59 59 if( group_id !== undefined ) {
60 60 btn_group.attr('id',group_id);
61 61 }
62 62 var el;
63 63 for(var i=0; i < list.length; i++) {
64 64 el = list[i];
65 65 var button = $('<button/>')
66 66 .addClass('btn btn-default')
67 67 .attr("title", el.label)
68 68 .append(
69 69 $("<i/>").addClass(el.icon)
70 70 );
71 71 var id = el.id;
72 72 if( id !== undefined )
73 73 button.attr('id',id);
74 74 var fun = el.callback;
75 75 button.click(fun);
76 76 btn_group.append(button);
77 77 }
78 78 $(this.selector).append(btn_group);
79 79 };
80 80
81 81 ToolBar.prototype.style = function () {
82 82 this.element.addClass('border-box-sizing')
83 83 .addClass('toolbar');
84 84 };
85 85
86 86 /**
87 87 * Show and hide toolbar
88 88 * @method toggle
89 89 */
90 90 ToolBar.prototype.toggle = function () {
91 91 this.element.toggle();
92 92 if (this.layout_manager !== undefined) {
93 93 this.layout_manager.do_resize();
94 94 }
95 95 };
96 96
97 97 // Backwards compatability.
98 98 IPython.ToolBar = ToolBar;
99 99
100 return ToolBar;
100 return {'ToolBar': ToolBar};
101 101 });
@@ -1,335 +1,335 b''
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 4 define([
5 5 'base/js/namespace',
6 6 'jquery',
7 7 'base/js/utils',
8 8 ], function(IPython, $, utils) {
9 9 "use strict";
10 10
11 11 // tooltip constructor
12 12 var Tooltip = function (events) {
13 13 var that = this;
14 14 this.events = events;
15 15 this.time_before_tooltip = 1200;
16 16
17 17 // handle to html
18 18 this.tooltip = $('#tooltip');
19 19 this._hidden = true;
20 20
21 21 // variable for consecutive call
22 22 this._old_cell = null;
23 23 this._old_request = null;
24 24 this._consecutive_counter = 0;
25 25
26 26 // 'sticky ?'
27 27 this._sticky = false;
28 28
29 29 // display tooltip if the docstring is empty?
30 30 this._hide_if_no_docstring = false;
31 31
32 32 // contain the button in the upper right corner
33 33 this.buttons = $('<div/>').addClass('tooltipbuttons');
34 34
35 35 // will contain the docstring
36 36 this.text = $('<div/>').addClass('tooltiptext').addClass('smalltooltip');
37 37
38 38 // build the buttons menu on the upper right
39 39 // expand the tooltip to see more
40 40 var expandlink = $('<a/>').attr('href', "#").addClass("ui-corner-all") //rounded corner
41 41 .attr('role', "button").attr('id', 'expanbutton').attr('title', 'Grow the tooltip vertically (press shift-tab twice)').click(function () {
42 42 that.expand();
43 43 }).append(
44 44 $('<span/>').text('Expand').addClass('ui-icon').addClass('ui-icon-plus'));
45 45
46 46 // open in pager
47 47 var morelink = $('<a/>').attr('href', "#").attr('role', "button").addClass('ui-button').attr('title', 'show the current docstring in pager (press shift-tab 4 times)');
48 48 var morespan = $('<span/>').text('Open in Pager').addClass('ui-icon').addClass('ui-icon-arrowstop-l-n');
49 49 morelink.append(morespan);
50 50 morelink.click(function () {
51 51 that.showInPager(that._old_cell);
52 52 });
53 53
54 54 // close the tooltip
55 55 var closelink = $('<a/>').attr('href', "#").attr('role', "button").addClass('ui-button');
56 56 var closespan = $('<span/>').text('Close').addClass('ui-icon').addClass('ui-icon-close');
57 57 closelink.append(closespan);
58 58 closelink.click(function () {
59 59 that.remove_and_cancel_tooltip(true);
60 60 });
61 61
62 62 this._clocklink = $('<a/>').attr('href', "#");
63 63 this._clocklink.attr('role', "button");
64 64 this._clocklink.addClass('ui-button');
65 65 this._clocklink.attr('title', 'Tootip is not dismissed while typing for 10 seconds');
66 66 var clockspan = $('<span/>').text('Close');
67 67 clockspan.addClass('ui-icon');
68 68 clockspan.addClass('ui-icon-clock');
69 69 this._clocklink.append(clockspan);
70 70 this._clocklink.click(function () {
71 71 that.cancel_stick();
72 72 });
73 73
74 74
75 75
76 76
77 77 //construct the tooltip
78 78 // add in the reverse order you want them to appear
79 79 this.buttons.append(closelink);
80 80 this.buttons.append(expandlink);
81 81 this.buttons.append(morelink);
82 82 this.buttons.append(this._clocklink);
83 83 this._clocklink.hide();
84 84
85 85
86 86 // we need a phony element to make the small arrow
87 87 // of the tooltip in css
88 88 // we will move the arrow later
89 89 this.arrow = $('<div/>').addClass('pretooltiparrow');
90 90 this.tooltip.append(this.buttons);
91 91 this.tooltip.append(this.arrow);
92 92 this.tooltip.append(this.text);
93 93
94 94 // function that will be called if you press tab 1, 2, 3... times in a row
95 95 this.tabs_functions = [function (cell, text, cursor) {
96 96 that._request_tooltip(cell, text, cursor);
97 97 }, function () {
98 98 that.expand();
99 99 }, function () {
100 100 that.stick();
101 101 }, function (cell) {
102 102 that.cancel_stick();
103 103 that.showInPager(cell);
104 104 }];
105 105 // call after all the tabs function above have bee call to clean their effects
106 106 // if necessary
107 107 this.reset_tabs_function = function (cell, text) {
108 108 this._old_cell = (cell) ? cell : null;
109 109 this._old_request = (text) ? text : null;
110 110 this._consecutive_counter = 0;
111 111 };
112 112 };
113 113
114 114 Tooltip.prototype.is_visible = function () {
115 115 return !this._hidden;
116 116 };
117 117
118 118 Tooltip.prototype.showInPager = function (cell) {
119 119 // reexecute last call in pager by appending ? to show back in pager
120 120 var that = this;
121 121 var payload = {};
122 122 payload.text = that._reply.content.data['text/plain'];
123 123
124 124 this.events.trigger('open_with_text.Pager', payload);
125 125 this.remove_and_cancel_tooltip();
126 126 };
127 127
128 128 // grow the tooltip verticaly
129 129 Tooltip.prototype.expand = function () {
130 130 this.text.removeClass('smalltooltip');
131 131 this.text.addClass('bigtooltip');
132 132 $('#expanbutton').hide('slow');
133 133 };
134 134
135 135 // deal with all the logic of hiding the tooltip
136 136 // and reset it's status
137 137 Tooltip.prototype._hide = function () {
138 138 this._hidden = true;
139 139 this.tooltip.fadeOut('fast');
140 140 $('#expanbutton').show('slow');
141 141 this.text.removeClass('bigtooltip');
142 142 this.text.addClass('smalltooltip');
143 143 // keep scroll top to be sure to always see the first line
144 144 this.text.scrollTop(0);
145 145 this.code_mirror = null;
146 146 };
147 147
148 148 // return true on successfully removing a visible tooltip; otherwise return
149 149 // false.
150 150 Tooltip.prototype.remove_and_cancel_tooltip = function (force) {
151 151 // note that we don't handle closing directly inside the calltip
152 152 // as in the completer, because it is not focusable, so won't
153 153 // get the event.
154 154 this.cancel_pending();
155 155 if (!this._hidden) {
156 156 if (force || !this._sticky) {
157 157 this.cancel_stick();
158 158 this._hide();
159 159 }
160 160 this.reset_tabs_function();
161 161 return true;
162 162 } else {
163 163 return false;
164 164 }
165 165 };
166 166
167 167 // cancel autocall done after '(' for example.
168 168 Tooltip.prototype.cancel_pending = function () {
169 169 if (this._tooltip_timeout !== null) {
170 170 clearTimeout(this._tooltip_timeout);
171 171 this._tooltip_timeout = null;
172 172 }
173 173 };
174 174
175 175 // will trigger tooltip after timeout
176 176 Tooltip.prototype.pending = function (cell, hide_if_no_docstring) {
177 177 var that = this;
178 178 this._tooltip_timeout = setTimeout(function () {
179 179 that.request(cell, hide_if_no_docstring);
180 180 }, that.time_before_tooltip);
181 181 };
182 182
183 183 // easy access for julia monkey patching.
184 184 Tooltip.last_token_re = /[a-z_][0-9a-z._]*$/gi;
185 185
186 186 Tooltip.prototype.extract_oir_token = function(line){
187 187 // use internally just to make the request to the kernel
188 188 // Feel free to shorten this logic if you are better
189 189 // than me in regEx
190 190 // basicaly you shoul be able to get xxx.xxx.xxx from
191 191 // something(range(10), kwarg=smth) ; xxx.xxx.xxx( firstarg, rand(234,23), kwarg1=2,
192 192 // remove everything between matchin bracket (need to iterate)
193 193 var matchBracket = /\([^\(\)]+\)/g;
194 194 var endBracket = /\([^\(]*$/g;
195 195 var oldline = line;
196 196
197 197 line = line.replace(matchBracket, "");
198 198 while (oldline != line) {
199 199 oldline = line;
200 200 line = line.replace(matchBracket, "");
201 201 }
202 202 // remove everything after last open bracket
203 203 line = line.replace(endBracket, "");
204 204 // reset the regex object
205 205 Tooltip.last_token_re.lastIndex = 0;
206 206 return Tooltip.last_token_re.exec(line);
207 207 };
208 208
209 209 Tooltip.prototype._request_tooltip = function (cell, text, cursor_pos) {
210 210 var callbacks = $.proxy(this._show, this);
211 211 var msg_id = cell.kernel.inspect(text, cursor_pos, callbacks);
212 212 };
213 213
214 214 // make an imediate completion request
215 215 Tooltip.prototype.request = function (cell, hide_if_no_docstring) {
216 216 // request(codecell)
217 217 // Deal with extracting the text from the cell and counting
218 218 // call in a row
219 219 this.cancel_pending();
220 220 var editor = cell.code_mirror;
221 221 var cursor = editor.getCursor();
222 222 var cursor_pos = utils.to_absolute_cursor_pos(editor, cursor);
223 223 var text = cell.get_text();
224 224
225 225 this._hide_if_no_docstring = hide_if_no_docstring;
226 226
227 227 if(editor.somethingSelected()){
228 228 text = editor.getSelection();
229 229 }
230 230
231 231 // need a permanent handel to code_mirror for future auto recall
232 232 this.code_mirror = editor;
233 233
234 234 // now we treat the different number of keypress
235 235 // first if same cell, same text, increment counter by 1
236 236 if (this._old_cell == cell && this._old_request == text && this._hidden === false) {
237 237 this._consecutive_counter++;
238 238 } else {
239 239 // else reset
240 240 this.cancel_stick();
241 241 this.reset_tabs_function (cell, text);
242 242 }
243 243
244 244 this.tabs_functions[this._consecutive_counter](cell, text, cursor_pos);
245 245
246 246 // then if we are at the end of list function, reset
247 247 if (this._consecutive_counter == this.tabs_functions.length) {
248 248 this.reset_tabs_function (cell, text, cursor);
249 249 }
250 250
251 251 return;
252 252 };
253 253
254 254 // cancel the option of having the tooltip to stick
255 255 Tooltip.prototype.cancel_stick = function () {
256 256 clearTimeout(this._stick_timeout);
257 257 this._stick_timeout = null;
258 258 this._clocklink.hide('slow');
259 259 this._sticky = false;
260 260 };
261 261
262 262 // put the tooltip in a sicky state for 10 seconds
263 263 // it won't be removed by remove_and_cancell() unless you called with
264 264 // the first parameter set to true.
265 265 // remove_and_cancell_tooltip(true)
266 266 Tooltip.prototype.stick = function (time) {
267 267 time = (time !== undefined) ? time : 10;
268 268 var that = this;
269 269 this._sticky = true;
270 270 this._clocklink.show('slow');
271 271 this._stick_timeout = setTimeout(function () {
272 272 that._sticky = false;
273 273 that._clocklink.hide('slow');
274 274 }, time * 1000);
275 275 };
276 276
277 277 // should be called with the kernel reply to actually show the tooltip
278 278 Tooltip.prototype._show = function (reply) {
279 279 // move the bubble if it is not hidden
280 280 // otherwise fade it
281 281 this._reply = reply;
282 282 var content = reply.content;
283 283 if (!content.found) {
284 284 // object not found, nothing to show
285 285 return;
286 286 }
287 287 this.name = content.name;
288 288
289 289 // do some math to have the tooltip arrow on more or less on left or right
290 290 // width of the editor
291 291 var w = $(this.code_mirror.getScrollerElement()).width();
292 292 // ofset of the editor
293 293 var o = $(this.code_mirror.getScrollerElement()).offset();
294 294
295 295 // whatever anchor/head order but arrow at mid x selection
296 296 var anchor = this.code_mirror.cursorCoords(false);
297 297 var head = this.code_mirror.cursorCoords(true);
298 298 var xinit = (head.left+anchor.left)/2;
299 299 var xinter = o.left + (xinit - o.left) / w * (w - 450);
300 300 var posarrowleft = xinit - xinter;
301 301
302 302 if (this._hidden === false) {
303 303 this.tooltip.animate({
304 304 'left': xinter - 30 + 'px',
305 305 'top': (head.bottom + 10) + 'px'
306 306 });
307 307 } else {
308 308 this.tooltip.css({
309 309 'left': xinter - 30 + 'px'
310 310 });
311 311 this.tooltip.css({
312 312 'top': (head.bottom + 10) + 'px'
313 313 });
314 314 }
315 315 this.arrow.animate({
316 316 'left': posarrowleft + 'px'
317 317 });
318 318
319 319 this._hidden = false;
320 320 this.tooltip.fadeIn('fast');
321 321 this.text.children().remove();
322 322
323 323 // This should support rich data types, but only text/plain for now
324 324 // Any HTML within the docstring is escaped by the fixConsole() method.
325 325 var pre = $('<pre/>').html(utils.fixConsole(content.data['text/plain']));
326 326 this.text.append(pre);
327 327 // keep scroll top to be sure to always see the first line
328 328 this.text.scrollTop(0);
329 329 };
330 330
331 331 // Backwards compatability.
332 332 IPython.Tooltip = Tooltip;
333 333
334 return Tooltip;
334 return {'Tooltip': Tooltip};
335 335 });
@@ -1,170 +1,170 b''
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 4 define([
5 5 'base/js/namespace',
6 6 'jquery',
7 7 ], function(IPython, $) {
8 8 "use strict";
9 9
10 10 var tour_style = "<div class='popover tour'>\n" +
11 11 "<div class='arrow'></div>\n" +
12 12 "<div style='position:absolute; top:7px; right:7px'>\n" +
13 13 "<button class='btn btn-default btn-sm icon-remove' data-role='end'></button>\n" +
14 14 "</div><h3 class='popover-title'></h3>\n" +
15 15 "<div class='popover-content'></div>\n" +
16 16 "<div class='popover-navigation'>\n" +
17 17 "<button class='btn btn-default icon-step-backward' data-role='prev'></button>\n" +
18 18 "<button class='btn btn-default icon-step-forward pull-right' data-role='next'></button>\n" +
19 19 "<button id='tour-pause' class='btn btn-sm btn-default icon-pause' data-resume-text='' data-pause-text='' data-role='pause-resume'></button>\n" +
20 20 "</div>\n" +
21 21 "</div>";
22 22
23 23 var NotebookTour = function (notebook, events) {
24 24 var that = this;
25 25 this.step_duration = 0;
26 26 this.tour_steps = [
27 27 {
28 28 title: "Welcome to the Notebook Tour",
29 29 placement: 'bottom',
30 30 orphan: true,
31 31 content: "You can use the left and right arrow keys to go backwards and forwards.",
32 32 }, {
33 33 element: "#notebook_name",
34 34 title: "Filename",
35 35 placement: 'bottom',
36 36 content: "Click here to change the filename for this notebook."
37 37 }, {
38 38 element: $("#menus").parent(),
39 39 placement: 'bottom',
40 40 backdrop: true,
41 41 title: "Notebook Menubar",
42 42 content: "The menubar has menus for actions on the notebook, its cells, and the kernel it communicates with."
43 43 }, {
44 44 element: "#maintoolbar",
45 45 placement: 'bottom',
46 46 backdrop: true,
47 47 title: "Notebook Toolbar",
48 48 content: "The toolbar has buttons for the most common actions. Hover your mouse over each button for more information."
49 49 }, {
50 50 element: "#modal_indicator",
51 51 title: "Mode Indicator",
52 52 placement: 'bottom',
53 53 content: "The Notebook has two modes: Edit Mode and Command Mode. In this area, an indicator can appear to tell you which mode you are in.",
54 54 onShow: function(tour) { command_icon_hack(); }
55 55 }, {
56 56 element: "#modal_indicator",
57 57 title: "Command Mode",
58 58 placement: 'bottom',
59 59 onShow: function(tour) { that.notebook.command_mode(); command_icon_hack(); },
60 60 onNext: function(tour) { edit_mode(); },
61 61 content: "Right now you are in Command Mode, and many keyboard shortcuts are available. In this mode, no icon is displayed in the indicator area."
62 62 }, {
63 63 element: "#modal_indicator",
64 64 title: "Edit Mode",
65 65 placement: 'bottom',
66 66 onShow: function(tour) { edit_mode(); },
67 67 content: "Pressing <code>Enter</code> or clicking in the input text area of the cell switches to Edit Mode."
68 68 }, {
69 69 element: '.selected',
70 70 title: "Edit Mode",
71 71 placement: 'bottom',
72 72 onShow: function(tour) { edit_mode(); },
73 73 content: "Notice that the border around the currently active cell changed color. Typing will insert text into the currently active cell."
74 74 }, {
75 75 element: '.selected',
76 76 title: "Back to Command Mode",
77 77 placement: 'bottom',
78 78 onShow: function(tour) { that.notebook.command_mode(); },
79 79 onHide: function(tour) { $('#help_menu').parent().children('a').click(); },
80 80 content: "Pressing <code>Esc</code> or clicking outside of the input text area takes you back to Command Mode."
81 81 }, {
82 82 element: '#keyboard_shortcuts',
83 83 title: "Keyboard Shortcuts",
84 84 placement: 'bottom',
85 85 onHide: function(tour) { $('#help_menu').parent().children('a').click(); },
86 86 content: "You can click here to get a list of all of the keyboard shortcuts."
87 87 }, {
88 88 element: "#kernel_indicator",
89 89 title: "Kernel Indicator",
90 90 placement: 'bottom',
91 91 onShow: function(tour) { events.trigger('status_idle.Kernel');},
92 92 content: "This is the Kernel indicator. It looks like this when the Kernel is idle.",
93 93 }, {
94 94 element: "#kernel_indicator",
95 95 title: "Kernel Indicator",
96 96 placement: 'bottom',
97 97 onShow: function(tour) { events.trigger('status_busy.Kernel'); },
98 98 content: "The Kernel indicator looks like this when the Kernel is busy.",
99 99 }, {
100 100 element: ".icon-stop",
101 101 placement: 'bottom',
102 102 title: "Interrupting the Kernel",
103 103 onHide: function(tour) { events.trigger('status_idle.Kernel'); },
104 104 content: "To cancel a computation in progress, you can click here."
105 105 }, {
106 106 element: "#notification_kernel",
107 107 placement: 'bottom',
108 108 onShow: function(tour) { $('.icon-stop').click(); },
109 109 title: "Notification Area",
110 110 content: "Messages in response to user actions (Save, Interrupt, etc) appear here."
111 111 }, {
112 112 title: "Fin.",
113 113 placement: 'bottom',
114 114 orphan: true,
115 115 content: "This concludes the IPython Notebook User Interface Tour. Happy hacking!",
116 116 }
117 117 ];
118 118
119 119 this.tour = new Tour({
120 120 //orphan: true,
121 121 storage: false, // start tour from beginning every time
122 122 //element: $("#ipython_notebook"),
123 123 debug: true,
124 124 reflex: true, // click on element to continue tour
125 125 //backdrop: true, // show dark behind popover
126 126 animation: false,
127 127 duration: this.step_duration,
128 128 onStart: function() { console.log('tour started'); },
129 129 // TODO: remove the onPause/onResume logic once pi's patch has been
130 130 // merged upstream to make this work via data-resume-class and
131 131 // data-resume-text attributes.
132 132 onPause: this.toggle_pause_play,
133 133 onResume: this.toggle_pause_play,
134 134 steps: this.tour_steps,
135 135 template: tour_style,
136 136 orphan: true
137 137 });
138 138
139 139 };
140 140
141 141 NotebookTour.prototype.start = function () {
142 142 console.log("let's start the tour");
143 143 this.tour.init();
144 144 this.tour.start();
145 145 if (this.tour.ended())
146 146 {
147 147 this.tour.restart();
148 148 }
149 149 };
150 150
151 151 NotebookTour.prototype.command_icon_hack = function() {
152 152 $('#modal_indicator').css('min-height', 20);
153 153 };
154 154
155 155 NotebookTour.prototype.toggle_pause_play = function () {
156 156 $('#tour-pause').toggleClass('icon-pause icon-play');
157 157 };
158 158
159 159 NotebookTour.prototype.edit_mode = function() {
160 160 this.notebook.focus_cell();
161 161 this.notebook.edit_mode();
162 162 };
163 163
164 164 // For backwards compatability.
165 165 IPython.NotebookTour = NotebookTour;
166 166
167 return NotebookTour;
167 return {'NotebookTour': NotebookTour};
168 168
169 169 });
170 170
@@ -1,612 +1,612 b''
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 4 define([
5 5 'base/js/namespace',
6 6 'jquery',
7 7 'base/js/utils',
8 8 'services/kernels/js/comm',
9 9 'widgets/js/init',
10 10 ], function(IPython, $, utils, comm, WidgetManager) {
11 11 "use strict";
12 12
13 13 // Initialization and connection.
14 14 /**
15 15 * A Kernel Class to communicate with the Python kernel
16 16 * @Class Kernel
17 17 */
18 18 var Kernel = function (kernel_service_url, notebook) {
19 19 this.events = notebook.events;
20 20 this.kernel_id = null;
21 21 this.shell_channel = null;
22 22 this.iopub_channel = null;
23 23 this.stdin_channel = null;
24 24 this.kernel_service_url = kernel_service_url;
25 25 this.running = false;
26 26 this.username = "username";
27 27 this.session_id = utils.uuid();
28 28 this._msg_callbacks = {};
29 29 this.post = $.post;
30 30
31 31 if (typeof(WebSocket) !== 'undefined') {
32 32 this.WebSocket = WebSocket;
33 33 } else if (typeof(MozWebSocket) !== 'undefined') {
34 34 this.WebSocket = MozWebSocket;
35 35 } else {
36 36 alert('Your browser does not have WebSocket support, please try Chrome, Safari or Firefox ≥ 6. Firefox 4 and 5 are also supported by you have to enable WebSockets in about:config.');
37 37 }
38 38
39 39 this.bind_events();
40 40 this.init_iopub_handlers();
41 41 this.comm_manager = new comm.CommManager(this);
42 42 this.widget_manager = new WidgetManager(this.comm_manager, notebook);
43 43
44 44 this.last_msg_id = null;
45 45 this.last_msg_callbacks = {};
46 46 };
47 47
48 48
49 49 Kernel.prototype._get_msg = function (msg_type, content, metadata) {
50 50 var msg = {
51 51 header : {
52 52 msg_id : utils.uuid(),
53 53 username : this.username,
54 54 session : this.session_id,
55 55 msg_type : msg_type,
56 56 version : "5.0"
57 57 },
58 58 metadata : metadata || {},
59 59 content : content,
60 60 parent_header : {}
61 61 };
62 62 return msg;
63 63 };
64 64
65 65 Kernel.prototype.bind_events = function () {
66 66 var that = this;
67 67 this.events.on('send_input_reply.Kernel', function(evt, data) {
68 68 that.send_input_reply(data);
69 69 });
70 70 };
71 71
72 72 // Initialize the iopub handlers
73 73
74 74 Kernel.prototype.init_iopub_handlers = function () {
75 75 var output_msg_types = ['stream', 'display_data', 'execute_result', 'error'];
76 76 this._iopub_handlers = {};
77 77 this.register_iopub_handler('status', $.proxy(this._handle_status_message, this));
78 78 this.register_iopub_handler('clear_output', $.proxy(this._handle_clear_output, this));
79 79
80 80 for (var i=0; i < output_msg_types.length; i++) {
81 81 this.register_iopub_handler(output_msg_types[i], $.proxy(this._handle_output_message, this));
82 82 }
83 83 };
84 84
85 85 /**
86 86 * Start the Python kernel
87 87 * @method start
88 88 */
89 89 Kernel.prototype.start = function (params) {
90 90 params = params || {};
91 91 if (!this.running) {
92 92 var qs = $.param(params);
93 93 this.post(utils.url_join_encode(this.kernel_service_url) + '?' + qs,
94 94 $.proxy(this._kernel_started, this),
95 95 'json'
96 96 );
97 97 }
98 98 };
99 99
100 100 /**
101 101 * Restart the python kernel.
102 102 *
103 103 * Emit a 'status_restarting.Kernel' event with
104 104 * the current object as parameter
105 105 *
106 106 * @method restart
107 107 */
108 108 Kernel.prototype.restart = function () {
109 109 this.events.trigger('status_restarting.Kernel', {kernel: this});
110 110 if (this.running) {
111 111 this.stop_channels();
112 112 this.post(utils.url_join_encode(this.kernel_url, "restart"),
113 113 $.proxy(this._kernel_started, this),
114 114 'json'
115 115 );
116 116 }
117 117 };
118 118
119 119
120 120 Kernel.prototype._kernel_started = function (json) {
121 121 console.log("Kernel started: ", json.id);
122 122 this.running = true;
123 123 this.kernel_id = json.id;
124 124 // trailing 's' in https will become wss for secure web sockets
125 125 this.ws_host = location.protocol.replace('http', 'ws') + "//" + location.host;
126 126 this.kernel_url = utils.url_path_join(this.kernel_service_url, this.kernel_id);
127 127 this.start_channels();
128 128 };
129 129
130 130
131 131 Kernel.prototype._websocket_closed = function(ws_url, early) {
132 132 this.stop_channels();
133 133 this.events.trigger('websocket_closed.Kernel',
134 134 {ws_url: ws_url, kernel: this, early: early}
135 135 );
136 136 };
137 137
138 138 /**
139 139 * Start the `shell`and `iopub` channels.
140 140 * Will stop and restart them if they already exist.
141 141 *
142 142 * @method start_channels
143 143 */
144 144 Kernel.prototype.start_channels = function () {
145 145 var that = this;
146 146 this.stop_channels();
147 147 var ws_host_url = this.ws_host + this.kernel_url;
148 148 console.log("Starting WebSockets:", ws_host_url);
149 149 this.shell_channel = new this.WebSocket(
150 150 this.ws_host + utils.url_join_encode(this.kernel_url, "shell")
151 151 );
152 152 this.stdin_channel = new this.WebSocket(
153 153 this.ws_host + utils.url_join_encode(this.kernel_url, "stdin")
154 154 );
155 155 this.iopub_channel = new this.WebSocket(
156 156 this.ws_host + utils.url_join_encode(this.kernel_url, "iopub")
157 157 );
158 158
159 159 var already_called_onclose = false; // only alert once
160 160 var ws_closed_early = function(evt){
161 161 if (already_called_onclose){
162 162 return;
163 163 }
164 164 already_called_onclose = true;
165 165 if ( ! evt.wasClean ){
166 166 that._websocket_closed(ws_host_url, true);
167 167 }
168 168 };
169 169 var ws_closed_late = function(evt){
170 170 if (already_called_onclose){
171 171 return;
172 172 }
173 173 already_called_onclose = true;
174 174 if ( ! evt.wasClean ){
175 175 that._websocket_closed(ws_host_url, false);
176 176 }
177 177 };
178 178 var channels = [this.shell_channel, this.iopub_channel, this.stdin_channel];
179 179 for (var i=0; i < channels.length; i++) {
180 180 channels[i].onopen = $.proxy(this._ws_opened, this);
181 181 channels[i].onclose = ws_closed_early;
182 182 }
183 183 // switch from early-close to late-close message after 1s
184 184 setTimeout(function() {
185 185 for (var i=0; i < channels.length; i++) {
186 186 if (channels[i] !== null) {
187 187 channels[i].onclose = ws_closed_late;
188 188 }
189 189 }
190 190 }, 1000);
191 191 this.shell_channel.onmessage = $.proxy(this._handle_shell_reply, this);
192 192 this.iopub_channel.onmessage = $.proxy(this._handle_iopub_message, this);
193 193 this.stdin_channel.onmessage = $.proxy(this._handle_input_request, this);
194 194 };
195 195
196 196 /**
197 197 * Handle a websocket entering the open state
198 198 * sends session and cookie authentication info as first message.
199 199 * Once all sockets are open, signal the Kernel.status_started event.
200 200 * @method _ws_opened
201 201 */
202 202 Kernel.prototype._ws_opened = function (evt) {
203 203 // send the session id so the Session object Python-side
204 204 // has the same identity
205 205 evt.target.send(this.session_id + ':' + document.cookie);
206 206
207 207 var channels = [this.shell_channel, this.iopub_channel, this.stdin_channel];
208 208 for (var i=0; i < channels.length; i++) {
209 209 // if any channel is not ready, don't trigger event.
210 210 if ( !channels[i].readyState ) return;
211 211 }
212 212 // all events ready, trigger started event.
213 213 this.events.trigger('status_started.Kernel', {kernel: this});
214 214 };
215 215
216 216 /**
217 217 * Stop the websocket channels.
218 218 * @method stop_channels
219 219 */
220 220 Kernel.prototype.stop_channels = function () {
221 221 var channels = [this.shell_channel, this.iopub_channel, this.stdin_channel];
222 222 for (var i=0; i < channels.length; i++) {
223 223 if ( channels[i] !== null ) {
224 224 channels[i].onclose = null;
225 225 channels[i].close();
226 226 }
227 227 }
228 228 this.shell_channel = this.iopub_channel = this.stdin_channel = null;
229 229 };
230 230
231 231 // Main public methods.
232 232
233 233 // send a message on the Kernel's shell channel
234 234 Kernel.prototype.send_shell_message = function (msg_type, content, callbacks, metadata) {
235 235 var msg = this._get_msg(msg_type, content, metadata);
236 236 this.shell_channel.send(JSON.stringify(msg));
237 237 this.set_callbacks_for_msg(msg.header.msg_id, callbacks);
238 238 return msg.header.msg_id;
239 239 };
240 240
241 241 /**
242 242 * Get kernel info
243 243 *
244 244 * @param callback {function}
245 245 * @method kernel_info
246 246 *
247 247 * When calling this method, pass a callback function that expects one argument.
248 248 * The callback will be passed the complete `kernel_info_reply` message documented
249 249 * [here](http://ipython.org/ipython-doc/dev/development/messaging.html#kernel-info)
250 250 */
251 251 Kernel.prototype.kernel_info = function (callback) {
252 252 var callbacks;
253 253 if (callback) {
254 254 callbacks = { shell : { reply : callback } };
255 255 }
256 256 return this.send_shell_message("kernel_info_request", {}, callbacks);
257 257 };
258 258
259 259 /**
260 260 * Get info on an object
261 261 *
262 262 * @param code {string}
263 263 * @param cursor_pos {integer}
264 264 * @param callback {function}
265 265 * @method inspect
266 266 *
267 267 * When calling this method, pass a callback function that expects one argument.
268 268 * The callback will be passed the complete `inspect_reply` message documented
269 269 * [here](http://ipython.org/ipython-doc/dev/development/messaging.html#object-information)
270 270 */
271 271 Kernel.prototype.inspect = function (code, cursor_pos, callback) {
272 272 var callbacks;
273 273 if (callback) {
274 274 callbacks = { shell : { reply : callback } };
275 275 }
276 276
277 277 var content = {
278 278 code : code,
279 279 cursor_pos : cursor_pos,
280 280 detail_level : 0,
281 281 };
282 282 return this.send_shell_message("inspect_request", content, callbacks);
283 283 };
284 284
285 285 /**
286 286 * Execute given code into kernel, and pass result to callback.
287 287 *
288 288 * @async
289 289 * @method execute
290 290 * @param {string} code
291 291 * @param [callbacks] {Object} With the following keys (all optional)
292 292 * @param callbacks.shell.reply {function}
293 293 * @param callbacks.shell.payload.[payload_name] {function}
294 294 * @param callbacks.iopub.output {function}
295 295 * @param callbacks.iopub.clear_output {function}
296 296 * @param callbacks.input {function}
297 297 * @param {object} [options]
298 298 * @param [options.silent=false] {Boolean}
299 299 * @param [options.user_expressions=empty_dict] {Dict}
300 300 * @param [options.allow_stdin=false] {Boolean} true|false
301 301 *
302 302 * @example
303 303 *
304 304 * The options object should contain the options for the execute call. Its default
305 305 * values are:
306 306 *
307 307 * options = {
308 308 * silent : true,
309 309 * user_expressions : {},
310 310 * allow_stdin : false
311 311 * }
312 312 *
313 313 * When calling this method pass a callbacks structure of the form:
314 314 *
315 315 * callbacks = {
316 316 * shell : {
317 317 * reply : execute_reply_callback,
318 318 * payload : {
319 319 * set_next_input : set_next_input_callback,
320 320 * }
321 321 * },
322 322 * iopub : {
323 323 * output : output_callback,
324 324 * clear_output : clear_output_callback,
325 325 * },
326 326 * input : raw_input_callback
327 327 * }
328 328 *
329 329 * Each callback will be passed the entire message as a single arugment.
330 330 * Payload handlers will be passed the corresponding payload and the execute_reply message.
331 331 */
332 332 Kernel.prototype.execute = function (code, callbacks, options) {
333 333
334 334 var content = {
335 335 code : code,
336 336 silent : true,
337 337 store_history : false,
338 338 user_expressions : {},
339 339 allow_stdin : false
340 340 };
341 341 callbacks = callbacks || {};
342 342 if (callbacks.input !== undefined) {
343 343 content.allow_stdin = true;
344 344 }
345 345 $.extend(true, content, options);
346 346 this.events.trigger('execution_request.Kernel', {kernel: this, content:content});
347 347 return this.send_shell_message("execute_request", content, callbacks);
348 348 };
349 349
350 350 /**
351 351 * When calling this method, pass a function to be called with the `complete_reply` message
352 352 * as its only argument when it arrives.
353 353 *
354 354 * `complete_reply` is documented
355 355 * [here](http://ipython.org/ipython-doc/dev/development/messaging.html#complete)
356 356 *
357 357 * @method complete
358 358 * @param code {string}
359 359 * @param cursor_pos {integer}
360 360 * @param callback {function}
361 361 *
362 362 */
363 363 Kernel.prototype.complete = function (code, cursor_pos, callback) {
364 364 var callbacks;
365 365 if (callback) {
366 366 callbacks = { shell : { reply : callback } };
367 367 }
368 368 var content = {
369 369 code : code,
370 370 cursor_pos : cursor_pos,
371 371 };
372 372 return this.send_shell_message("complete_request", content, callbacks);
373 373 };
374 374
375 375
376 376 Kernel.prototype.interrupt = function () {
377 377 if (this.running) {
378 378 this.events.trigger('status_interrupting.Kernel', {kernel: this});
379 379 this.post(utils.url_join_encode(this.kernel_url, "interrupt"));
380 380 }
381 381 };
382 382
383 383
384 384 Kernel.prototype.kill = function () {
385 385 if (this.running) {
386 386 this.running = false;
387 387 var settings = {
388 388 cache : false,
389 389 type : "DELETE",
390 390 error : utils.log_ajax_error,
391 391 };
392 392 $.ajax(utils.url_join_encode(this.kernel_url), settings);
393 393 }
394 394 };
395 395
396 396 Kernel.prototype.send_input_reply = function (input) {
397 397 var content = {
398 398 value : input,
399 399 };
400 400 this.events.trigger('input_reply.Kernel', {kernel: this, content:content});
401 401 var msg = this._get_msg("input_reply", content);
402 402 this.stdin_channel.send(JSON.stringify(msg));
403 403 return msg.header.msg_id;
404 404 };
405 405
406 406
407 407 // Reply handlers
408 408
409 409 Kernel.prototype.register_iopub_handler = function (msg_type, callback) {
410 410 this._iopub_handlers[msg_type] = callback;
411 411 };
412 412
413 413 Kernel.prototype.get_iopub_handler = function (msg_type) {
414 414 // get iopub handler for a specific message type
415 415 return this._iopub_handlers[msg_type];
416 416 };
417 417
418 418
419 419 Kernel.prototype.get_callbacks_for_msg = function (msg_id) {
420 420 // get callbacks for a specific message
421 421 if (msg_id == this.last_msg_id) {
422 422 return this.last_msg_callbacks;
423 423 } else {
424 424 return this._msg_callbacks[msg_id];
425 425 }
426 426 };
427 427
428 428
429 429 Kernel.prototype.clear_callbacks_for_msg = function (msg_id) {
430 430 if (this._msg_callbacks[msg_id] !== undefined ) {
431 431 delete this._msg_callbacks[msg_id];
432 432 }
433 433 };
434 434
435 435 Kernel.prototype._finish_shell = function (msg_id) {
436 436 var callbacks = this._msg_callbacks[msg_id];
437 437 if (callbacks !== undefined) {
438 438 callbacks.shell_done = true;
439 439 if (callbacks.iopub_done) {
440 440 this.clear_callbacks_for_msg(msg_id);
441 441 }
442 442 }
443 443 };
444 444
445 445 Kernel.prototype._finish_iopub = function (msg_id) {
446 446 var callbacks = this._msg_callbacks[msg_id];
447 447 if (callbacks !== undefined) {
448 448 callbacks.iopub_done = true;
449 449 if (callbacks.shell_done) {
450 450 this.clear_callbacks_for_msg(msg_id);
451 451 }
452 452 }
453 453 };
454 454
455 455 /* Set callbacks for a particular message.
456 456 * Callbacks should be a struct of the following form:
457 457 * shell : {
458 458 *
459 459 * }
460 460
461 461 */
462 462 Kernel.prototype.set_callbacks_for_msg = function (msg_id, callbacks) {
463 463 this.last_msg_id = msg_id;
464 464 if (callbacks) {
465 465 // shallow-copy mapping, because we will modify it at the top level
466 466 var cbcopy = this._msg_callbacks[msg_id] = this.last_msg_callbacks = {};
467 467 cbcopy.shell = callbacks.shell;
468 468 cbcopy.iopub = callbacks.iopub;
469 469 cbcopy.input = callbacks.input;
470 470 cbcopy.shell_done = (!callbacks.shell);
471 471 cbcopy.iopub_done = (!callbacks.iopub);
472 472 } else {
473 473 this.last_msg_callbacks = {};
474 474 }
475 475 };
476 476
477 477
478 478 Kernel.prototype._handle_shell_reply = function (e) {
479 479 var reply = $.parseJSON(e.data);
480 480 this.events.trigger('shell_reply.Kernel', {kernel: this, reply:reply});
481 481 var content = reply.content;
482 482 var metadata = reply.metadata;
483 483 var parent_id = reply.parent_header.msg_id;
484 484 var callbacks = this.get_callbacks_for_msg(parent_id);
485 485 if (!callbacks || !callbacks.shell) {
486 486 return;
487 487 }
488 488 var shell_callbacks = callbacks.shell;
489 489
490 490 // signal that shell callbacks are done
491 491 this._finish_shell(parent_id);
492 492
493 493 if (shell_callbacks.reply !== undefined) {
494 494 shell_callbacks.reply(reply);
495 495 }
496 496 if (content.payload && shell_callbacks.payload) {
497 497 this._handle_payloads(content.payload, shell_callbacks.payload, reply);
498 498 }
499 499 };
500 500
501 501
502 502 Kernel.prototype._handle_payloads = function (payloads, payload_callbacks, msg) {
503 503 var l = payloads.length;
504 504 // Payloads are handled by triggering events because we don't want the Kernel
505 505 // to depend on the Notebook or Pager classes.
506 506 for (var i=0; i<l; i++) {
507 507 var payload = payloads[i];
508 508 var callback = payload_callbacks[payload.source];
509 509 if (callback) {
510 510 callback(payload, msg);
511 511 }
512 512 }
513 513 };
514 514
515 515 Kernel.prototype._handle_status_message = function (msg) {
516 516 var execution_state = msg.content.execution_state;
517 517 var parent_id = msg.parent_header.msg_id;
518 518
519 519 // dispatch status msg callbacks, if any
520 520 var callbacks = this.get_callbacks_for_msg(parent_id);
521 521 if (callbacks && callbacks.iopub && callbacks.iopub.status) {
522 522 try {
523 523 callbacks.iopub.status(msg);
524 524 } catch (e) {
525 525 console.log("Exception in status msg handler", e, e.stack);
526 526 }
527 527 }
528 528
529 529 if (execution_state === 'busy') {
530 530 this.events.trigger('status_busy.Kernel', {kernel: this});
531 531 } else if (execution_state === 'idle') {
532 532 // signal that iopub callbacks are (probably) done
533 533 // async output may still arrive,
534 534 // but only for the most recent request
535 535 this._finish_iopub(parent_id);
536 536
537 537 // trigger status_idle event
538 538 this.events.trigger('status_idle.Kernel', {kernel: this});
539 539 } else if (execution_state === 'restarting') {
540 540 // autorestarting is distinct from restarting,
541 541 // in that it means the kernel died and the server is restarting it.
542 542 // status_restarting sets the notification widget,
543 543 // autorestart shows the more prominent dialog.
544 544 this.events.trigger('status_autorestarting.Kernel', {kernel: this});
545 545 this.events.trigger('status_restarting.Kernel', {kernel: this});
546 546 } else if (execution_state === 'dead') {
547 547 this.stop_channels();
548 548 this.events.trigger('status_dead.Kernel', {kernel: this});
549 549 }
550 550 };
551 551
552 552
553 553 // handle clear_output message
554 554 Kernel.prototype._handle_clear_output = function (msg) {
555 555 var callbacks = this.get_callbacks_for_msg(msg.parent_header.msg_id);
556 556 if (!callbacks || !callbacks.iopub) {
557 557 return;
558 558 }
559 559 var callback = callbacks.iopub.clear_output;
560 560 if (callback) {
561 561 callback(msg);
562 562 }
563 563 };
564 564
565 565
566 566 // handle an output message (execute_result, display_data, etc.)
567 567 Kernel.prototype._handle_output_message = function (msg) {
568 568 var callbacks = this.get_callbacks_for_msg(msg.parent_header.msg_id);
569 569 if (!callbacks || !callbacks.iopub) {
570 570 return;
571 571 }
572 572 var callback = callbacks.iopub.output;
573 573 if (callback) {
574 574 callback(msg);
575 575 }
576 576 };
577 577
578 578 // dispatch IOPub messages to respective handlers.
579 579 // each message type should have a handler.
580 580 Kernel.prototype._handle_iopub_message = function (e) {
581 581 var msg = $.parseJSON(e.data);
582 582
583 583 var handler = this.get_iopub_handler(msg.header.msg_type);
584 584 if (handler !== undefined) {
585 585 handler(msg);
586 586 }
587 587 };
588 588
589 589
590 590 Kernel.prototype._handle_input_request = function (e) {
591 591 var request = $.parseJSON(e.data);
592 592 var header = request.header;
593 593 var content = request.content;
594 594 var metadata = request.metadata;
595 595 var msg_type = header.msg_type;
596 596 if (msg_type !== 'input_request') {
597 597 console.log("Invalid input request!", request);
598 598 return;
599 599 }
600 600 var callbacks = this.get_callbacks_for_msg(request.parent_header.msg_id);
601 601 if (callbacks) {
602 602 if (callbacks.input) {
603 603 callbacks.input(request);
604 604 }
605 605 }
606 606 };
607 607
608 608 // Backwards compatability.
609 609 IPython.Kernel = Kernel;
610 610
611 return Kernel;
611 return {'Kernel': Kernel};
612 612 });
@@ -1,116 +1,116 b''
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 4 define([
5 5 'base/js/namespace',
6 6 'jquery',
7 7 'base/js/utils',
8 8 'services/kernels/js/kernel',
9 9 ], function(IPython, $, utils, Kernel) {
10 10 "use strict";
11 11
12 12 var Session = function(notebook, options){
13 13 this.kernel = null;
14 14 this.id = null;
15 15 this.notebook = notebook;
16 16 this.name = notebook.notebook_name;
17 17 this.path = notebook.notebook_path;
18 18 this.base_url = notebook.base_url;
19 19 };
20 20
21 21 Session.prototype.start = function(callback) {
22 22 var that = this;
23 23 var model = {
24 24 notebook : {
25 25 name : this.name,
26 26 path : this.path
27 27 }
28 28 };
29 29 var settings = {
30 30 processData : false,
31 31 cache : false,
32 32 type : "POST",
33 33 data: JSON.stringify(model),
34 34 dataType : "json",
35 35 success : function (data, status, xhr) {
36 36 that._handle_start_success(data);
37 37 if (callback) {
38 38 callback(data, status, xhr);
39 39 }
40 40 },
41 41 error : utils.log_ajax_error,
42 42 };
43 43 var url = utils.url_join_encode(this.base_url, 'api/sessions');
44 44 $.ajax(url, settings);
45 45 };
46 46
47 47 Session.prototype.rename_notebook = function (name, path) {
48 48 this.name = name;
49 49 this.path = path;
50 50 var model = {
51 51 notebook : {
52 52 name : this.name,
53 53 path : this.path
54 54 }
55 55 };
56 56 var settings = {
57 57 processData : false,
58 58 cache : false,
59 59 type : "PATCH",
60 60 data: JSON.stringify(model),
61 61 dataType : "json",
62 62 error : utils.log_ajax_error,
63 63 };
64 64 var url = utils.url_join_encode(this.base_url, 'api/sessions', this.id);
65 65 $.ajax(url, settings);
66 66 };
67 67
68 68 Session.prototype.delete = function() {
69 69 var settings = {
70 70 processData : false,
71 71 cache : false,
72 72 type : "DELETE",
73 73 dataType : "json",
74 74 error : utils.log_ajax_error,
75 75 };
76 76 this.kernel.running = false;
77 77 var url = utils.url_join_encode(this.base_url, 'api/sessions', this.id);
78 78 $.ajax(url, settings);
79 79 };
80 80
81 81 // Kernel related things
82 82 /**
83 83 * Create the Kernel object associated with this Session.
84 84 *
85 85 * @method _handle_start_success
86 86 */
87 87 Session.prototype._handle_start_success = function (data, status, xhr) {
88 88 this.id = data.id;
89 89 var kernel_service_url = utils.url_path_join(this.base_url, "api/kernels");
90 90 this.kernel = new Kernel(kernel_service_url, this.notebook);
91 91 this.kernel._kernel_started(data.kernel);
92 92 };
93 93
94 94 /**
95 95 * Prompt the user to restart the IPython kernel.
96 96 *
97 97 * @method restart_kernel
98 98 */
99 99 Session.prototype.restart_kernel = function () {
100 100 this.kernel.restart();
101 101 };
102 102
103 103 Session.prototype.interrupt_kernel = function() {
104 104 this.kernel.interrupt();
105 105 };
106 106
107 107
108 108 Session.prototype.kill_kernel = function() {
109 109 this.kernel.kill();
110 110 };
111 111
112 112 // For backwards compatability.
113 113 IPython.Session = Session;
114 114
115 return Session;
115 return {'Session': Session};
116 116 });
@@ -1,190 +1,193 b''
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 4 define([
5 5 'base/js/namespace',
6 6 'jquery',
7 7 'base/js/utils',
8 8 ], function(IPython, $, utils) {
9 9 "use strict";
10 10
11 11 var ClusterList = function (selector, options) {
12 12 this.selector = selector;
13 13 if (this.selector !== undefined) {
14 14 this.element = $(selector);
15 15 this.style();
16 16 this.bind_events();
17 17 }
18 18 options = options || {};
19 19 this.options = options;
20 20 this.base_url = options.base_url || utils.get_body_data("baseUrl");
21 21 this.notebook_path = options.notebook_path || utils.get_body_data("notebookPath");
22 22 };
23 23
24 24 ClusterList.prototype.style = function () {
25 25 $('#cluster_list').addClass('list_container');
26 26 $('#cluster_toolbar').addClass('list_toolbar');
27 27 $('#cluster_list_info').addClass('toolbar_info');
28 28 $('#cluster_buttons').addClass('toolbar_buttons');
29 29 };
30 30
31 31
32 32 ClusterList.prototype.bind_events = function () {
33 33 var that = this;
34 34 $('#refresh_cluster_list').click(function () {
35 35 that.load_list();
36 36 });
37 37 };
38 38
39 39
40 40 ClusterList.prototype.load_list = function () {
41 41 var settings = {
42 42 processData : false,
43 43 cache : false,
44 44 type : "GET",
45 45 dataType : "json",
46 46 success : $.proxy(this.load_list_success, this),
47 47 error : utils.log_ajax_error,
48 48 };
49 49 var url = utils.url_join_encode(this.base_url, 'clusters');
50 50 $.ajax(url, settings);
51 51 };
52 52
53 53
54 54 ClusterList.prototype.clear_list = function () {
55 55 this.element.children('.list_item').remove();
56 56 };
57 57
58 58 ClusterList.prototype.load_list_success = function (data, status, xhr) {
59 59 this.clear_list();
60 60 var len = data.length;
61 61 for (var i=0; i<len; i++) {
62 62 var element = $('<div/>');
63 63 var item = new ClusterItem(element, this.options);
64 64 item.update_state(data[i]);
65 65 element.data('item', item);
66 66 this.element.append(element);
67 67 }
68 68 };
69 69
70 70
71 71 var ClusterItem = function (element, options) {
72 72 this.element = $(element);
73 73 this.base_url = options.base_url || utils.get_body_data("baseUrl");
74 74 this.notebook_path = options.notebook_path || utils.get_body_data("notebookPath");
75 75 this.data = null;
76 76 this.style();
77 77 };
78 78
79 79 ClusterItem.prototype.style = function () {
80 80 this.element.addClass('list_item').addClass("row");
81 81 };
82 82
83 83 ClusterItem.prototype.update_state = function (data) {
84 84 this.data = data;
85 85 if (data.status === 'running') {
86 86 this.state_running();
87 87 } else if (data.status === 'stopped') {
88 88 this.state_stopped();
89 89 }
90 90 };
91 91
92 92
93 93 ClusterItem.prototype.state_stopped = function () {
94 94 var that = this;
95 95 var profile_col = $('<div/>').addClass('profile_col col-md-4').text(this.data.profile);
96 96 var status_col = $('<div/>').addClass('status_col col-md-3').text('stopped');
97 97 var engines_col = $('<div/>').addClass('engine_col col-md-3');
98 98 var input = $('<input/>').attr('type','number')
99 99 .attr('min',1)
100 100 .attr('size',3)
101 101 .addClass('engine_num_input form-control');
102 102 engines_col.append(input);
103 103 var start_button = $('<button/>').addClass("btn btn-default btn-xs").text("Start");
104 104 var action_col = $('<div/>').addClass('action_col col-md-2').append(
105 105 $("<span/>").addClass("item_buttons btn-group").append(
106 106 start_button
107 107 )
108 108 );
109 109 this.element.empty()
110 110 .append(profile_col)
111 111 .append(status_col)
112 112 .append(engines_col)
113 113 .append(action_col);
114 114 start_button.click(function (e) {
115 115 var n = that.element.find('.engine_num_input').val();
116 116 if (!/^\d+$/.test(n) && n.length>0) {
117 117 status_col.text('invalid engine #');
118 118 } else {
119 119 var settings = {
120 120 cache : false,
121 121 data : {n:n},
122 122 type : "POST",
123 123 dataType : "json",
124 124 success : function (data, status, xhr) {
125 125 that.update_state(data);
126 126 },
127 127 error : function (xhr, status, error) {
128 128 status_col.text("error starting cluster");
129 129 utils.log_ajax_error(xhr, status, error);
130 130 }
131 131 };
132 132 status_col.text('starting');
133 133 var url = utils.url_join_encode(
134 134 that.base_url,
135 135 'clusters',
136 136 that.data.profile,
137 137 'start'
138 138 );
139 139 $.ajax(url, settings);
140 140 }
141 141 });
142 142 };
143 143
144 144
145 145 ClusterItem.prototype.state_running = function () {
146 146 var that = this;
147 147 var profile_col = $('<div/>').addClass('profile_col col-md-4').text(this.data.profile);
148 148 var status_col = $('<div/>').addClass('status_col col-md-3').text('running');
149 149 var engines_col = $('<div/>').addClass('engines_col col-md-3').text(this.data.n);
150 150 var stop_button = $('<button/>').addClass("btn btn-default btn-xs").text("Stop");
151 151 var action_col = $('<div/>').addClass('action_col col-md-2').append(
152 152 $("<span/>").addClass("item_buttons btn-group").append(
153 153 stop_button
154 154 )
155 155 );
156 156 this.element.empty()
157 157 .append(profile_col)
158 158 .append(status_col)
159 159 .append(engines_col)
160 160 .append(action_col);
161 161 stop_button.click(function (e) {
162 162 var settings = {
163 163 cache : false,
164 164 type : "POST",
165 165 dataType : "json",
166 166 success : function (data, status, xhr) {
167 167 that.update_state(data);
168 168 },
169 169 error : function (xhr, status, error) {
170 170 utils.log_ajax_error(xhr, status, error),
171 171 status_col.text("error stopping cluster");
172 172 }
173 173 };
174 174 status_col.text('stopping');
175 175 var url = utils.url_join_encode(
176 176 that.base_url,
177 177 'clusters',
178 178 that.data.profile,
179 179 'stop'
180 180 );
181 181 $.ajax(url, settings);
182 182 });
183 183 };
184 184
185 185 // For backwards compatability.
186 186 IPython.ClusterList = ClusterList;
187 187 IPython.ClusterItem = ClusterItem;
188 188
189 return ClusterList;
189 return {
190 'ClusterList': ClusterList,
191 'ClusterItem': ClusterItem,
192 };
190 193 });
@@ -1,34 +1,34 b''
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 4 define([
5 5 'base/js/namespace',
6 6 'jquery',
7 7 'tree/js/notebooklist',
8 8 ], function(IPython, $, NotebookList) {
9 9 "use strict";
10 10
11 11 var KernelList = function (selector, options, session_list) {
12 12 NotebookList.call(this, selector, options, 'running', session_list);
13 13 };
14 14
15 15 KernelList.prototype = Object.create(NotebookList.prototype);
16 16
17 17 KernelList.prototype.sessions_loaded = function (d) {
18 18 this.sessions = d;
19 19 this.clear_list();
20 20 var item;
21 21 for (var path in d) {
22 22 item = this.new_notebook_item(-1);
23 23 this.add_link('', path, item);
24 24 this.add_shutdown_button(item, this.sessions[path]);
25 25 }
26 26
27 27 $('#running_list_header').toggle($.isEmptyObject(d));
28 28 };
29 29
30 30 // Backwards compatability.
31 31 IPython.KernelList = KernelList;
32 32
33 return KernelList;
33 return {'KernelList': KernelList};
34 34 });
@@ -1,438 +1,438 b''
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 4 define([
5 5 'base/js/namespace',
6 6 'jquery',
7 7 'base/js/utils',
8 8 'base/js/dialog',
9 9 ], function(IPython, $, utils, Dialog) {
10 10 "use strict";
11 11
12 12 var NotebookList = function (selector, options, element_name, session_list) {
13 13 var that = this;
14 14 this.session_list = session_list;
15 15 // allow code re-use by just changing element_name in kernellist.js
16 16 this.element_name = element_name || 'notebook';
17 17 this.selector = selector;
18 18 if (this.selector !== undefined) {
19 19 this.element = $(selector);
20 20 this.style();
21 21 this.bind_events();
22 22 }
23 23 this.notebooks_list = [];
24 24 this.sessions = {};
25 25 this.base_url = options.base_url || utils.get_body_data("baseUrl");
26 26 this.notebook_path = options.notebook_path || utils.get_body_data("notebookPath");
27 27 if (this.session_list && this.session_list.events) {
28 28 this.session_list.events.on('sessions_loaded.Dashboard',
29 29 function(e, d) { that.sessions_loaded(d); });
30 30 }
31 31 };
32 32
33 33 NotebookList.prototype.style = function () {
34 34 var prefix = '#' + this.element_name;
35 35 $(prefix + '_toolbar').addClass('list_toolbar');
36 36 $(prefix + '_list_info').addClass('toolbar_info');
37 37 $(prefix + '_buttons').addClass('toolbar_buttons');
38 38 $(prefix + '_list_header').addClass('list_header');
39 39 this.element.addClass("list_container");
40 40 };
41 41
42 42
43 43 NotebookList.prototype.bind_events = function () {
44 44 var that = this;
45 45 $('#refresh_' + this.element_name + '_list').click(function () {
46 46 that.load_sessions();
47 47 });
48 48 this.element.bind('dragover', function () {
49 49 return false;
50 50 });
51 51 this.element.bind('drop', function(event){
52 52 that.handleFilesUpload(event,'drop');
53 53 return false;
54 54 });
55 55 };
56 56
57 57 NotebookList.prototype.handleFilesUpload = function(event, dropOrForm) {
58 58 var that = this;
59 59 var files;
60 60 if(dropOrForm =='drop'){
61 61 files = event.originalEvent.dataTransfer.files;
62 62 } else
63 63 {
64 64 files = event.originalEvent.target.files;
65 65 }
66 66 for (var i = 0; i < files.length; i++) {
67 67 var f = files[i];
68 68 var reader = new FileReader();
69 69 reader.readAsText(f);
70 70 var name_and_ext = utils.splitext(f.name);
71 71 var file_ext = name_and_ext[1];
72 72 if (file_ext === '.ipynb') {
73 73 var item = that.new_notebook_item(0);
74 74 item.addClass('new-file');
75 75 that.add_name_input(f.name, item);
76 76 // Store the notebook item in the reader so we can use it later
77 77 // to know which item it belongs to.
78 78 $(reader).data('item', item);
79 79 reader.onload = function (event) {
80 80 var nbitem = $(event.target).data('item');
81 81 that.add_notebook_data(event.target.result, nbitem);
82 82 that.add_upload_button(nbitem);
83 83 };
84 84 } else {
85 85 var dialog = 'Uploaded notebooks must be .ipynb files';
86 86 Dialog.modal({
87 87 title : 'Invalid file type',
88 88 body : dialog,
89 89 buttons : {'OK' : {'class' : 'btn-primary'}}
90 90 });
91 91 }
92 92 }
93 93 // Replace the file input form wth a clone of itself. This is required to
94 94 // reset the form. Otherwise, if you upload a file, delete it and try to
95 95 // upload it again, the changed event won't fire.
96 96 var form = $('input.fileinput');
97 97 form.replaceWith(form.clone(true));
98 98 return false;
99 99 };
100 100
101 101 NotebookList.prototype.clear_list = function (remove_uploads) {
102 102 // Clears the navigation tree.
103 103 //
104 104 // Parameters
105 105 // remove_uploads: bool=False
106 106 // Should upload prompts also be removed from the tree.
107 107 if (remove_uploads) {
108 108 this.element.children('.list_item').remove();
109 109 } else {
110 110 this.element.children('.list_item:not(.new-file)').remove();
111 111 }
112 112 };
113 113
114 114 NotebookList.prototype.load_sessions = function(){
115 115 this.session_list.load_sessions();
116 116 };
117 117
118 118
119 119 NotebookList.prototype.sessions_loaded = function(data){
120 120 this.sessions = data;
121 121 this.load_list();
122 122 };
123 123
124 124 NotebookList.prototype.load_list = function () {
125 125 var that = this;
126 126 var settings = {
127 127 processData : false,
128 128 cache : false,
129 129 type : "GET",
130 130 dataType : "json",
131 131 success : $.proxy(this.list_loaded, this),
132 132 error : $.proxy( function(xhr, status, error){
133 133 utils.log_ajax_error(xhr, status, error);
134 134 that.list_loaded([], null, null, {msg:"Error connecting to server."});
135 135 },this)
136 136 };
137 137
138 138 var url = utils.url_join_encode(
139 139 this.base_url,
140 140 'api',
141 141 'notebooks',
142 142 this.notebook_path
143 143 );
144 144 $.ajax(url, settings);
145 145 };
146 146
147 147
148 148 NotebookList.prototype.list_loaded = function (data, status, xhr, param) {
149 149 var message = 'Notebook list empty.';
150 150 if (param !== undefined && param.msg) {
151 151 message = param.msg;
152 152 }
153 153 var item = null;
154 154 var len = data.length;
155 155 this.clear_list();
156 156 if (len === 0) {
157 157 item = this.new_notebook_item(0);
158 158 var span12 = item.children().first();
159 159 span12.empty();
160 160 span12.append($('<div style="margin:auto;text-align:center;color:grey"/>').text(message));
161 161 }
162 162 var path = this.notebook_path;
163 163 var offset = 0;
164 164 if (path !== '') {
165 165 item = this.new_notebook_item(0);
166 166 this.add_dir(path, '..', item);
167 167 offset = 1;
168 168 }
169 169 for (var i=0; i<len; i++) {
170 170 if (data[i].type === 'directory') {
171 171 var name = data[i].name;
172 172 item = this.new_notebook_item(i+offset);
173 173 this.add_dir(path, name, item);
174 174 } else {
175 175 var name = data[i].name;
176 176 item = this.new_notebook_item(i+offset);
177 177 this.add_link(path, name, item);
178 178 name = utils.url_path_join(path, name);
179 179 if(this.sessions[name] === undefined){
180 180 this.add_delete_button(item);
181 181 } else {
182 182 this.add_shutdown_button(item,this.sessions[name]);
183 183 }
184 184 }
185 185 }
186 186 };
187 187
188 188
189 189 NotebookList.prototype.new_notebook_item = function (index) {
190 190 var item = $('<div/>').addClass("list_item").addClass("row");
191 191 // item.addClass('list_item ui-widget ui-widget-content ui-helper-clearfix');
192 192 // item.css('border-top-style','none');
193 193 item.append($("<div/>").addClass("col-md-12").append(
194 194 $('<i/>').addClass('item_icon')
195 195 ).append(
196 196 $("<a/>").addClass("item_link").append(
197 197 $("<span/>").addClass("item_name")
198 198 )
199 199 ).append(
200 200 $('<div/>').addClass("item_buttons btn-group pull-right")
201 201 ));
202 202
203 203 if (index === -1) {
204 204 this.element.append(item);
205 205 } else {
206 206 this.element.children().eq(index).after(item);
207 207 }
208 208 return item;
209 209 };
210 210
211 211
212 212 NotebookList.prototype.add_dir = function (path, name, item) {
213 213 item.data('name', name);
214 214 item.data('path', path);
215 215 item.find(".item_name").text(name);
216 216 item.find(".item_icon").addClass('folder_icon').addClass('icon-fixed-width');
217 217 item.find("a.item_link")
218 218 .attr('href',
219 219 utils.url_join_encode(
220 220 this.base_url,
221 221 "tree",
222 222 path,
223 223 name
224 224 )
225 225 );
226 226 };
227 227
228 228
229 229 NotebookList.prototype.add_link = function (path, nbname, item) {
230 230 item.data('nbname', nbname);
231 231 item.data('path', path);
232 232 item.find(".item_name").text(nbname);
233 233 item.find(".item_icon").addClass('notebook_icon').addClass('icon-fixed-width');
234 234 item.find("a.item_link")
235 235 .attr('href',
236 236 utils.url_join_encode(
237 237 this.base_url,
238 238 "notebooks",
239 239 path,
240 240 nbname
241 241 )
242 242 ).attr('target','_blank');
243 243 };
244 244
245 245
246 246 NotebookList.prototype.add_name_input = function (nbname, item) {
247 247 item.data('nbname', nbname);
248 248 item.find(".item_icon").addClass('notebook_icon').addClass('icon-fixed-width');
249 249 item.find(".item_name").empty().append(
250 250 $('<input/>')
251 251 .addClass("nbname_input")
252 252 .attr('value', utils.splitext(nbname)[0])
253 253 .attr('size', '30')
254 254 .attr('type', 'text')
255 255 );
256 256 };
257 257
258 258
259 259 NotebookList.prototype.add_notebook_data = function (data, item) {
260 260 item.data('nbdata', data);
261 261 };
262 262
263 263
264 264 NotebookList.prototype.add_shutdown_button = function (item, session) {
265 265 var that = this;
266 266 var shutdown_button = $("<button/>").text("Shutdown").addClass("btn btn-xs btn-danger").
267 267 click(function (e) {
268 268 var settings = {
269 269 processData : false,
270 270 cache : false,
271 271 type : "DELETE",
272 272 dataType : "json",
273 273 success : function () {
274 274 that.load_sessions();
275 275 },
276 276 error : utils.log_ajax_error,
277 277 };
278 278 var url = utils.url_join_encode(
279 279 that.base_url,
280 280 'api/sessions',
281 281 session
282 282 );
283 283 $.ajax(url, settings);
284 284 return false;
285 285 });
286 286 // var new_buttons = item.find('a'); // shutdown_button;
287 287 item.find(".item_buttons").text("").append(shutdown_button);
288 288 };
289 289
290 290 NotebookList.prototype.add_delete_button = function (item) {
291 291 var new_buttons = $('<span/>').addClass("btn-group pull-right");
292 292 var notebooklist = this;
293 293 var delete_button = $("<button/>").text("Delete").addClass("btn btn-default btn-xs").
294 294 click(function (e) {
295 295 // $(this) is the button that was clicked.
296 296 var that = $(this);
297 297 // We use the nbname and notebook_id from the parent notebook_item element's
298 298 // data because the outer scopes values change as we iterate through the loop.
299 299 var parent_item = that.parents('div.list_item');
300 300 var nbname = parent_item.data('nbname');
301 301 var message = 'Are you sure you want to permanently delete the notebook: ' + nbname + '?';
302 302 Dialog.modal({
303 303 title : "Delete notebook",
304 304 body : message,
305 305 buttons : {
306 306 Delete : {
307 307 class: "btn-danger",
308 308 click: function() {
309 309 var settings = {
310 310 processData : false,
311 311 cache : false,
312 312 type : "DELETE",
313 313 dataType : "json",
314 314 success : function (data, status, xhr) {
315 315 parent_item.remove();
316 316 },
317 317 error : utils.log_ajax_error,
318 318 };
319 319 var url = utils.url_join_encode(
320 320 notebooklist.base_url,
321 321 'api/notebooks',
322 322 notebooklist.notebook_path,
323 323 nbname
324 324 );
325 325 $.ajax(url, settings);
326 326 }
327 327 },
328 328 Cancel : {}
329 329 }
330 330 });
331 331 return false;
332 332 });
333 333 item.find(".item_buttons").text("").append(delete_button);
334 334 };
335 335
336 336
337 337 NotebookList.prototype.add_upload_button = function (item) {
338 338 var that = this;
339 339 var upload_button = $('<button/>').text("Upload")
340 340 .addClass('btn btn-primary btn-xs upload_button')
341 341 .click(function (e) {
342 342 var nbname = item.find('.item_name > input').val();
343 343 if (nbname.slice(nbname.length-6, nbname.length) != ".ipynb") {
344 344 nbname = nbname + ".ipynb";
345 345 }
346 346 var path = that.notebook_path;
347 347 var nbdata = item.data('nbdata');
348 348 var content_type = 'application/json';
349 349 var model = {
350 350 content : JSON.parse(nbdata),
351 351 };
352 352 var settings = {
353 353 processData : false,
354 354 cache : false,
355 355 type : 'PUT',
356 356 dataType : 'json',
357 357 data : JSON.stringify(model),
358 358 headers : {'Content-Type': content_type},
359 359 success : function (data, status, xhr) {
360 360 that.add_link(path, nbname, item);
361 361 that.add_delete_button(item);
362 362 },
363 363 error : utils.log_ajax_error,
364 364 };
365 365
366 366 var url = utils.url_join_encode(
367 367 that.base_url,
368 368 'api/notebooks',
369 369 that.notebook_path,
370 370 nbname
371 371 );
372 372 $.ajax(url, settings);
373 373 return false;
374 374 });
375 375 var cancel_button = $('<button/>').text("Cancel")
376 376 .addClass("btn btn-default btn-xs")
377 377 .click(function (e) {
378 378 console.log('cancel click');
379 379 item.remove();
380 380 return false;
381 381 });
382 382 item.find(".item_buttons").empty()
383 383 .append(upload_button)
384 384 .append(cancel_button);
385 385 };
386 386
387 387
388 388 NotebookList.prototype.new_notebook = function(){
389 389 var path = this.notebook_path;
390 390 var base_url = this.base_url;
391 391 var settings = {
392 392 processData : false,
393 393 cache : false,
394 394 type : "POST",
395 395 dataType : "json",
396 396 async : false,
397 397 success : function (data, status, xhr) {
398 398 var notebook_name = data.name;
399 399 window.open(
400 400 utils.url_join_encode(
401 401 base_url,
402 402 'notebooks',
403 403 path,
404 404 notebook_name),
405 405 '_blank'
406 406 );
407 407 },
408 408 error : $.proxy(this.new_notebook_failed, this),
409 409 };
410 410 var url = utils.url_join_encode(
411 411 base_url,
412 412 'api/notebooks',
413 413 path
414 414 );
415 415 $.ajax(url, settings);
416 416 };
417 417
418 418
419 419 NotebookList.prototype.new_notebook_failed = function (xhr, status, error) {
420 420 utils.log_ajax_error(xhr, status, error);
421 421 var msg;
422 422 if (xhr.responseJSON && xhr.responseJSON.message) {
423 423 msg = xhr.responseJSON.message;
424 424 } else {
425 425 msg = xhr.statusText;
426 426 }
427 427 Dialog.modal({
428 428 title : 'Creating Notebook Failed',
429 429 body : "The error was: " + msg,
430 430 buttons : {'OK' : {'class' : 'btn-primary'}}
431 431 });
432 432 };
433 433
434 434 // Backwards compatability.
435 435 IPython.NotebookList = NotebookList;
436 436
437 return NotebookList;
437 return {'NotebookList': NotebookList};
438 438 });
@@ -1,49 +1,49 b''
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 4 define([
5 5 'base/js/namespace',
6 6 'jquery',
7 7 'base/js/utils',
8 8 ], function(IPython, $, utils) {
9 9 "use strict";
10 10
11 11 var SesssionList = function (options, events) {
12 12 this.events = events;
13 13 this.sessions = {};
14 14 this.base_url = options.base_url || utils.get_body_data("baseUrl");
15 15 };
16 16
17 17 SesssionList.prototype.load_sessions = function(){
18 18 var that = this;
19 19 var settings = {
20 20 processData : false,
21 21 cache : false,
22 22 type : "GET",
23 23 dataType : "json",
24 24 success : $.proxy(that.sessions_loaded, this),
25 25 error : utils.log_ajax_error,
26 26 };
27 27 var url = utils.url_join_encode(this.base_url, 'api/sessions');
28 28 $.ajax(url, settings);
29 29 };
30 30
31 31 SesssionList.prototype.sessions_loaded = function(data){
32 32 this.sessions = {};
33 33 var len = data.length;
34 34 var nb_path;
35 35 for (var i=0; i<len; i++) {
36 36 nb_path = utils.url_path_join(
37 37 data[i].notebook.path,
38 38 data[i].notebook.name
39 39 );
40 40 this.sessions[nb_path] = data[i].id;
41 41 }
42 42 this.events.trigger('sessions_loaded.Dashboard', this.sessions);
43 43 };
44 44
45 45 // Backwards compatability.
46 46 IPython.SesssionList = SesssionList;
47 47
48 return SesssionList;
48 return {'SesssionList': SesssionList};
49 49 });
General Comments 0
You need to be logged in to leave comments. Login now