##// END OF EJS Templates
Return dicts instead of classes,...
Jonathan Frederic -
Show More
@@ -1,10 +1,10 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 var ipython = ipython || {};
4 var ipython = ipython || {};
5 require(['base/js/page'], function(Page) {
5 require(['base/js/page'], function(page) {
6 ipython.page = new Page();
6 ipython.page = new page.Page();
7 $('button#login_submit').addClass("btn btn-default");
7 $('button#login_submit').addClass("btn btn-default");
8 ipython.page.show();
8 ipython.page.show();
9 $('input#password_input').focus();
9 $('input#password_input').focus();
10 });
10 });
@@ -1,47 +1,47 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 define([
4 define([
5 'base/js/namespace',
5 'base/js/namespace',
6 'base/js/utils',
6 'base/js/utils',
7 'jquery',
7 'jquery',
8 ], function(IPython, utils, $){
8 ], function(IPython, utils, $){
9 "use strict";
9 "use strict";
10
10
11 var LoginWidget = function (selector, options) {
11 var LoginWidget = function (selector, options) {
12 options = options || {};
12 options = options || {};
13 this.base_url = options.base_url || utils.get_body_data("baseUrl");
13 this.base_url = options.base_url || utils.get_body_data("baseUrl");
14 this.selector = selector;
14 this.selector = selector;
15 if (this.selector !== undefined) {
15 if (this.selector !== undefined) {
16 this.element = $(selector);
16 this.element = $(selector);
17 this.style();
17 this.style();
18 this.bind_events();
18 this.bind_events();
19 }
19 }
20 };
20 };
21
21
22 LoginWidget.prototype.style = function () {
22 LoginWidget.prototype.style = function () {
23 this.element.find("button").addClass("btn btn-default btn-sm");
23 this.element.find("button").addClass("btn btn-default btn-sm");
24 };
24 };
25
25
26
26
27 LoginWidget.prototype.bind_events = function () {
27 LoginWidget.prototype.bind_events = function () {
28 var that = this;
28 var that = this;
29 this.element.find("button#logout").click(function () {
29 this.element.find("button#logout").click(function () {
30 window.location = utils.url_join_encode(
30 window.location = utils.url_join_encode(
31 that.base_url,
31 that.base_url,
32 "logout"
32 "logout"
33 );
33 );
34 });
34 });
35 this.element.find("button#login").click(function () {
35 this.element.find("button#login").click(function () {
36 window.location = utils.url_join_encode(
36 window.location = utils.url_join_encode(
37 that.base_url,
37 that.base_url,
38 "login"
38 "login"
39 );
39 );
40 });
40 });
41 };
41 };
42
42
43 // Set module variables
43 // Set module variables
44 IPython.LoginWidget = LoginWidget;
44 IPython.LoginWidget = LoginWidget;
45
45
46 return LoginWidget;
46 return {'LoginWidget': LoginWidget};
47 }); No newline at end of file
47 });
@@ -1,9 +1,9 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 var ipython = ipython || {};
4 var ipython = ipython || {};
5 require(['base/js/page'], function(Page) {
5 require(['base/js/page'], function(Page) {
6 ipython.page = new Page();
6 ipython.page = new page.Page();
7 $('#ipython-main-app').addClass('border-box-sizing');
7 $('#ipython-main-app').addClass('border-box-sizing');
8 ipython.page.show();
8 ipython.page.show();
9 });
9 });
@@ -1,158 +1,158 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 define([
4 define([
5 'base/js/namespace',
5 'base/js/namespace',
6 'jquery',
6 'jquery',
7 ], function(IPython, $) {
7 ], function(IPython, $) {
8 "use strict";
8 "use strict";
9
9
10 var modal = function (options, keyboard_manager, notebook) {
10 var modal = function (options, keyboard_manager, notebook) {
11 var modal = $("<div/>")
11 var modal = $("<div/>")
12 .addClass("modal")
12 .addClass("modal")
13 .addClass("fade")
13 .addClass("fade")
14 .attr("role", "dialog");
14 .attr("role", "dialog");
15 var dialog = $("<div/>")
15 var dialog = $("<div/>")
16 .addClass("modal-dialog")
16 .addClass("modal-dialog")
17 .appendTo(modal);
17 .appendTo(modal);
18 var dialog_content = $("<div/>")
18 var dialog_content = $("<div/>")
19 .addClass("modal-content")
19 .addClass("modal-content")
20 .appendTo(dialog);
20 .appendTo(dialog);
21 dialog_content.append(
21 dialog_content.append(
22 $("<div/>")
22 $("<div/>")
23 .addClass("modal-header")
23 .addClass("modal-header")
24 .append($("<button>")
24 .append($("<button>")
25 .attr("type", "button")
25 .attr("type", "button")
26 .addClass("close")
26 .addClass("close")
27 .attr("data-dismiss", "modal")
27 .attr("data-dismiss", "modal")
28 .attr("aria-hidden", "true")
28 .attr("aria-hidden", "true")
29 .html("&times;")
29 .html("&times;")
30 ).append(
30 ).append(
31 $("<h4/>")
31 $("<h4/>")
32 .addClass('modal-title')
32 .addClass('modal-title')
33 .text(options.title || "")
33 .text(options.title || "")
34 )
34 )
35 ).append(
35 ).append(
36 $("<div/>").addClass("modal-body").append(
36 $("<div/>").addClass("modal-body").append(
37 options.body || $("<p/>")
37 options.body || $("<p/>")
38 )
38 )
39 );
39 );
40
40
41 var footer = $("<div/>").addClass("modal-footer");
41 var footer = $("<div/>").addClass("modal-footer");
42
42
43 for (var label in options.buttons) {
43 for (var label in options.buttons) {
44 var btn_opts = options.buttons[label];
44 var btn_opts = options.buttons[label];
45 var button = $("<button/>")
45 var button = $("<button/>")
46 .addClass("btn btn-default btn-sm")
46 .addClass("btn btn-default btn-sm")
47 .attr("data-dismiss", "modal")
47 .attr("data-dismiss", "modal")
48 .text(label);
48 .text(label);
49 if (btn_opts.click) {
49 if (btn_opts.click) {
50 button.click($.proxy(btn_opts.click, dialog_content));
50 button.click($.proxy(btn_opts.click, dialog_content));
51 }
51 }
52 if (btn_opts.class) {
52 if (btn_opts.class) {
53 button.addClass(btn_opts.class);
53 button.addClass(btn_opts.class);
54 }
54 }
55 footer.append(button);
55 footer.append(button);
56 }
56 }
57 dialog_content.append(footer);
57 dialog_content.append(footer);
58 // hook up on-open event
58 // hook up on-open event
59 modal.on("shown.bs.modal", function() {
59 modal.on("shown.bs.modal", function() {
60 setTimeout(function() {
60 setTimeout(function() {
61 footer.find("button").last().focus();
61 footer.find("button").last().focus();
62 if (options.open) {
62 if (options.open) {
63 $.proxy(options.open, modal)();
63 $.proxy(options.open, modal)();
64 }
64 }
65 }, 0);
65 }, 0);
66 });
66 });
67
67
68 // destroy modal on hide, unless explicitly asked not to
68 // destroy modal on hide, unless explicitly asked not to
69 if (options.destroy === undefined || options.destroy) {
69 if (options.destroy === undefined || options.destroy) {
70 modal.on("hidden.bs.modal", function () {
70 modal.on("hidden.bs.modal", function () {
71 modal.remove();
71 modal.remove();
72 });
72 });
73 }
73 }
74 modal.on("hidden.bs.modal", function () {
74 modal.on("hidden.bs.modal", function () {
75 if (notebook) {
75 if (notebook) {
76 var cell = notebook.get_selected_cell();
76 var cell = notebook.get_selected_cell();
77 if (cell) cell.select();
77 if (cell) cell.select();
78 keyboard_manager.enable();
78 keyboard_manager.enable();
79 keyboard_manager.command_mode();
79 keyboard_manager.command_mode();
80 }
80 }
81 });
81 });
82
82
83 if (keyboard_manager) {
83 if (keyboard_manager) {
84 keyboard_manager.disable();
84 keyboard_manager.disable();
85 }
85 }
86
86
87 return modal.modal(options);
87 return modal.modal(options);
88 };
88 };
89
89
90 var edit_metadata = function (md, callback, name, keyboard_manager, notebook) {
90 var edit_metadata = function (md, callback, name, keyboard_manager, notebook) {
91 name = name || "Cell";
91 name = name || "Cell";
92 var error_div = $('<div/>').css('color', 'red');
92 var error_div = $('<div/>').css('color', 'red');
93 var message =
93 var message =
94 "Manually edit the JSON below to manipulate the metadata for this " + name + "." +
94 "Manually edit the JSON below to manipulate the metadata for this " + name + "." +
95 " We recommend putting custom metadata attributes in an appropriately named sub-structure," +
95 " We recommend putting custom metadata attributes in an appropriately named sub-structure," +
96 " so they don't conflict with those of others.";
96 " so they don't conflict with those of others.";
97
97
98 var textarea = $('<textarea/>')
98 var textarea = $('<textarea/>')
99 .attr('rows', '13')
99 .attr('rows', '13')
100 .attr('cols', '80')
100 .attr('cols', '80')
101 .attr('name', 'metadata')
101 .attr('name', 'metadata')
102 .text(JSON.stringify(md || {}, null, 2));
102 .text(JSON.stringify(md || {}, null, 2));
103
103
104 var dialogform = $('<div/>').attr('title', 'Edit the metadata')
104 var dialogform = $('<div/>').attr('title', 'Edit the metadata')
105 .append(
105 .append(
106 $('<form/>').append(
106 $('<form/>').append(
107 $('<fieldset/>').append(
107 $('<fieldset/>').append(
108 $('<label/>')
108 $('<label/>')
109 .attr('for','metadata')
109 .attr('for','metadata')
110 .text(message)
110 .text(message)
111 )
111 )
112 .append(error_div)
112 .append(error_div)
113 .append($('<br/>'))
113 .append($('<br/>'))
114 .append(textarea)
114 .append(textarea)
115 )
115 )
116 );
116 );
117 var editor = CodeMirror.fromTextArea(textarea[0], {
117 var editor = CodeMirror.fromTextArea(textarea[0], {
118 lineNumbers: true,
118 lineNumbers: true,
119 matchBrackets: true,
119 matchBrackets: true,
120 indentUnit: 2,
120 indentUnit: 2,
121 autoIndent: true,
121 autoIndent: true,
122 mode: 'application/json',
122 mode: 'application/json',
123 });
123 });
124 var modal = modal({
124 var modal = modal({
125 title: "Edit " + name + " Metadata",
125 title: "Edit " + name + " Metadata",
126 body: dialogform,
126 body: dialogform,
127 buttons: {
127 buttons: {
128 OK: { class : "btn-primary",
128 OK: { class : "btn-primary",
129 click: function() {
129 click: function() {
130 // validate json and set it
130 // validate json and set it
131 var new_md;
131 var new_md;
132 try {
132 try {
133 new_md = JSON.parse(editor.getValue());
133 new_md = JSON.parse(editor.getValue());
134 } catch(e) {
134 } catch(e) {
135 console.log(e);
135 console.log(e);
136 error_div.text('WARNING: Could not save invalid JSON.');
136 error_div.text('WARNING: Could not save invalid JSON.');
137 return false;
137 return false;
138 }
138 }
139 callback(new_md);
139 callback(new_md);
140 }
140 }
141 },
141 },
142 Cancel: {}
142 Cancel: {}
143 }
143 }
144 }, keyboard_manager, notebook);
144 }, keyboard_manager, notebook);
145
145
146 modal.on('shown.bs.modal', function(){ editor.refresh(); });
146 modal.on('shown.bs.modal', function(){ editor.refresh(); });
147 };
147 };
148
148
149 var Dialog = {
149 var Dialog = {
150 modal : modal,
150 modal : modal,
151 edit_metadata : edit_metadata,
151 edit_metadata : edit_metadata,
152 };
152 };
153
153
154 // Backwards compatability.
154 // Backwards compatability.
155 IPython.Dialog = Dialog;
155 IPython.Dialog = Dialog;
156
156
157 return Dialog;
157 return {'Dialog': Dialog};
158 });
158 });
@@ -1,19 +1,19 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 // Give us an object to bind all events to. This object should be created
4 // Give us an object to bind all events to. This object should be created
5 // before all other objects so it exists when others register event handlers.
5 // before all other objects so it exists when others register event handlers.
6 // To trigger an event handler:
6 // To trigger an event handler:
7 // $([IPython.events]).trigger('event.Namespace');
7 // $([IPython.events]).trigger('event.Namespace');
8 // To handle it:
8 // To handle it:
9 // $([IPython.events]).on('event.Namespace',function () {});
9 // $([IPython.events]).on('event.Namespace',function () {});
10 define(['base/js/namespace'], function(IPython) {
10 define(['base/js/namespace'], function(IPython) {
11 "use strict";
11 "use strict";
12
12
13 var Events = function () {};
13 var Events = function () {};
14
14
15 // Backwards compatability.
15 // Backwards compatability.
16 IPython.Events = Events;
16 IPython.Events = Events;
17
17
18 return Events;
18 return {'Events': Events};
19 });
19 });
@@ -1,48 +1,48 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 define([
4 define([
5 'base/js/namespace',
5 'base/js/namespace',
6 'jquery',
6 'jquery',
7 'components/jquery-ui/ui/minified/jquery-ui.min',
7 'components/jquery-ui/ui/minified/jquery-ui.min',
8 'components/bootstrap/js/bootstrap.min',
8 'components/bootstrap/js/bootstrap.min',
9 'auth/js/loginwidget'
9 'auth/js/loginwidget'
10 ], function(IPython, $){
10 ], function(IPython, $){
11 "use strict";
11 "use strict";
12
12
13 var Page = function () {
13 var Page = function () {
14 this.style();
14 this.style();
15 this.bind_events();
15 this.bind_events();
16 };
16 };
17
17
18 Page.prototype.style = function () {
18 Page.prototype.style = function () {
19 $('div#header').addClass('border-box-sizing');
19 $('div#header').addClass('border-box-sizing');
20 $('div#site').addClass('border-box-sizing');
20 $('div#site').addClass('border-box-sizing');
21 };
21 };
22
22
23 Page.prototype.bind_events = function () {
23 Page.prototype.bind_events = function () {
24 };
24 };
25
25
26 Page.prototype.show = function () {
26 Page.prototype.show = function () {
27 // The header and site divs start out hidden to prevent FLOUC.
27 // The header and site divs start out hidden to prevent FLOUC.
28 // Main scripts should call this method after styling everything.
28 // Main scripts should call this method after styling everything.
29 this.show_header();
29 this.show_header();
30 this.show_site();
30 this.show_site();
31 };
31 };
32
32
33 Page.prototype.show_header = function () {
33 Page.prototype.show_header = function () {
34 // The header and site divs start out hidden to prevent FLOUC.
34 // The header and site divs start out hidden to prevent FLOUC.
35 // Main scripts should call this method after styling everything.
35 // Main scripts should call this method after styling everything.
36 $('div#header').css('display','block');
36 $('div#header').css('display','block');
37 };
37 };
38
38
39 Page.prototype.show_site = function () {
39 Page.prototype.show_site = function () {
40 // The header and site divs start out hidden to prevent FLOUC.
40 // The header and site divs start out hidden to prevent FLOUC.
41 // Main scripts should call this method after styling everything.
41 // Main scripts should call this method after styling everything.
42 $('div#site').css('display','block');
42 $('div#site').css('display','block');
43 };
43 };
44
44
45 // Register self in the global namespace for convenience.
45 // Register self in the global namespace for convenience.
46 IPython.Page = Page;
46 IPython.Page = Page;
47 return Page;
47 return {'Page': Page};
48 });
48 });
@@ -1,8 +1,8 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 var ipython = ipython || {};
4 var ipython = ipython || {};
5 require(['base/js/page'], function(Page) {
5 require(['base/js/page'], function(Page) {
6 ipython.page = new Page();
6 ipython.page = new page.Page();
7 ipython.page.show();
7 ipython.page.show();
8 });
8 });
@@ -1,132 +1,132 b''
1 /*
1 /*
2 * Date Format 1.2.3
2 * Date Format 1.2.3
3 * (c) 2007-2009 Steven Levithan <stevenlevithan.com>
3 * (c) 2007-2009 Steven Levithan <stevenlevithan.com>
4 * MIT license
4 * MIT license
5 *
5 *
6 * Includes enhancements by Scott Trenda <scott.trenda.net>
6 * Includes enhancements by Scott Trenda <scott.trenda.net>
7 * and Kris Kowal <cixar.com/~kris.kowal/>
7 * and Kris Kowal <cixar.com/~kris.kowal/>
8 *
8 *
9 * Accepts a date, a mask, or a date and a mask.
9 * Accepts a date, a mask, or a date and a mask.
10 * Returns a formatted version of the given date.
10 * Returns a formatted version of the given date.
11 * The date defaults to the current date/time.
11 * The date defaults to the current date/time.
12 * The mask defaults to dateFormat.masks.default.
12 * The mask defaults to dateFormat.masks.default.
13 */
13 */
14 // Copyright (c) IPython Development Team.
14 // Copyright (c) IPython Development Team.
15 // Distributed under the terms of the Modified BSD License.
15 // Distributed under the terms of the Modified BSD License.
16
16
17 // Require.js define call added by IPython team.
17 // Require.js define call added by IPython team.
18 define([], function() {
18 define([], function() {
19 var dateFormat = function () {
19 var dateFormat = function () {
20 var token = /d{1,4}|m{1,4}|yy(?:yy)?|([HhMsTt])\1?|[LloSZ]|"[^"]*"|'[^']*'/g,
20 var token = /d{1,4}|m{1,4}|yy(?:yy)?|([HhMsTt])\1?|[LloSZ]|"[^"]*"|'[^']*'/g,
21 timezone = /\b(?:[PMCEA][SDP]T|(?:Pacific|Mountain|Central|Eastern|Atlantic) (?:Standard|Daylight|Prevailing) Time|(?:GMT|UTC)(?:[-+]\d{4})?)\b/g,
21 timezone = /\b(?:[PMCEA][SDP]T|(?:Pacific|Mountain|Central|Eastern|Atlantic) (?:Standard|Daylight|Prevailing) Time|(?:GMT|UTC)(?:[-+]\d{4})?)\b/g,
22 timezoneClip = /[^-+\dA-Z]/g,
22 timezoneClip = /[^-+\dA-Z]/g,
23 pad = function (val, len) {
23 pad = function (val, len) {
24 val = String(val);
24 val = String(val);
25 len = len || 2;
25 len = len || 2;
26 while (val.length < len) val = "0" + val;
26 while (val.length < len) val = "0" + val;
27 return val;
27 return val;
28 };
28 };
29
29
30 // Regexes and supporting functions are cached through closure
30 // Regexes and supporting functions are cached through closure
31 return function (date, mask, utc) {
31 return function (date, mask, utc) {
32 var dF = dateFormat;
32 var dF = dateFormat;
33
33
34 // You can't provide utc if you skip other args (use the "UTC:" mask prefix)
34 // You can't provide utc if you skip other args (use the "UTC:" mask prefix)
35 if (arguments.length == 1 && Object.prototype.toString.call(date) == "[object String]" && !/\d/.test(date)) {
35 if (arguments.length == 1 && Object.prototype.toString.call(date) == "[object String]" && !/\d/.test(date)) {
36 mask = date;
36 mask = date;
37 date = undefined;
37 date = undefined;
38 }
38 }
39
39
40 // Passing date through Date applies Date.parse, if necessary
40 // Passing date through Date applies Date.parse, if necessary
41 date = date ? new Date(date) : new Date;
41 date = date ? new Date(date) : new Date;
42 if (isNaN(date)) throw SyntaxError("invalid date");
42 if (isNaN(date)) throw SyntaxError("invalid date");
43
43
44 mask = String(dF.masks[mask] || mask || dF.masks["default"]);
44 mask = String(dF.masks[mask] || mask || dF.masks["default"]);
45
45
46 // Allow setting the utc argument via the mask
46 // Allow setting the utc argument via the mask
47 if (mask.slice(0, 4) == "UTC:") {
47 if (mask.slice(0, 4) == "UTC:") {
48 mask = mask.slice(4);
48 mask = mask.slice(4);
49 utc = true;
49 utc = true;
50 }
50 }
51
51
52 var _ = utc ? "getUTC" : "get",
52 var _ = utc ? "getUTC" : "get",
53 d = date[_ + "Date"](),
53 d = date[_ + "Date"](),
54 D = date[_ + "Day"](),
54 D = date[_ + "Day"](),
55 m = date[_ + "Month"](),
55 m = date[_ + "Month"](),
56 y = date[_ + "FullYear"](),
56 y = date[_ + "FullYear"](),
57 H = date[_ + "Hours"](),
57 H = date[_ + "Hours"](),
58 M = date[_ + "Minutes"](),
58 M = date[_ + "Minutes"](),
59 s = date[_ + "Seconds"](),
59 s = date[_ + "Seconds"](),
60 L = date[_ + "Milliseconds"](),
60 L = date[_ + "Milliseconds"](),
61 o = utc ? 0 : date.getTimezoneOffset(),
61 o = utc ? 0 : date.getTimezoneOffset(),
62 flags = {
62 flags = {
63 d: d,
63 d: d,
64 dd: pad(d),
64 dd: pad(d),
65 ddd: dF.i18n.dayNames[D],
65 ddd: dF.i18n.dayNames[D],
66 dddd: dF.i18n.dayNames[D + 7],
66 dddd: dF.i18n.dayNames[D + 7],
67 m: m + 1,
67 m: m + 1,
68 mm: pad(m + 1),
68 mm: pad(m + 1),
69 mmm: dF.i18n.monthNames[m],
69 mmm: dF.i18n.monthNames[m],
70 mmmm: dF.i18n.monthNames[m + 12],
70 mmmm: dF.i18n.monthNames[m + 12],
71 yy: String(y).slice(2),
71 yy: String(y).slice(2),
72 yyyy: y,
72 yyyy: y,
73 h: H % 12 || 12,
73 h: H % 12 || 12,
74 hh: pad(H % 12 || 12),
74 hh: pad(H % 12 || 12),
75 H: H,
75 H: H,
76 HH: pad(H),
76 HH: pad(H),
77 M: M,
77 M: M,
78 MM: pad(M),
78 MM: pad(M),
79 s: s,
79 s: s,
80 ss: pad(s),
80 ss: pad(s),
81 l: pad(L, 3),
81 l: pad(L, 3),
82 L: pad(L > 99 ? Math.round(L / 10) : L),
82 L: pad(L > 99 ? Math.round(L / 10) : L),
83 t: H < 12 ? "a" : "p",
83 t: H < 12 ? "a" : "p",
84 tt: H < 12 ? "am" : "pm",
84 tt: H < 12 ? "am" : "pm",
85 T: H < 12 ? "A" : "P",
85 T: H < 12 ? "A" : "P",
86 TT: H < 12 ? "AM" : "PM",
86 TT: H < 12 ? "AM" : "PM",
87 Z: utc ? "UTC" : (String(date).match(timezone) || [""]).pop().replace(timezoneClip, ""),
87 Z: utc ? "UTC" : (String(date).match(timezone) || [""]).pop().replace(timezoneClip, ""),
88 o: (o > 0 ? "-" : "+") + pad(Math.floor(Math.abs(o) / 60) * 100 + Math.abs(o) % 60, 4),
88 o: (o > 0 ? "-" : "+") + pad(Math.floor(Math.abs(o) / 60) * 100 + Math.abs(o) % 60, 4),
89 S: ["th", "st", "nd", "rd"][d % 10 > 3 ? 0 : (d % 100 - d % 10 != 10) * d % 10]
89 S: ["th", "st", "nd", "rd"][d % 10 > 3 ? 0 : (d % 100 - d % 10 != 10) * d % 10]
90 };
90 };
91
91
92 return mask.replace(token, function ($0) {
92 return mask.replace(token, function ($0) {
93 return $0 in flags ? flags[$0] : $0.slice(1, $0.length - 1);
93 return $0 in flags ? flags[$0] : $0.slice(1, $0.length - 1);
94 });
94 });
95 };
95 };
96 }();
96 }();
97
97
98 // Some common format strings
98 // Some common format strings
99 dateFormat.masks = {
99 dateFormat.masks = {
100 "default": "ddd mmm dd yyyy HH:MM:ss",
100 "default": "ddd mmm dd yyyy HH:MM:ss",
101 shortDate: "m/d/yy",
101 shortDate: "m/d/yy",
102 mediumDate: "mmm d, yyyy",
102 mediumDate: "mmm d, yyyy",
103 longDate: "mmmm d, yyyy",
103 longDate: "mmmm d, yyyy",
104 fullDate: "dddd, mmmm d, yyyy",
104 fullDate: "dddd, mmmm d, yyyy",
105 shortTime: "h:MM TT",
105 shortTime: "h:MM TT",
106 mediumTime: "h:MM:ss TT",
106 mediumTime: "h:MM:ss TT",
107 longTime: "h:MM:ss TT Z",
107 longTime: "h:MM:ss TT Z",
108 isoDate: "yyyy-mm-dd",
108 isoDate: "yyyy-mm-dd",
109 isoTime: "HH:MM:ss",
109 isoTime: "HH:MM:ss",
110 isoDateTime: "yyyy-mm-dd'T'HH:MM:ss",
110 isoDateTime: "yyyy-mm-dd'T'HH:MM:ss",
111 isoUtcDateTime: "UTC:yyyy-mm-dd'T'HH:MM:ss'Z'"
111 isoUtcDateTime: "UTC:yyyy-mm-dd'T'HH:MM:ss'Z'"
112 };
112 };
113
113
114 // Internationalization strings
114 // Internationalization strings
115 dateFormat.i18n = {
115 dateFormat.i18n = {
116 dayNames: [
116 dayNames: [
117 "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat",
117 "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat",
118 "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"
118 "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"
119 ],
119 ],
120 monthNames: [
120 monthNames: [
121 "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
121 "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
122 "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"
122 "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"
123 ]
123 ]
124 };
124 };
125
125
126 // For convenience...
126 // For convenience...
127 Date.prototype.format = function (mask, utc) {
127 Date.prototype.format = function (mask, utc) {
128 return dateFormat(this, mask, utc);
128 return dateFormat(this, mask, utc);
129 };
129 };
130
130
131 return dateFormat;
131 return {'dateFormat': dateFormat};
132 }); No newline at end of file
132 });
@@ -1,564 +1,564 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 define([
4 define([
5 'base/js/namespace',
5 'base/js/namespace',
6 'jquery',
6 'jquery',
7 'base/js/utils',
7 'base/js/utils',
8 ], function(IPython, $, utils) {
8 ], function(IPython, $, utils) {
9 "use strict";
9 "use strict";
10
10
11 // monkey patch CM to be able to syntax highlight cell magics
11 // monkey patch CM to be able to syntax highlight cell magics
12 // bug reported upstream,
12 // bug reported upstream,
13 // see https://github.com/marijnh/CodeMirror2/issues/670
13 // see https://github.com/marijnh/CodeMirror2/issues/670
14 if(CodeMirror.getMode(1,'text/plain').indent === undefined ){
14 if(CodeMirror.getMode(1,'text/plain').indent === undefined ){
15 CodeMirror.modes.null = function() {
15 CodeMirror.modes.null = function() {
16 return {token: function(stream) {stream.skipToEnd();},indent : function(){return 0;}};
16 return {token: function(stream) {stream.skipToEnd();},indent : function(){return 0;}};
17 };
17 };
18 }
18 }
19
19
20 CodeMirror.patchedGetMode = function(config, mode){
20 CodeMirror.patchedGetMode = function(config, mode){
21 var cmmode = CodeMirror.getMode(config, mode);
21 var cmmode = CodeMirror.getMode(config, mode);
22 if(cmmode.indent === null) {
22 if(cmmode.indent === null) {
23 console.log('patch mode "' , mode, '" on the fly');
23 console.log('patch mode "' , mode, '" on the fly');
24 cmmode.indent = function(){return 0;};
24 cmmode.indent = function(){return 0;};
25 }
25 }
26 return cmmode;
26 return cmmode;
27 };
27 };
28 // end monkey patching CodeMirror
28 // end monkey patching CodeMirror
29
29
30 /**
30 /**
31 * The Base `Cell` class from which to inherit
31 * The Base `Cell` class from which to inherit
32 * @class Cell
32 * @class Cell
33 **/
33 **/
34
34
35 /*
35 /*
36 * @constructor
36 * @constructor
37 *
37 *
38 * @param {object|undefined} [options]
38 * @param {object|undefined} [options]
39 * @param [options.cm_config] {object} config to pass to CodeMirror, will extend default parameters
39 * @param [options.cm_config] {object} config to pass to CodeMirror, will extend default parameters
40 */
40 */
41 var Cell = function (options, keyboard_manager, events) {
41 var Cell = function (options, keyboard_manager, events) {
42 this.keyboard_manager = keyboard_manager;
42 this.keyboard_manager = keyboard_manager;
43 this.events = events;
43 this.events = events;
44 options = this.mergeopt(Cell, options);
44 options = this.mergeopt(Cell, options);
45 // superclass default overwrite our default
45 // superclass default overwrite our default
46
46
47 this.placeholder = options.placeholder || '';
47 this.placeholder = options.placeholder || '';
48 this.read_only = options.cm_config.readOnly;
48 this.read_only = options.cm_config.readOnly;
49 this.selected = false;
49 this.selected = false;
50 this.rendered = false;
50 this.rendered = false;
51 this.mode = 'command';
51 this.mode = 'command';
52 this.metadata = {};
52 this.metadata = {};
53 // load this from metadata later ?
53 // load this from metadata later ?
54 this.user_highlight = 'auto';
54 this.user_highlight = 'auto';
55 this.cm_config = options.cm_config;
55 this.cm_config = options.cm_config;
56 this.cell_id = utils.uuid();
56 this.cell_id = utils.uuid();
57 this._options = options;
57 this._options = options;
58
58
59 // For JS VM engines optimization, attributes should be all set (even
59 // For JS VM engines optimization, attributes should be all set (even
60 // to null) in the constructor, and if possible, if different subclass
60 // to null) in the constructor, and if possible, if different subclass
61 // have new attributes with same name, they should be created in the
61 // have new attributes with same name, they should be created in the
62 // same order. Easiest is to create and set to null in parent class.
62 // same order. Easiest is to create and set to null in parent class.
63
63
64 this.element = null;
64 this.element = null;
65 this.cell_type = this.cell_type || null;
65 this.cell_type = this.cell_type || null;
66 this.code_mirror = null;
66 this.code_mirror = null;
67
67
68 this.create_element();
68 this.create_element();
69 if (this.element !== null) {
69 if (this.element !== null) {
70 this.element.data("cell", this);
70 this.element.data("cell", this);
71 this.bind_events();
71 this.bind_events();
72 this.init_classes();
72 this.init_classes();
73 }
73 }
74 };
74 };
75
75
76 Cell.options_default = {
76 Cell.options_default = {
77 cm_config : {
77 cm_config : {
78 indentUnit : 4,
78 indentUnit : 4,
79 readOnly: false,
79 readOnly: false,
80 theme: "default",
80 theme: "default",
81 extraKeys: {
81 extraKeys: {
82 "Cmd-Right":"goLineRight",
82 "Cmd-Right":"goLineRight",
83 "End":"goLineRight",
83 "End":"goLineRight",
84 "Cmd-Left":"goLineLeft"
84 "Cmd-Left":"goLineLeft"
85 }
85 }
86 }
86 }
87 };
87 };
88
88
89 // FIXME: Workaround CM Bug #332 (Safari segfault on drag)
89 // FIXME: Workaround CM Bug #332 (Safari segfault on drag)
90 // by disabling drag/drop altogether on Safari
90 // by disabling drag/drop altogether on Safari
91 // https://github.com/marijnh/CodeMirror/issues/332
91 // https://github.com/marijnh/CodeMirror/issues/332
92 if (utils.browser[0] == "Safari") {
92 if (utils.browser[0] == "Safari") {
93 Cell.options_default.cm_config.dragDrop = false;
93 Cell.options_default.cm_config.dragDrop = false;
94 }
94 }
95
95
96 Cell.prototype.mergeopt = function(_class, options, overwrite){
96 Cell.prototype.mergeopt = function(_class, options, overwrite){
97 options = options || {};
97 options = options || {};
98 overwrite = overwrite || {};
98 overwrite = overwrite || {};
99 return $.extend(true, {}, _class.options_default, options, overwrite);
99 return $.extend(true, {}, _class.options_default, options, overwrite);
100 };
100 };
101
101
102 /**
102 /**
103 * Empty. Subclasses must implement create_element.
103 * Empty. Subclasses must implement create_element.
104 * This should contain all the code to create the DOM element in notebook
104 * This should contain all the code to create the DOM element in notebook
105 * and will be called by Base Class constructor.
105 * and will be called by Base Class constructor.
106 * @method create_element
106 * @method create_element
107 */
107 */
108 Cell.prototype.create_element = function () {
108 Cell.prototype.create_element = function () {
109 };
109 };
110
110
111 Cell.prototype.init_classes = function () {
111 Cell.prototype.init_classes = function () {
112 // Call after this.element exists to initialize the css classes
112 // Call after this.element exists to initialize the css classes
113 // related to selected, rendered and mode.
113 // related to selected, rendered and mode.
114 if (this.selected) {
114 if (this.selected) {
115 this.element.addClass('selected');
115 this.element.addClass('selected');
116 } else {
116 } else {
117 this.element.addClass('unselected');
117 this.element.addClass('unselected');
118 }
118 }
119 if (this.rendered) {
119 if (this.rendered) {
120 this.element.addClass('rendered');
120 this.element.addClass('rendered');
121 } else {
121 } else {
122 this.element.addClass('unrendered');
122 this.element.addClass('unrendered');
123 }
123 }
124 if (this.mode === 'edit') {
124 if (this.mode === 'edit') {
125 this.element.addClass('edit_mode');
125 this.element.addClass('edit_mode');
126 } else {
126 } else {
127 this.element.addClass('command_mode');
127 this.element.addClass('command_mode');
128 }
128 }
129 };
129 };
130
130
131 /**
131 /**
132 * Subclasses can implement override bind_events.
132 * Subclasses can implement override bind_events.
133 * Be carefull to call the parent method when overwriting as it fires event.
133 * Be carefull to call the parent method when overwriting as it fires event.
134 * this will be triggerd after create_element in constructor.
134 * this will be triggerd after create_element in constructor.
135 * @method bind_events
135 * @method bind_events
136 */
136 */
137 Cell.prototype.bind_events = function () {
137 Cell.prototype.bind_events = function () {
138 var that = this;
138 var that = this;
139 // We trigger events so that Cell doesn't have to depend on Notebook.
139 // We trigger events so that Cell doesn't have to depend on Notebook.
140 that.element.click(function (event) {
140 that.element.click(function (event) {
141 if (!that.selected) {
141 if (!that.selected) {
142 that.events.trigger('select.Cell', {'cell':that});
142 that.events.trigger('select.Cell', {'cell':that});
143 }
143 }
144 });
144 });
145 that.element.focusin(function (event) {
145 that.element.focusin(function (event) {
146 if (!that.selected) {
146 if (!that.selected) {
147 that.events.trigger('select.Cell', {'cell':that});
147 that.events.trigger('select.Cell', {'cell':that});
148 }
148 }
149 });
149 });
150 if (this.code_mirror) {
150 if (this.code_mirror) {
151 this.code_mirror.on("change", function(cm, change) {
151 this.code_mirror.on("change", function(cm, change) {
152 that.events.trigger("set_dirty.Notebook", {value: true});
152 that.events.trigger("set_dirty.Notebook", {value: true});
153 });
153 });
154 }
154 }
155 if (this.code_mirror) {
155 if (this.code_mirror) {
156 this.code_mirror.on('focus', function(cm, change) {
156 this.code_mirror.on('focus', function(cm, change) {
157 that.events.trigger('edit_mode.Cell', {cell: that});
157 that.events.trigger('edit_mode.Cell', {cell: that});
158 });
158 });
159 }
159 }
160 if (this.code_mirror) {
160 if (this.code_mirror) {
161 this.code_mirror.on('blur', function(cm, change) {
161 this.code_mirror.on('blur', function(cm, change) {
162 that.events.trigger('command_mode.Cell', {cell: that});
162 that.events.trigger('command_mode.Cell', {cell: that});
163 });
163 });
164 }
164 }
165 };
165 };
166
166
167 /**
167 /**
168 * This method gets called in CodeMirror's onKeyDown/onKeyPress
168 * This method gets called in CodeMirror's onKeyDown/onKeyPress
169 * handlers and is used to provide custom key handling.
169 * handlers and is used to provide custom key handling.
170 *
170 *
171 * To have custom handling, subclasses should override this method, but still call it
171 * To have custom handling, subclasses should override this method, but still call it
172 * in order to process the Edit mode keyboard shortcuts.
172 * in order to process the Edit mode keyboard shortcuts.
173 *
173 *
174 * @method handle_codemirror_keyevent
174 * @method handle_codemirror_keyevent
175 * @param {CodeMirror} editor - The codemirror instance bound to the cell
175 * @param {CodeMirror} editor - The codemirror instance bound to the cell
176 * @param {event} event - key press event which either should or should not be handled by CodeMirror
176 * @param {event} event - key press event which either should or should not be handled by CodeMirror
177 * @return {Boolean} `true` if CodeMirror should ignore the event, `false` Otherwise
177 * @return {Boolean} `true` if CodeMirror should ignore the event, `false` Otherwise
178 */
178 */
179 Cell.prototype.handle_codemirror_keyevent = function (editor, event) {
179 Cell.prototype.handle_codemirror_keyevent = function (editor, event) {
180 var that = this;
180 var that = this;
181 var shortcuts = this.keyboard_manager.edit_shortcuts;
181 var shortcuts = this.keyboard_manager.edit_shortcuts;
182
182
183 // if this is an edit_shortcuts shortcut, the global keyboard/shortcut
183 // if this is an edit_shortcuts shortcut, the global keyboard/shortcut
184 // manager will handle it
184 // manager will handle it
185 if (shortcuts.handles(event)) { return true; }
185 if (shortcuts.handles(event)) { return true; }
186
186
187 return false;
187 return false;
188 };
188 };
189
189
190
190
191 /**
191 /**
192 * Triger typsetting of math by mathjax on current cell element
192 * Triger typsetting of math by mathjax on current cell element
193 * @method typeset
193 * @method typeset
194 */
194 */
195 Cell.prototype.typeset = function () {
195 Cell.prototype.typeset = function () {
196 if (window.MathJax) {
196 if (window.MathJax) {
197 var cell_math = this.element.get(0);
197 var cell_math = this.element.get(0);
198 MathJax.Hub.Queue(["Typeset", MathJax.Hub, cell_math]);
198 MathJax.Hub.Queue(["Typeset", MathJax.Hub, cell_math]);
199 }
199 }
200 };
200 };
201
201
202 /**
202 /**
203 * handle cell level logic when a cell is selected
203 * handle cell level logic when a cell is selected
204 * @method select
204 * @method select
205 * @return is the action being taken
205 * @return is the action being taken
206 */
206 */
207 Cell.prototype.select = function () {
207 Cell.prototype.select = function () {
208 if (!this.selected) {
208 if (!this.selected) {
209 this.element.addClass('selected');
209 this.element.addClass('selected');
210 this.element.removeClass('unselected');
210 this.element.removeClass('unselected');
211 this.selected = true;
211 this.selected = true;
212 return true;
212 return true;
213 } else {
213 } else {
214 return false;
214 return false;
215 }
215 }
216 };
216 };
217
217
218 /**
218 /**
219 * handle cell level logic when a cell is unselected
219 * handle cell level logic when a cell is unselected
220 * @method unselect
220 * @method unselect
221 * @return is the action being taken
221 * @return is the action being taken
222 */
222 */
223 Cell.prototype.unselect = function () {
223 Cell.prototype.unselect = function () {
224 if (this.selected) {
224 if (this.selected) {
225 this.element.addClass('unselected');
225 this.element.addClass('unselected');
226 this.element.removeClass('selected');
226 this.element.removeClass('selected');
227 this.selected = false;
227 this.selected = false;
228 return true;
228 return true;
229 } else {
229 } else {
230 return false;
230 return false;
231 }
231 }
232 };
232 };
233
233
234 /**
234 /**
235 * handle cell level logic when a cell is rendered
235 * handle cell level logic when a cell is rendered
236 * @method render
236 * @method render
237 * @return is the action being taken
237 * @return is the action being taken
238 */
238 */
239 Cell.prototype.render = function () {
239 Cell.prototype.render = function () {
240 if (!this.rendered) {
240 if (!this.rendered) {
241 this.element.addClass('rendered');
241 this.element.addClass('rendered');
242 this.element.removeClass('unrendered');
242 this.element.removeClass('unrendered');
243 this.rendered = true;
243 this.rendered = true;
244 return true;
244 return true;
245 } else {
245 } else {
246 return false;
246 return false;
247 }
247 }
248 };
248 };
249
249
250 /**
250 /**
251 * handle cell level logic when a cell is unrendered
251 * handle cell level logic when a cell is unrendered
252 * @method unrender
252 * @method unrender
253 * @return is the action being taken
253 * @return is the action being taken
254 */
254 */
255 Cell.prototype.unrender = function () {
255 Cell.prototype.unrender = function () {
256 if (this.rendered) {
256 if (this.rendered) {
257 this.element.addClass('unrendered');
257 this.element.addClass('unrendered');
258 this.element.removeClass('rendered');
258 this.element.removeClass('rendered');
259 this.rendered = false;
259 this.rendered = false;
260 return true;
260 return true;
261 } else {
261 } else {
262 return false;
262 return false;
263 }
263 }
264 };
264 };
265
265
266 /**
266 /**
267 * Delegates keyboard shortcut handling to either IPython keyboard
267 * Delegates keyboard shortcut handling to either IPython keyboard
268 * manager when in command mode, or CodeMirror when in edit mode
268 * manager when in command mode, or CodeMirror when in edit mode
269 *
269 *
270 * @method handle_keyevent
270 * @method handle_keyevent
271 * @param {CodeMirror} editor - The codemirror instance bound to the cell
271 * @param {CodeMirror} editor - The codemirror instance bound to the cell
272 * @param {event} - key event to be handled
272 * @param {event} - key event to be handled
273 * @return {Boolean} `true` if CodeMirror should ignore the event, `false` Otherwise
273 * @return {Boolean} `true` if CodeMirror should ignore the event, `false` Otherwise
274 */
274 */
275 Cell.prototype.handle_keyevent = function (editor, event) {
275 Cell.prototype.handle_keyevent = function (editor, event) {
276
276
277 // console.log('CM', this.mode, event.which, event.type)
277 // console.log('CM', this.mode, event.which, event.type)
278
278
279 if (this.mode === 'command') {
279 if (this.mode === 'command') {
280 return true;
280 return true;
281 } else if (this.mode === 'edit') {
281 } else if (this.mode === 'edit') {
282 return this.handle_codemirror_keyevent(editor, event);
282 return this.handle_codemirror_keyevent(editor, event);
283 }
283 }
284 };
284 };
285
285
286 /**
286 /**
287 * @method at_top
287 * @method at_top
288 * @return {Boolean}
288 * @return {Boolean}
289 */
289 */
290 Cell.prototype.at_top = function () {
290 Cell.prototype.at_top = function () {
291 var cm = this.code_mirror;
291 var cm = this.code_mirror;
292 var cursor = cm.getCursor();
292 var cursor = cm.getCursor();
293 if (cursor.line === 0 && cursor.ch === 0) {
293 if (cursor.line === 0 && cursor.ch === 0) {
294 return true;
294 return true;
295 }
295 }
296 return false;
296 return false;
297 };
297 };
298
298
299 /**
299 /**
300 * @method at_bottom
300 * @method at_bottom
301 * @return {Boolean}
301 * @return {Boolean}
302 * */
302 * */
303 Cell.prototype.at_bottom = function () {
303 Cell.prototype.at_bottom = function () {
304 var cm = this.code_mirror;
304 var cm = this.code_mirror;
305 var cursor = cm.getCursor();
305 var cursor = cm.getCursor();
306 if (cursor.line === (cm.lineCount()-1) && cursor.ch === cm.getLine(cursor.line).length) {
306 if (cursor.line === (cm.lineCount()-1) && cursor.ch === cm.getLine(cursor.line).length) {
307 return true;
307 return true;
308 }
308 }
309 return false;
309 return false;
310 };
310 };
311
311
312 /**
312 /**
313 * enter the command mode for the cell
313 * enter the command mode for the cell
314 * @method command_mode
314 * @method command_mode
315 * @return is the action being taken
315 * @return is the action being taken
316 */
316 */
317 Cell.prototype.command_mode = function () {
317 Cell.prototype.command_mode = function () {
318 if (this.mode !== 'command') {
318 if (this.mode !== 'command') {
319 this.element.addClass('command_mode');
319 this.element.addClass('command_mode');
320 this.element.removeClass('edit_mode');
320 this.element.removeClass('edit_mode');
321 this.mode = 'command';
321 this.mode = 'command';
322 return true;
322 return true;
323 } else {
323 } else {
324 return false;
324 return false;
325 }
325 }
326 };
326 };
327
327
328 /**
328 /**
329 * enter the edit mode for the cell
329 * enter the edit mode for the cell
330 * @method command_mode
330 * @method command_mode
331 * @return is the action being taken
331 * @return is the action being taken
332 */
332 */
333 Cell.prototype.edit_mode = function () {
333 Cell.prototype.edit_mode = function () {
334 if (this.mode !== 'edit') {
334 if (this.mode !== 'edit') {
335 this.element.addClass('edit_mode');
335 this.element.addClass('edit_mode');
336 this.element.removeClass('command_mode');
336 this.element.removeClass('command_mode');
337 this.mode = 'edit';
337 this.mode = 'edit';
338 return true;
338 return true;
339 } else {
339 } else {
340 return false;
340 return false;
341 }
341 }
342 };
342 };
343
343
344 /**
344 /**
345 * Focus the cell in the DOM sense
345 * Focus the cell in the DOM sense
346 * @method focus_cell
346 * @method focus_cell
347 */
347 */
348 Cell.prototype.focus_cell = function () {
348 Cell.prototype.focus_cell = function () {
349 this.element.focus();
349 this.element.focus();
350 };
350 };
351
351
352 /**
352 /**
353 * Focus the editor area so a user can type
353 * Focus the editor area so a user can type
354 *
354 *
355 * NOTE: If codemirror is focused via a mouse click event, you don't want to
355 * NOTE: If codemirror is focused via a mouse click event, you don't want to
356 * call this because it will cause a page jump.
356 * call this because it will cause a page jump.
357 * @method focus_editor
357 * @method focus_editor
358 */
358 */
359 Cell.prototype.focus_editor = function () {
359 Cell.prototype.focus_editor = function () {
360 this.refresh();
360 this.refresh();
361 this.code_mirror.focus();
361 this.code_mirror.focus();
362 };
362 };
363
363
364 /**
364 /**
365 * Refresh codemirror instance
365 * Refresh codemirror instance
366 * @method refresh
366 * @method refresh
367 */
367 */
368 Cell.prototype.refresh = function () {
368 Cell.prototype.refresh = function () {
369 this.code_mirror.refresh();
369 this.code_mirror.refresh();
370 };
370 };
371
371
372 /**
372 /**
373 * should be overritten by subclass
373 * should be overritten by subclass
374 * @method get_text
374 * @method get_text
375 */
375 */
376 Cell.prototype.get_text = function () {
376 Cell.prototype.get_text = function () {
377 };
377 };
378
378
379 /**
379 /**
380 * should be overritten by subclass
380 * should be overritten by subclass
381 * @method set_text
381 * @method set_text
382 * @param {string} text
382 * @param {string} text
383 */
383 */
384 Cell.prototype.set_text = function (text) {
384 Cell.prototype.set_text = function (text) {
385 };
385 };
386
386
387 /**
387 /**
388 * should be overritten by subclass
388 * should be overritten by subclass
389 * serialise cell to json.
389 * serialise cell to json.
390 * @method toJSON
390 * @method toJSON
391 **/
391 **/
392 Cell.prototype.toJSON = function () {
392 Cell.prototype.toJSON = function () {
393 var data = {};
393 var data = {};
394 data.metadata = this.metadata;
394 data.metadata = this.metadata;
395 data.cell_type = this.cell_type;
395 data.cell_type = this.cell_type;
396 return data;
396 return data;
397 };
397 };
398
398
399
399
400 /**
400 /**
401 * should be overritten by subclass
401 * should be overritten by subclass
402 * @method fromJSON
402 * @method fromJSON
403 **/
403 **/
404 Cell.prototype.fromJSON = function (data) {
404 Cell.prototype.fromJSON = function (data) {
405 if (data.metadata !== undefined) {
405 if (data.metadata !== undefined) {
406 this.metadata = data.metadata;
406 this.metadata = data.metadata;
407 }
407 }
408 this.celltoolbar.rebuild();
408 this.celltoolbar.rebuild();
409 };
409 };
410
410
411
411
412 /**
412 /**
413 * can the cell be split into two cells
413 * can the cell be split into two cells
414 * @method is_splittable
414 * @method is_splittable
415 **/
415 **/
416 Cell.prototype.is_splittable = function () {
416 Cell.prototype.is_splittable = function () {
417 return true;
417 return true;
418 };
418 };
419
419
420
420
421 /**
421 /**
422 * can the cell be merged with other cells
422 * can the cell be merged with other cells
423 * @method is_mergeable
423 * @method is_mergeable
424 **/
424 **/
425 Cell.prototype.is_mergeable = function () {
425 Cell.prototype.is_mergeable = function () {
426 return true;
426 return true;
427 };
427 };
428
428
429
429
430 /**
430 /**
431 * @return {String} - the text before the cursor
431 * @return {String} - the text before the cursor
432 * @method get_pre_cursor
432 * @method get_pre_cursor
433 **/
433 **/
434 Cell.prototype.get_pre_cursor = function () {
434 Cell.prototype.get_pre_cursor = function () {
435 var cursor = this.code_mirror.getCursor();
435 var cursor = this.code_mirror.getCursor();
436 var text = this.code_mirror.getRange({line:0, ch:0}, cursor);
436 var text = this.code_mirror.getRange({line:0, ch:0}, cursor);
437 text = text.replace(/^\n+/, '').replace(/\n+$/, '');
437 text = text.replace(/^\n+/, '').replace(/\n+$/, '');
438 return text;
438 return text;
439 };
439 };
440
440
441
441
442 /**
442 /**
443 * @return {String} - the text after the cursor
443 * @return {String} - the text after the cursor
444 * @method get_post_cursor
444 * @method get_post_cursor
445 **/
445 **/
446 Cell.prototype.get_post_cursor = function () {
446 Cell.prototype.get_post_cursor = function () {
447 var cursor = this.code_mirror.getCursor();
447 var cursor = this.code_mirror.getCursor();
448 var last_line_num = this.code_mirror.lineCount()-1;
448 var last_line_num = this.code_mirror.lineCount()-1;
449 var last_line_len = this.code_mirror.getLine(last_line_num).length;
449 var last_line_len = this.code_mirror.getLine(last_line_num).length;
450 var end = {line:last_line_num, ch:last_line_len};
450 var end = {line:last_line_num, ch:last_line_len};
451 var text = this.code_mirror.getRange(cursor, end);
451 var text = this.code_mirror.getRange(cursor, end);
452 text = text.replace(/^\n+/, '').replace(/\n+$/, '');
452 text = text.replace(/^\n+/, '').replace(/\n+$/, '');
453 return text;
453 return text;
454 };
454 };
455
455
456 /**
456 /**
457 * Show/Hide CodeMirror LineNumber
457 * Show/Hide CodeMirror LineNumber
458 * @method show_line_numbers
458 * @method show_line_numbers
459 *
459 *
460 * @param value {Bool} show (true), or hide (false) the line number in CodeMirror
460 * @param value {Bool} show (true), or hide (false) the line number in CodeMirror
461 **/
461 **/
462 Cell.prototype.show_line_numbers = function (value) {
462 Cell.prototype.show_line_numbers = function (value) {
463 this.code_mirror.setOption('lineNumbers', value);
463 this.code_mirror.setOption('lineNumbers', value);
464 this.code_mirror.refresh();
464 this.code_mirror.refresh();
465 };
465 };
466
466
467 /**
467 /**
468 * Toggle CodeMirror LineNumber
468 * Toggle CodeMirror LineNumber
469 * @method toggle_line_numbers
469 * @method toggle_line_numbers
470 **/
470 **/
471 Cell.prototype.toggle_line_numbers = function () {
471 Cell.prototype.toggle_line_numbers = function () {
472 var val = this.code_mirror.getOption('lineNumbers');
472 var val = this.code_mirror.getOption('lineNumbers');
473 this.show_line_numbers(!val);
473 this.show_line_numbers(!val);
474 };
474 };
475
475
476 /**
476 /**
477 * Force codemirror highlight mode
477 * Force codemirror highlight mode
478 * @method force_highlight
478 * @method force_highlight
479 * @param {object} - CodeMirror mode
479 * @param {object} - CodeMirror mode
480 **/
480 **/
481 Cell.prototype.force_highlight = function(mode) {
481 Cell.prototype.force_highlight = function(mode) {
482 this.user_highlight = mode;
482 this.user_highlight = mode;
483 this.auto_highlight();
483 this.auto_highlight();
484 };
484 };
485
485
486 /**
486 /**
487 * Try to autodetect cell highlight mode, or use selected mode
487 * Try to autodetect cell highlight mode, or use selected mode
488 * @methods _auto_highlight
488 * @methods _auto_highlight
489 * @private
489 * @private
490 * @param {String|object|undefined} - CodeMirror mode | 'auto'
490 * @param {String|object|undefined} - CodeMirror mode | 'auto'
491 **/
491 **/
492 Cell.prototype._auto_highlight = function (modes) {
492 Cell.prototype._auto_highlight = function (modes) {
493 //Here we handle manually selected modes
493 //Here we handle manually selected modes
494 var mode;
494 var mode;
495 if( this.user_highlight !== undefined && this.user_highlight != 'auto' )
495 if( this.user_highlight !== undefined && this.user_highlight != 'auto' )
496 {
496 {
497 mode = this.user_highlight;
497 mode = this.user_highlight;
498 CodeMirror.autoLoadMode(this.code_mirror, mode);
498 CodeMirror.autoLoadMode(this.code_mirror, mode);
499 this.code_mirror.setOption('mode', mode);
499 this.code_mirror.setOption('mode', mode);
500 return;
500 return;
501 }
501 }
502 var current_mode = this.code_mirror.getOption('mode', mode);
502 var current_mode = this.code_mirror.getOption('mode', mode);
503 var first_line = this.code_mirror.getLine(0);
503 var first_line = this.code_mirror.getLine(0);
504 // loop on every pairs
504 // loop on every pairs
505 for(mode in modes) {
505 for(mode in modes) {
506 var regs = modes[mode].reg;
506 var regs = modes[mode].reg;
507 // only one key every time but regexp can't be keys...
507 // only one key every time but regexp can't be keys...
508 for(var i=0; i<regs.length; i++) {
508 for(var i=0; i<regs.length; i++) {
509 // here we handle non magic_modes
509 // here we handle non magic_modes
510 if(first_line.match(regs[i]) !== null) {
510 if(first_line.match(regs[i]) !== null) {
511 if(current_mode == mode){
511 if(current_mode == mode){
512 return;
512 return;
513 }
513 }
514 if (mode.search('magic_') !== 0) {
514 if (mode.search('magic_') !== 0) {
515 this.code_mirror.setOption('mode', mode);
515 this.code_mirror.setOption('mode', mode);
516 CodeMirror.autoLoadMode(this.code_mirror, mode);
516 CodeMirror.autoLoadMode(this.code_mirror, mode);
517 return;
517 return;
518 }
518 }
519 var open = modes[mode].open || "%%";
519 var open = modes[mode].open || "%%";
520 var close = modes[mode].close || "%%end";
520 var close = modes[mode].close || "%%end";
521 var mmode = mode;
521 var mmode = mode;
522 mode = mmode.substr(6);
522 mode = mmode.substr(6);
523 if(current_mode == mode){
523 if(current_mode == mode){
524 return;
524 return;
525 }
525 }
526 CodeMirror.autoLoadMode(this.code_mirror, mode);
526 CodeMirror.autoLoadMode(this.code_mirror, mode);
527 // create on the fly a mode that swhitch between
527 // create on the fly a mode that swhitch between
528 // plain/text and smth else otherwise `%%` is
528 // plain/text and smth else otherwise `%%` is
529 // source of some highlight issues.
529 // source of some highlight issues.
530 // we use patchedGetMode to circumvent a bug in CM
530 // we use patchedGetMode to circumvent a bug in CM
531 CodeMirror.defineMode(mmode , function(config) {
531 CodeMirror.defineMode(mmode , function(config) {
532 return CodeMirror.multiplexingMode(
532 return CodeMirror.multiplexingMode(
533 CodeMirror.patchedGetMode(config, 'text/plain'),
533 CodeMirror.patchedGetMode(config, 'text/plain'),
534 // always set someting on close
534 // always set someting on close
535 {open: open, close: close,
535 {open: open, close: close,
536 mode: CodeMirror.patchedGetMode(config, mode),
536 mode: CodeMirror.patchedGetMode(config, mode),
537 delimStyle: "delimit"
537 delimStyle: "delimit"
538 }
538 }
539 );
539 );
540 });
540 });
541 this.code_mirror.setOption('mode', mmode);
541 this.code_mirror.setOption('mode', mmode);
542 return;
542 return;
543 }
543 }
544 }
544 }
545 }
545 }
546 // fallback on default
546 // fallback on default
547 var default_mode;
547 var default_mode;
548 try {
548 try {
549 default_mode = this._options.cm_config.mode;
549 default_mode = this._options.cm_config.mode;
550 } catch(e) {
550 } catch(e) {
551 default_mode = 'text/plain';
551 default_mode = 'text/plain';
552 }
552 }
553 if( current_mode === default_mode){
553 if( current_mode === default_mode){
554 return;
554 return;
555 }
555 }
556 this.code_mirror.setOption('mode', default_mode);
556 this.code_mirror.setOption('mode', default_mode);
557 };
557 };
558
558
559 // Backwards compatability.
559 // Backwards compatability.
560 IPython.Cell = Cell;
560 IPython.Cell = Cell;
561
561
562 return Cell;
562 return {'Cell': Cell};
563
563
564 });
564 });
@@ -1,415 +1,415 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 define([
4 define([
5 'base/js/namespace',
5 'base/js/namespace',
6 'jquery',
6 'jquery',
7 'notebook/js/textcell',
7 'notebook/js/textcell',
8 ], function(IPython, $, TextCell) {
8 ], function(IPython, $, TextCell) {
9 "use strict";
9 "use strict";
10
10
11 /**
11 /**
12 * @constructor
12 * @constructor
13 * @class CellToolbar
13 * @class CellToolbar
14 * @param {The cell to attach the metadata UI to} cell
14 * @param {The cell to attach the metadata UI to} cell
15 */
15 */
16 var CellToolbar = function (cell, events, notebook) {
16 var CellToolbar = function (cell, events, notebook) {
17 CellToolbar._instances.push(this);
17 CellToolbar._instances.push(this);
18 this.notebook = notebook;
18 this.notebook = notebook;
19 this.events = events;
19 this.events = events;
20 this.cell = cell;
20 this.cell = cell;
21 this.create_element();
21 this.create_element();
22 this.rebuild();
22 this.rebuild();
23 return this;
23 return this;
24 };
24 };
25
25
26
26
27 CellToolbar.prototype.create_element = function () {
27 CellToolbar.prototype.create_element = function () {
28 this.inner_element = $('<div/>').addClass('celltoolbar');
28 this.inner_element = $('<div/>').addClass('celltoolbar');
29 this.element = $('<div/>').addClass('ctb_hideshow')
29 this.element = $('<div/>').addClass('ctb_hideshow')
30 .append(this.inner_element);
30 .append(this.inner_element);
31 };
31 };
32
32
33
33
34 // The default css style for the outer celltoolbar div
34 // The default css style for the outer celltoolbar div
35 // (ctb_hideshow) is display: none.
35 // (ctb_hideshow) is display: none.
36 // To show the cell toolbar, *both* of the following conditions must be met:
36 // To show the cell toolbar, *both* of the following conditions must be met:
37 // - A parent container has class `ctb_global_show`
37 // - A parent container has class `ctb_global_show`
38 // - The celltoolbar has the class `ctb_show`
38 // - The celltoolbar has the class `ctb_show`
39 // This allows global show/hide, as well as per-cell show/hide.
39 // This allows global show/hide, as well as per-cell show/hide.
40
40
41 CellToolbar.global_hide = function () {
41 CellToolbar.global_hide = function () {
42 $('body').removeClass('ctb_global_show');
42 $('body').removeClass('ctb_global_show');
43 };
43 };
44
44
45
45
46 CellToolbar.global_show = function () {
46 CellToolbar.global_show = function () {
47 $('body').addClass('ctb_global_show');
47 $('body').addClass('ctb_global_show');
48 };
48 };
49
49
50
50
51 CellToolbar.prototype.hide = function () {
51 CellToolbar.prototype.hide = function () {
52 this.element.removeClass('ctb_show');
52 this.element.removeClass('ctb_show');
53 };
53 };
54
54
55
55
56 CellToolbar.prototype.show = function () {
56 CellToolbar.prototype.show = function () {
57 this.element.addClass('ctb_show');
57 this.element.addClass('ctb_show');
58 };
58 };
59
59
60
60
61 /**
61 /**
62 * Class variable that should contain a dict of all available callback
62 * Class variable that should contain a dict of all available callback
63 * we need to think of wether or not we allow nested namespace
63 * we need to think of wether or not we allow nested namespace
64 * @property _callback_dict
64 * @property _callback_dict
65 * @private
65 * @private
66 * @static
66 * @static
67 * @type Dict
67 * @type Dict
68 */
68 */
69 CellToolbar._callback_dict = {};
69 CellToolbar._callback_dict = {};
70
70
71
71
72 /**
72 /**
73 * Class variable that should contain the reverse order list of the button
73 * Class variable that should contain the reverse order list of the button
74 * to add to the toolbar of each cell
74 * to add to the toolbar of each cell
75 * @property _ui_controls_list
75 * @property _ui_controls_list
76 * @private
76 * @private
77 * @static
77 * @static
78 * @type List
78 * @type List
79 */
79 */
80 CellToolbar._ui_controls_list = [];
80 CellToolbar._ui_controls_list = [];
81
81
82
82
83 /**
83 /**
84 * Class variable that should contain the CellToolbar instances for each
84 * Class variable that should contain the CellToolbar instances for each
85 * cell of the notebook
85 * cell of the notebook
86 *
86 *
87 * @private
87 * @private
88 * @property _instances
88 * @property _instances
89 * @static
89 * @static
90 * @type List
90 * @type List
91 */
91 */
92 CellToolbar._instances = [];
92 CellToolbar._instances = [];
93
93
94
94
95 /**
95 /**
96 * keep a list of all the available presets for the toolbar
96 * keep a list of all the available presets for the toolbar
97 * @private
97 * @private
98 * @property _presets
98 * @property _presets
99 * @static
99 * @static
100 * @type Dict
100 * @type Dict
101 */
101 */
102 CellToolbar._presets = {};
102 CellToolbar._presets = {};
103
103
104
104
105 // this is by design not a prototype.
105 // this is by design not a prototype.
106 /**
106 /**
107 * Register a callback to create an UI element in a cell toolbar.
107 * Register a callback to create an UI element in a cell toolbar.
108 * @method register_callback
108 * @method register_callback
109 * @param name {String} name to use to refer to the callback. It is advised to use a prefix with the name
109 * @param name {String} name to use to refer to the callback. It is advised to use a prefix with the name
110 * for easier sorting and avoid collision
110 * for easier sorting and avoid collision
111 * @param callback {function(div, cell)} callback that will be called to generate the ui element
111 * @param callback {function(div, cell)} callback that will be called to generate the ui element
112 * @param [cell_types] {List of String|undefined} optional list of cell types. If present the UI element
112 * @param [cell_types] {List of String|undefined} optional list of cell types. If present the UI element
113 * will be added only to cells of types in the list.
113 * will be added only to cells of types in the list.
114 *
114 *
115 *
115 *
116 * The callback will receive the following element :
116 * The callback will receive the following element :
117 *
117 *
118 * * a div in which to add element.
118 * * a div in which to add element.
119 * * the cell it is responsible from
119 * * the cell it is responsible from
120 *
120 *
121 * @example
121 * @example
122 *
122 *
123 * Example that create callback for a button that toggle between `true` and `false` label,
123 * Example that create callback for a button that toggle between `true` and `false` label,
124 * with the metadata under the key 'foo' to reflect the status of the button.
124 * with the metadata under the key 'foo' to reflect the status of the button.
125 *
125 *
126 * // first param reference to a DOM div
126 * // first param reference to a DOM div
127 * // second param reference to the cell.
127 * // second param reference to the cell.
128 * var toggle = function(div, cell) {
128 * var toggle = function(div, cell) {
129 * var button_container = $(div)
129 * var button_container = $(div)
130 *
130 *
131 * // let's create a button that show the current value of the metadata
131 * // let's create a button that show the current value of the metadata
132 * var button = $('<div/>').button({label:String(cell.metadata.foo)});
132 * var button = $('<div/>').button({label:String(cell.metadata.foo)});
133 *
133 *
134 * // On click, change the metadata value and update the button label
134 * // On click, change the metadata value and update the button label
135 * button.click(function(){
135 * button.click(function(){
136 * var v = cell.metadata.foo;
136 * var v = cell.metadata.foo;
137 * cell.metadata.foo = !v;
137 * cell.metadata.foo = !v;
138 * button.button("option", "label", String(!v));
138 * button.button("option", "label", String(!v));
139 * })
139 * })
140 *
140 *
141 * // add the button to the DOM div.
141 * // add the button to the DOM div.
142 * button_container.append(button);
142 * button_container.append(button);
143 * }
143 * }
144 *
144 *
145 * // now we register the callback under the name `foo` to give the
145 * // now we register the callback under the name `foo` to give the
146 * // user the ability to use it later
146 * // user the ability to use it later
147 * CellToolbar.register_callback('foo', toggle);
147 * CellToolbar.register_callback('foo', toggle);
148 */
148 */
149 CellToolbar.register_callback = function(name, callback, cell_types) {
149 CellToolbar.register_callback = function(name, callback, cell_types) {
150 // Overwrite if it already exists.
150 // Overwrite if it already exists.
151 CellToolbar._callback_dict[name] = cell_types ? {callback: callback, cell_types: cell_types} : callback;
151 CellToolbar._callback_dict[name] = cell_types ? {callback: callback, cell_types: cell_types} : callback;
152 };
152 };
153
153
154
154
155 /**
155 /**
156 * Register a preset of UI element in a cell toolbar.
156 * Register a preset of UI element in a cell toolbar.
157 * Not supported Yet.
157 * Not supported Yet.
158 * @method register_preset
158 * @method register_preset
159 * @param name {String} name to use to refer to the preset. It is advised to use a prefix with the name
159 * @param name {String} name to use to refer to the preset. It is advised to use a prefix with the name
160 * for easier sorting and avoid collision
160 * for easier sorting and avoid collision
161 * @param preset_list {List of String} reverse order of the button in the toolbar. Each String of the list
161 * @param preset_list {List of String} reverse order of the button in the toolbar. Each String of the list
162 * should correspond to a name of a registerd callback.
162 * should correspond to a name of a registerd callback.
163 *
163 *
164 * @private
164 * @private
165 * @example
165 * @example
166 *
166 *
167 * CellToolbar.register_callback('foo.c1', function(div, cell){...});
167 * CellToolbar.register_callback('foo.c1', function(div, cell){...});
168 * CellToolbar.register_callback('foo.c2', function(div, cell){...});
168 * CellToolbar.register_callback('foo.c2', function(div, cell){...});
169 * CellToolbar.register_callback('foo.c3', function(div, cell){...});
169 * CellToolbar.register_callback('foo.c3', function(div, cell){...});
170 * CellToolbar.register_callback('foo.c4', function(div, cell){...});
170 * CellToolbar.register_callback('foo.c4', function(div, cell){...});
171 * CellToolbar.register_callback('foo.c5', function(div, cell){...});
171 * CellToolbar.register_callback('foo.c5', function(div, cell){...});
172 *
172 *
173 * CellToolbar.register_preset('foo.foo_preset1', ['foo.c1', 'foo.c2', 'foo.c5'])
173 * CellToolbar.register_preset('foo.foo_preset1', ['foo.c1', 'foo.c2', 'foo.c5'])
174 * CellToolbar.register_preset('foo.foo_preset2', ['foo.c4', 'foo.c5'])
174 * CellToolbar.register_preset('foo.foo_preset2', ['foo.c4', 'foo.c5'])
175 */
175 */
176 CellToolbar.register_preset = function(name, preset_list) {
176 CellToolbar.register_preset = function(name, preset_list) {
177 CellToolbar._presets[name] = preset_list;
177 CellToolbar._presets[name] = preset_list;
178 this.events.trigger('preset_added.CellToolbar', {name: name});
178 this.events.trigger('preset_added.CellToolbar', {name: name});
179 // When "register_callback" is called by a custom extension, it may be executed after notebook is loaded.
179 // When "register_callback" is called by a custom extension, it may be executed after notebook is loaded.
180 // In that case, activate the preset if needed.
180 // In that case, activate the preset if needed.
181 if (this.notebook && this.notebook.metadata && this.notebook.metadata.celltoolbar === name)
181 if (this.notebook && this.notebook.metadata && this.notebook.metadata.celltoolbar === name)
182 this.activate_preset(name);
182 this.activate_preset(name);
183 };
183 };
184
184
185
185
186 /**
186 /**
187 * List the names of the presets that are currently registered.
187 * List the names of the presets that are currently registered.
188 *
188 *
189 * @method list_presets
189 * @method list_presets
190 * @static
190 * @static
191 */
191 */
192 CellToolbar.list_presets = function() {
192 CellToolbar.list_presets = function() {
193 var keys = [];
193 var keys = [];
194 for (var k in CellToolbar._presets) {
194 for (var k in CellToolbar._presets) {
195 keys.push(k);
195 keys.push(k);
196 }
196 }
197 return keys;
197 return keys;
198 };
198 };
199
199
200
200
201 /**
201 /**
202 * Activate an UI preset from `register_preset`
202 * Activate an UI preset from `register_preset`
203 *
203 *
204 * This does not update the selection UI.
204 * This does not update the selection UI.
205 *
205 *
206 * @method activate_preset
206 * @method activate_preset
207 * @param preset_name {String} string corresponding to the preset name
207 * @param preset_name {String} string corresponding to the preset name
208 *
208 *
209 * @static
209 * @static
210 * @private
210 * @private
211 * @example
211 * @example
212 *
212 *
213 * CellToolbar.activate_preset('foo.foo_preset1');
213 * CellToolbar.activate_preset('foo.foo_preset1');
214 */
214 */
215 CellToolbar.activate_preset = function(preset_name){
215 CellToolbar.activate_preset = function(preset_name){
216 var preset = CellToolbar._presets[preset_name];
216 var preset = CellToolbar._presets[preset_name];
217
217
218 if(preset !== undefined){
218 if(preset !== undefined){
219 CellToolbar._ui_controls_list = preset;
219 CellToolbar._ui_controls_list = preset;
220 CellToolbar.rebuild_all();
220 CellToolbar.rebuild_all();
221 }
221 }
222
222
223 if (this.events) {
223 if (this.events) {
224 this.events.trigger('preset_activated.CellToolbar', {name: preset_name});
224 this.events.trigger('preset_activated.CellToolbar', {name: preset_name});
225 }
225 }
226 };
226 };
227
227
228
228
229 /**
229 /**
230 * This should be called on the class and not on a instance as it will trigger
230 * This should be called on the class and not on a instance as it will trigger
231 * rebuild of all the instances.
231 * rebuild of all the instances.
232 * @method rebuild_all
232 * @method rebuild_all
233 * @static
233 * @static
234 *
234 *
235 */
235 */
236 CellToolbar.rebuild_all = function(){
236 CellToolbar.rebuild_all = function(){
237 for(var i=0; i < CellToolbar._instances.length; i++){
237 for(var i=0; i < CellToolbar._instances.length; i++){
238 CellToolbar._instances[i].rebuild();
238 CellToolbar._instances[i].rebuild();
239 }
239 }
240 };
240 };
241
241
242 /**
242 /**
243 * Rebuild all the button on the toolbar to update its state.
243 * Rebuild all the button on the toolbar to update its state.
244 * @method rebuild
244 * @method rebuild
245 */
245 */
246 CellToolbar.prototype.rebuild = function(){
246 CellToolbar.prototype.rebuild = function(){
247 // strip evrything from the div
247 // strip evrything from the div
248 // which is probably inner_element
248 // which is probably inner_element
249 // or this.element.
249 // or this.element.
250 this.inner_element.empty();
250 this.inner_element.empty();
251 this.ui_controls_list = [];
251 this.ui_controls_list = [];
252
252
253 var callbacks = CellToolbar._callback_dict;
253 var callbacks = CellToolbar._callback_dict;
254 var preset = CellToolbar._ui_controls_list;
254 var preset = CellToolbar._ui_controls_list;
255 // Yes we iterate on the class variable, not the instance one.
255 // Yes we iterate on the class variable, not the instance one.
256 for (var i=0; i < preset.length; i++) {
256 for (var i=0; i < preset.length; i++) {
257 var key = preset[i];
257 var key = preset[i];
258 var callback = callbacks[key];
258 var callback = callbacks[key];
259 if (!callback) continue;
259 if (!callback) continue;
260
260
261 if (typeof callback === 'object') {
261 if (typeof callback === 'object') {
262 if (callback.cell_types.indexOf(this.cell.cell_type) === -1) continue;
262 if (callback.cell_types.indexOf(this.cell.cell_type) === -1) continue;
263 callback = callback.callback;
263 callback = callback.callback;
264 }
264 }
265
265
266 var local_div = $('<div/>').addClass('button_container');
266 var local_div = $('<div/>').addClass('button_container');
267 try {
267 try {
268 callback(local_div, this.cell, this);
268 callback(local_div, this.cell, this);
269 this.ui_controls_list.push(key);
269 this.ui_controls_list.push(key);
270 } catch (e) {
270 } catch (e) {
271 console.log("Error in cell toolbar callback " + key, e);
271 console.log("Error in cell toolbar callback " + key, e);
272 continue;
272 continue;
273 }
273 }
274 // only append if callback succeeded.
274 // only append if callback succeeded.
275 this.inner_element.append(local_div);
275 this.inner_element.append(local_div);
276 }
276 }
277
277
278 // If there are no controls or the cell is a rendered TextCell hide the toolbar.
278 // If there are no controls or the cell is a rendered TextCell hide the toolbar.
279 if (!this.ui_controls_list.length || (this.cell instanceof TextCell && this.cell.rendered)) {
279 if (!this.ui_controls_list.length || (this.cell instanceof TextCell && this.cell.rendered)) {
280 this.hide();
280 this.hide();
281 } else {
281 } else {
282 this.show();
282 this.show();
283 }
283 }
284 };
284 };
285
285
286
286
287 /**
287 /**
288 */
288 */
289 CellToolbar.utils = {};
289 CellToolbar.utils = {};
290
290
291
291
292 /**
292 /**
293 * A utility function to generate bindings between a checkbox and cell/metadata
293 * A utility function to generate bindings between a checkbox and cell/metadata
294 * @method utils.checkbox_ui_generator
294 * @method utils.checkbox_ui_generator
295 * @static
295 * @static
296 *
296 *
297 * @param name {string} Label in front of the checkbox
297 * @param name {string} Label in front of the checkbox
298 * @param setter {function( cell, newValue )}
298 * @param setter {function( cell, newValue )}
299 * A setter method to set the newValue
299 * A setter method to set the newValue
300 * @param getter {function( cell )}
300 * @param getter {function( cell )}
301 * A getter methods which return the current value.
301 * A getter methods which return the current value.
302 *
302 *
303 * @return callback {function( div, cell )} Callback to be passed to `register_callback`
303 * @return callback {function( div, cell )} Callback to be passed to `register_callback`
304 *
304 *
305 * @example
305 * @example
306 *
306 *
307 * An exmple that bind the subkey `slideshow.isSectionStart` to a checkbox with a `New Slide` label
307 * An exmple that bind the subkey `slideshow.isSectionStart` to a checkbox with a `New Slide` label
308 *
308 *
309 * var newSlide = CellToolbar.utils.checkbox_ui_generator('New Slide',
309 * var newSlide = CellToolbar.utils.checkbox_ui_generator('New Slide',
310 * // setter
310 * // setter
311 * function(cell, value){
311 * function(cell, value){
312 * // we check that the slideshow namespace exist and create it if needed
312 * // we check that the slideshow namespace exist and create it if needed
313 * if (cell.metadata.slideshow == undefined){cell.metadata.slideshow = {}}
313 * if (cell.metadata.slideshow == undefined){cell.metadata.slideshow = {}}
314 * // set the value
314 * // set the value
315 * cell.metadata.slideshow.isSectionStart = value
315 * cell.metadata.slideshow.isSectionStart = value
316 * },
316 * },
317 * //geter
317 * //geter
318 * function(cell){ var ns = cell.metadata.slideshow;
318 * function(cell){ var ns = cell.metadata.slideshow;
319 * // if the slideshow namespace does not exist return `undefined`
319 * // if the slideshow namespace does not exist return `undefined`
320 * // (will be interpreted as `false` by checkbox) otherwise
320 * // (will be interpreted as `false` by checkbox) otherwise
321 * // return the value
321 * // return the value
322 * return (ns == undefined)? undefined: ns.isSectionStart
322 * return (ns == undefined)? undefined: ns.isSectionStart
323 * }
323 * }
324 * );
324 * );
325 *
325 *
326 * CellToolbar.register_callback('newSlide', newSlide);
326 * CellToolbar.register_callback('newSlide', newSlide);
327 *
327 *
328 */
328 */
329 CellToolbar.utils.checkbox_ui_generator = function(name, setter, getter){
329 CellToolbar.utils.checkbox_ui_generator = function(name, setter, getter){
330 return function(div, cell, celltoolbar) {
330 return function(div, cell, celltoolbar) {
331 var button_container = $(div);
331 var button_container = $(div);
332
332
333 var chkb = $('<input/>').attr('type', 'checkbox');
333 var chkb = $('<input/>').attr('type', 'checkbox');
334 var lbl = $('<label/>').append($('<span/>').text(name));
334 var lbl = $('<label/>').append($('<span/>').text(name));
335 lbl.append(chkb);
335 lbl.append(chkb);
336 chkb.attr("checked", getter(cell));
336 chkb.attr("checked", getter(cell));
337
337
338 chkb.click(function(){
338 chkb.click(function(){
339 var v = getter(cell);
339 var v = getter(cell);
340 setter(cell, !v);
340 setter(cell, !v);
341 chkb.attr("checked", !v);
341 chkb.attr("checked", !v);
342 });
342 });
343 button_container.append($('<div/>').append(lbl));
343 button_container.append($('<div/>').append(lbl));
344 };
344 };
345 };
345 };
346
346
347
347
348 /**
348 /**
349 * A utility function to generate bindings between a dropdown list cell
349 * A utility function to generate bindings between a dropdown list cell
350 * @method utils.select_ui_generator
350 * @method utils.select_ui_generator
351 * @static
351 * @static
352 *
352 *
353 * @param list_list {list of sublist} List of sublist of metadata value and name in the dropdown list.
353 * @param list_list {list of sublist} List of sublist of metadata value and name in the dropdown list.
354 * subslit shoud contain 2 element each, first a string that woul be displayed in the dropdown list,
354 * subslit shoud contain 2 element each, first a string that woul be displayed in the dropdown list,
355 * and second the corresponding value to be passed to setter/return by getter. the corresponding value
355 * and second the corresponding value to be passed to setter/return by getter. the corresponding value
356 * should not be "undefined" or behavior can be unexpected.
356 * should not be "undefined" or behavior can be unexpected.
357 * @param setter {function( cell, newValue )}
357 * @param setter {function( cell, newValue )}
358 * A setter method to set the newValue
358 * A setter method to set the newValue
359 * @param getter {function( cell )}
359 * @param getter {function( cell )}
360 * A getter methods which return the current value of the metadata.
360 * A getter methods which return the current value of the metadata.
361 * @param [label=""] {String} optionnal label for the dropdown menu
361 * @param [label=""] {String} optionnal label for the dropdown menu
362 *
362 *
363 * @return callback {function( div, cell )} Callback to be passed to `register_callback`
363 * @return callback {function( div, cell )} Callback to be passed to `register_callback`
364 *
364 *
365 * @example
365 * @example
366 *
366 *
367 * var select_type = CellToolbar.utils.select_ui_generator([
367 * var select_type = CellToolbar.utils.select_ui_generator([
368 * ["<None>" , "None" ],
368 * ["<None>" , "None" ],
369 * ["Header Slide" , "header_slide" ],
369 * ["Header Slide" , "header_slide" ],
370 * ["Slide" , "slide" ],
370 * ["Slide" , "slide" ],
371 * ["Fragment" , "fragment" ],
371 * ["Fragment" , "fragment" ],
372 * ["Skip" , "skip" ],
372 * ["Skip" , "skip" ],
373 * ],
373 * ],
374 * // setter
374 * // setter
375 * function(cell, value){
375 * function(cell, value){
376 * // we check that the slideshow namespace exist and create it if needed
376 * // we check that the slideshow namespace exist and create it if needed
377 * if (cell.metadata.slideshow == undefined){cell.metadata.slideshow = {}}
377 * if (cell.metadata.slideshow == undefined){cell.metadata.slideshow = {}}
378 * // set the value
378 * // set the value
379 * cell.metadata.slideshow.slide_type = value
379 * cell.metadata.slideshow.slide_type = value
380 * },
380 * },
381 * //geter
381 * //geter
382 * function(cell){ var ns = cell.metadata.slideshow;
382 * function(cell){ var ns = cell.metadata.slideshow;
383 * // if the slideshow namespace does not exist return `undefined`
383 * // if the slideshow namespace does not exist return `undefined`
384 * // (will be interpreted as `false` by checkbox) otherwise
384 * // (will be interpreted as `false` by checkbox) otherwise
385 * // return the value
385 * // return the value
386 * return (ns == undefined)? undefined: ns.slide_type
386 * return (ns == undefined)? undefined: ns.slide_type
387 * }
387 * }
388 * CellToolbar.register_callback('slideshow.select', select_type);
388 * CellToolbar.register_callback('slideshow.select', select_type);
389 *
389 *
390 */
390 */
391 CellToolbar.utils.select_ui_generator = function(list_list, setter, getter, label) {
391 CellToolbar.utils.select_ui_generator = function(list_list, setter, getter, label) {
392 label = label || "";
392 label = label || "";
393 return function(div, cell, celltoolbar) {
393 return function(div, cell, celltoolbar) {
394 var button_container = $(div);
394 var button_container = $(div);
395 var lbl = $("<label/>").append($('<span/>').text(label));
395 var lbl = $("<label/>").append($('<span/>').text(label));
396 var select = $('<select/>').addClass('ui-widget ui-widget-content');
396 var select = $('<select/>').addClass('ui-widget ui-widget-content');
397 for(var i=0; i < list_list.length; i++){
397 for(var i=0; i < list_list.length; i++){
398 var opt = $('<option/>')
398 var opt = $('<option/>')
399 .attr('value', list_list[i][1])
399 .attr('value', list_list[i][1])
400 .text(list_list[i][0]);
400 .text(list_list[i][0]);
401 select.append(opt);
401 select.append(opt);
402 }
402 }
403 select.val(getter(cell));
403 select.val(getter(cell));
404 select.change(function(){
404 select.change(function(){
405 setter(cell, select.val());
405 setter(cell, select.val());
406 });
406 });
407 button_container.append($('<div/>').append(lbl).append(select));
407 button_container.append($('<div/>').append(lbl).append(select));
408 };
408 };
409 };
409 };
410
410
411 // Backwards compatability.
411 // Backwards compatability.
412 IPython.CellToolbar = CellToolbar;
412 IPython.CellToolbar = CellToolbar;
413
413
414 return CellToolbar;
414 return {'CellToolbar': CellToolbar};
415 });
415 });
@@ -1,515 +1,515 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 define([
4 define([
5 'base/js/namespace',
5 'base/js/namespace',
6 'jquery',
6 'jquery',
7 'base/js/utils',
7 'base/js/utils',
8 'notebook/js/tooltip',
8 'notebook/js/tooltip',
9 'base/js/keyboard',
9 'base/js/keyboard',
10 'notebook/js/cell',
10 'notebook/js/cell',
11 'notebook/js/outputarea',
11 'notebook/js/outputarea',
12 'notebook/js/completer',
12 'notebook/js/completer',
13 'notebook/js/celltoolbar',
13 'notebook/js/celltoolbar',
14 ], function(IPython, $, utils, Tooltip, keyboard, Cell, OutputArea, Completer, CellToolbar) {
14 ], function(IPython, $, utils, Tooltip, keyboard, Cell, OutputArea, Completer, CellToolbar) {
15 "use strict";
15 "use strict";
16
16
17 /* local util for codemirror */
17 /* local util for codemirror */
18 var posEq = function(a, b) {return a.line == b.line && a.ch == b.ch;};
18 var posEq = function(a, b) {return a.line == b.line && a.ch == b.ch;};
19
19
20 /**
20 /**
21 *
21 *
22 * function to delete until previous non blanking space character
22 * function to delete until previous non blanking space character
23 * or first multiple of 4 tabstop.
23 * or first multiple of 4 tabstop.
24 * @private
24 * @private
25 */
25 */
26 CodeMirror.commands.delSpaceToPrevTabStop = function(cm){
26 CodeMirror.commands.delSpaceToPrevTabStop = function(cm){
27 var from = cm.getCursor(true), to = cm.getCursor(false), sel = !posEq(from, to);
27 var from = cm.getCursor(true), to = cm.getCursor(false), sel = !posEq(from, to);
28 if (!posEq(from, to)) { cm.replaceRange("", from, to); return; }
28 if (!posEq(from, to)) { cm.replaceRange("", from, to); return; }
29 var cur = cm.getCursor(), line = cm.getLine(cur.line);
29 var cur = cm.getCursor(), line = cm.getLine(cur.line);
30 var tabsize = cm.getOption('tabSize');
30 var tabsize = cm.getOption('tabSize');
31 var chToPrevTabStop = cur.ch-(Math.ceil(cur.ch/tabsize)-1)*tabsize;
31 var chToPrevTabStop = cur.ch-(Math.ceil(cur.ch/tabsize)-1)*tabsize;
32 from = {ch:cur.ch-chToPrevTabStop,line:cur.line};
32 from = {ch:cur.ch-chToPrevTabStop,line:cur.line};
33 var select = cm.getRange(from,cur);
33 var select = cm.getRange(from,cur);
34 if( select.match(/^\ +$/) !== null){
34 if( select.match(/^\ +$/) !== null){
35 cm.replaceRange("",from,cur);
35 cm.replaceRange("",from,cur);
36 } else {
36 } else {
37 cm.deleteH(-1,"char");
37 cm.deleteH(-1,"char");
38 }
38 }
39 };
39 };
40
40
41 var keycodes = keyboard.keycodes;
41 var keycodes = keyboard.keycodes;
42
42
43 /**
43 /**
44 * A Cell conceived to write code.
44 * A Cell conceived to write code.
45 *
45 *
46 * The kernel doesn't have to be set at creation time, in that case
46 * The kernel doesn't have to be set at creation time, in that case
47 * it will be null and set_kernel has to be called later.
47 * it will be null and set_kernel has to be called later.
48 * @class CodeCell
48 * @class CodeCell
49 * @extends Cell
49 * @extends Cell
50 *
50 *
51 * @constructor
51 * @constructor
52 * @param {Object|null} kernel
52 * @param {Object|null} kernel
53 * @param {object|undefined} [options]
53 * @param {object|undefined} [options]
54 * @param [options.cm_config] {object} config to pass to CodeMirror
54 * @param [options.cm_config] {object} config to pass to CodeMirror
55 */
55 */
56 var CodeCell = function (kernel, options, events, config, keyboard_manager, notebook) {
56 var CodeCell = function (kernel, options, events, config, keyboard_manager, notebook) {
57 this.kernel = kernel || null;
57 this.kernel = kernel || null;
58 this.notebook = notebook;
58 this.notebook = notebook;
59 this.collapsed = false;
59 this.collapsed = false;
60 this.tooltip = new Tooltip(events);
60 this.tooltip = new Tooltip(events);
61 this.events = events;
61 this.events = events;
62 this.config = config;
62 this.config = config;
63
63
64 // create all attributed in constructor function
64 // create all attributed in constructor function
65 // even if null for V8 VM optimisation
65 // even if null for V8 VM optimisation
66 this.input_prompt_number = null;
66 this.input_prompt_number = null;
67 this.celltoolbar = null;
67 this.celltoolbar = null;
68 this.output_area = null;
68 this.output_area = null;
69 this.last_msg_id = null;
69 this.last_msg_id = null;
70 this.completer = null;
70 this.completer = null;
71
71
72
72
73 var cm_overwrite_options = {
73 var cm_overwrite_options = {
74 onKeyEvent: $.proxy(this.handle_keyevent,this)
74 onKeyEvent: $.proxy(this.handle_keyevent,this)
75 };
75 };
76
76
77 options = this.mergeopt(CodeCell, options, {cm_config:cm_overwrite_options});
77 options = this.mergeopt(CodeCell, options, {cm_config:cm_overwrite_options});
78
78
79 Cell.apply(this,[options, keyboard_manager, events]);
79 Cell.apply(this,[options, keyboard_manager, events]);
80
80
81 // Attributes we want to override in this subclass.
81 // Attributes we want to override in this subclass.
82 this.cell_type = "code";
82 this.cell_type = "code";
83
83
84 var that = this;
84 var that = this;
85 this.element.focusout(
85 this.element.focusout(
86 function() { that.auto_highlight(); }
86 function() { that.auto_highlight(); }
87 );
87 );
88 };
88 };
89
89
90 CodeCell.options_default = {
90 CodeCell.options_default = {
91 cm_config : {
91 cm_config : {
92 extraKeys: {
92 extraKeys: {
93 "Tab" : "indentMore",
93 "Tab" : "indentMore",
94 "Shift-Tab" : "indentLess",
94 "Shift-Tab" : "indentLess",
95 "Backspace" : "delSpaceToPrevTabStop",
95 "Backspace" : "delSpaceToPrevTabStop",
96 "Cmd-/" : "toggleComment",
96 "Cmd-/" : "toggleComment",
97 "Ctrl-/" : "toggleComment"
97 "Ctrl-/" : "toggleComment"
98 },
98 },
99 mode: 'ipython',
99 mode: 'ipython',
100 theme: 'ipython',
100 theme: 'ipython',
101 matchBrackets: true,
101 matchBrackets: true,
102 // don't auto-close strings because of CodeMirror #2385
102 // don't auto-close strings because of CodeMirror #2385
103 autoCloseBrackets: "()[]{}"
103 autoCloseBrackets: "()[]{}"
104 }
104 }
105 };
105 };
106
106
107 CodeCell.msg_cells = {};
107 CodeCell.msg_cells = {};
108
108
109 CodeCell.prototype = new Cell();
109 CodeCell.prototype = new Cell();
110
110
111 /**
111 /**
112 * @method auto_highlight
112 * @method auto_highlight
113 */
113 */
114 CodeCell.prototype.auto_highlight = function () {
114 CodeCell.prototype.auto_highlight = function () {
115 this._auto_highlight(this.config.cell_magic_highlight);
115 this._auto_highlight(this.config.cell_magic_highlight);
116 };
116 };
117
117
118 /** @method create_element */
118 /** @method create_element */
119 CodeCell.prototype.create_element = function () {
119 CodeCell.prototype.create_element = function () {
120 Cell.prototype.create_element.apply(this, arguments);
120 Cell.prototype.create_element.apply(this, arguments);
121
121
122 var cell = $('<div></div>').addClass('cell border-box-sizing code_cell');
122 var cell = $('<div></div>').addClass('cell border-box-sizing code_cell');
123 cell.attr('tabindex','2');
123 cell.attr('tabindex','2');
124
124
125 var input = $('<div></div>').addClass('input');
125 var input = $('<div></div>').addClass('input');
126 var prompt = $('<div/>').addClass('prompt input_prompt');
126 var prompt = $('<div/>').addClass('prompt input_prompt');
127 var inner_cell = $('<div/>').addClass('inner_cell');
127 var inner_cell = $('<div/>').addClass('inner_cell');
128 this.celltoolbar = new CellToolbar(this, this.events, this.notebook);
128 this.celltoolbar = new CellToolbar(this, this.events, this.notebook);
129 inner_cell.append(this.celltoolbar.element);
129 inner_cell.append(this.celltoolbar.element);
130 var input_area = $('<div/>').addClass('input_area');
130 var input_area = $('<div/>').addClass('input_area');
131 this.code_mirror = CodeMirror(input_area.get(0), this.cm_config);
131 this.code_mirror = CodeMirror(input_area.get(0), this.cm_config);
132 $(this.code_mirror.getInputField()).attr("spellcheck", "false");
132 $(this.code_mirror.getInputField()).attr("spellcheck", "false");
133 inner_cell.append(input_area);
133 inner_cell.append(input_area);
134 input.append(prompt).append(inner_cell);
134 input.append(prompt).append(inner_cell);
135
135
136 var widget_area = $('<div/>')
136 var widget_area = $('<div/>')
137 .addClass('widget-area')
137 .addClass('widget-area')
138 .hide();
138 .hide();
139 this.widget_area = widget_area;
139 this.widget_area = widget_area;
140 var widget_prompt = $('<div/>')
140 var widget_prompt = $('<div/>')
141 .addClass('prompt')
141 .addClass('prompt')
142 .appendTo(widget_area);
142 .appendTo(widget_area);
143 var widget_subarea = $('<div/>')
143 var widget_subarea = $('<div/>')
144 .addClass('widget-subarea')
144 .addClass('widget-subarea')
145 .appendTo(widget_area);
145 .appendTo(widget_area);
146 this.widget_subarea = widget_subarea;
146 this.widget_subarea = widget_subarea;
147 var widget_clear_buton = $('<button />')
147 var widget_clear_buton = $('<button />')
148 .addClass('close')
148 .addClass('close')
149 .html('&times;')
149 .html('&times;')
150 .click(function() {
150 .click(function() {
151 widget_area.slideUp('', function(){ widget_subarea.html(''); });
151 widget_area.slideUp('', function(){ widget_subarea.html(''); });
152 })
152 })
153 .appendTo(widget_prompt);
153 .appendTo(widget_prompt);
154
154
155 var output = $('<div></div>');
155 var output = $('<div></div>');
156 cell.append(input).append(widget_area).append(output);
156 cell.append(input).append(widget_area).append(output);
157 this.element = cell;
157 this.element = cell;
158 this.output_area = new OutputArea(output, true, this.events, this.keyboard_manager);
158 this.output_area = new OutputArea(output, true, this.events, this.keyboard_manager);
159 this.completer = new Completer(this, this.events);
159 this.completer = new Completer(this, this.events);
160 };
160 };
161
161
162 /** @method bind_events */
162 /** @method bind_events */
163 CodeCell.prototype.bind_events = function () {
163 CodeCell.prototype.bind_events = function () {
164 Cell.prototype.bind_events.apply(this);
164 Cell.prototype.bind_events.apply(this);
165 var that = this;
165 var that = this;
166
166
167 this.element.focusout(
167 this.element.focusout(
168 function() { that.auto_highlight(); }
168 function() { that.auto_highlight(); }
169 );
169 );
170 };
170 };
171
171
172
172
173 /**
173 /**
174 * This method gets called in CodeMirror's onKeyDown/onKeyPress
174 * This method gets called in CodeMirror's onKeyDown/onKeyPress
175 * handlers and is used to provide custom key handling. Its return
175 * handlers and is used to provide custom key handling. Its return
176 * value is used to determine if CodeMirror should ignore the event:
176 * value is used to determine if CodeMirror should ignore the event:
177 * true = ignore, false = don't ignore.
177 * true = ignore, false = don't ignore.
178 * @method handle_codemirror_keyevent
178 * @method handle_codemirror_keyevent
179 */
179 */
180 CodeCell.prototype.handle_codemirror_keyevent = function (editor, event) {
180 CodeCell.prototype.handle_codemirror_keyevent = function (editor, event) {
181
181
182 var that = this;
182 var that = this;
183 // whatever key is pressed, first, cancel the tooltip request before
183 // whatever key is pressed, first, cancel the tooltip request before
184 // they are sent, and remove tooltip if any, except for tab again
184 // they are sent, and remove tooltip if any, except for tab again
185 var tooltip_closed = null;
185 var tooltip_closed = null;
186 if (event.type === 'keydown' && event.which != keycodes.tab ) {
186 if (event.type === 'keydown' && event.which != keycodes.tab ) {
187 tooltip_closed = this.tooltip.remove_and_cancel_tooltip();
187 tooltip_closed = this.tooltip.remove_and_cancel_tooltip();
188 }
188 }
189
189
190 var cur = editor.getCursor();
190 var cur = editor.getCursor();
191 if (event.keyCode === keycodes.enter){
191 if (event.keyCode === keycodes.enter){
192 this.auto_highlight();
192 this.auto_highlight();
193 }
193 }
194
194
195 if (event.which === keycodes.down && event.type === 'keypress' && this.tooltip.time_before_tooltip >= 0) {
195 if (event.which === keycodes.down && event.type === 'keypress' && this.tooltip.time_before_tooltip >= 0) {
196 // triger on keypress (!) otherwise inconsistent event.which depending on plateform
196 // triger on keypress (!) otherwise inconsistent event.which depending on plateform
197 // browser and keyboard layout !
197 // browser and keyboard layout !
198 // Pressing '(' , request tooltip, don't forget to reappend it
198 // Pressing '(' , request tooltip, don't forget to reappend it
199 // The second argument says to hide the tooltip if the docstring
199 // The second argument says to hide the tooltip if the docstring
200 // is actually empty
200 // is actually empty
201 this.tooltip.pending(that, true);
201 this.tooltip.pending(that, true);
202 } else if ( tooltip_closed && event.which === keycodes.esc && event.type === 'keydown') {
202 } else if ( tooltip_closed && event.which === keycodes.esc && event.type === 'keydown') {
203 // If tooltip is active, cancel it. The call to
203 // If tooltip is active, cancel it. The call to
204 // remove_and_cancel_tooltip above doesn't pass, force=true.
204 // remove_and_cancel_tooltip above doesn't pass, force=true.
205 // Because of this it won't actually close the tooltip
205 // Because of this it won't actually close the tooltip
206 // if it is in sticky mode. Thus, we have to check again if it is open
206 // if it is in sticky mode. Thus, we have to check again if it is open
207 // and close it with force=true.
207 // and close it with force=true.
208 if (!this.tooltip._hidden) {
208 if (!this.tooltip._hidden) {
209 this.tooltip.remove_and_cancel_tooltip(true);
209 this.tooltip.remove_and_cancel_tooltip(true);
210 }
210 }
211 // If we closed the tooltip, don't let CM or the global handlers
211 // If we closed the tooltip, don't let CM or the global handlers
212 // handle this event.
212 // handle this event.
213 event.stop();
213 event.stop();
214 return true;
214 return true;
215 } else if (event.keyCode === keycodes.tab && event.type === 'keydown' && event.shiftKey) {
215 } else if (event.keyCode === keycodes.tab && event.type === 'keydown' && event.shiftKey) {
216 if (editor.somethingSelected()){
216 if (editor.somethingSelected()){
217 var anchor = editor.getCursor("anchor");
217 var anchor = editor.getCursor("anchor");
218 var head = editor.getCursor("head");
218 var head = editor.getCursor("head");
219 if( anchor.line != head.line){
219 if( anchor.line != head.line){
220 return false;
220 return false;
221 }
221 }
222 }
222 }
223 this.tooltip.request(that);
223 this.tooltip.request(that);
224 event.stop();
224 event.stop();
225 return true;
225 return true;
226 } else if (event.keyCode === keycodes.tab && event.type == 'keydown') {
226 } else if (event.keyCode === keycodes.tab && event.type == 'keydown') {
227 // Tab completion.
227 // Tab completion.
228 this.tooltip.remove_and_cancel_tooltip();
228 this.tooltip.remove_and_cancel_tooltip();
229 if (editor.somethingSelected()) {
229 if (editor.somethingSelected()) {
230 return false;
230 return false;
231 }
231 }
232 var pre_cursor = editor.getRange({line:cur.line,ch:0},cur);
232 var pre_cursor = editor.getRange({line:cur.line,ch:0},cur);
233 if (pre_cursor.trim() === "") {
233 if (pre_cursor.trim() === "") {
234 // Don't autocomplete if the part of the line before the cursor
234 // Don't autocomplete if the part of the line before the cursor
235 // is empty. In this case, let CodeMirror handle indentation.
235 // is empty. In this case, let CodeMirror handle indentation.
236 return false;
236 return false;
237 } else {
237 } else {
238 event.stop();
238 event.stop();
239 this.completer.startCompletion();
239 this.completer.startCompletion();
240 return true;
240 return true;
241 }
241 }
242 }
242 }
243
243
244 // keyboard event wasn't one of those unique to code cells, let's see
244 // keyboard event wasn't one of those unique to code cells, let's see
245 // if it's one of the generic ones (i.e. check edit mode shortcuts)
245 // if it's one of the generic ones (i.e. check edit mode shortcuts)
246 return Cell.prototype.handle_codemirror_keyevent.apply(this, [editor, event]);
246 return Cell.prototype.handle_codemirror_keyevent.apply(this, [editor, event]);
247 };
247 };
248
248
249 // Kernel related calls.
249 // Kernel related calls.
250
250
251 CodeCell.prototype.set_kernel = function (kernel) {
251 CodeCell.prototype.set_kernel = function (kernel) {
252 this.kernel = kernel;
252 this.kernel = kernel;
253 };
253 };
254
254
255 /**
255 /**
256 * Execute current code cell to the kernel
256 * Execute current code cell to the kernel
257 * @method execute
257 * @method execute
258 */
258 */
259 CodeCell.prototype.execute = function () {
259 CodeCell.prototype.execute = function () {
260 this.output_area.clear_output();
260 this.output_area.clear_output();
261
261
262 // Clear widget area
262 // Clear widget area
263 this.widget_subarea.html('');
263 this.widget_subarea.html('');
264 this.widget_subarea.height('');
264 this.widget_subarea.height('');
265 this.widget_area.height('');
265 this.widget_area.height('');
266 this.widget_area.hide();
266 this.widget_area.hide();
267
267
268 this.set_input_prompt('*');
268 this.set_input_prompt('*');
269 this.element.addClass("running");
269 this.element.addClass("running");
270 if (this.last_msg_id) {
270 if (this.last_msg_id) {
271 this.kernel.clear_callbacks_for_msg(this.last_msg_id);
271 this.kernel.clear_callbacks_for_msg(this.last_msg_id);
272 }
272 }
273 var callbacks = this.get_callbacks();
273 var callbacks = this.get_callbacks();
274
274
275 var old_msg_id = this.last_msg_id;
275 var old_msg_id = this.last_msg_id;
276 this.last_msg_id = this.kernel.execute(this.get_text(), callbacks, {silent: false, store_history: true});
276 this.last_msg_id = this.kernel.execute(this.get_text(), callbacks, {silent: false, store_history: true});
277 if (old_msg_id) {
277 if (old_msg_id) {
278 delete CodeCell.msg_cells[old_msg_id];
278 delete CodeCell.msg_cells[old_msg_id];
279 }
279 }
280 CodeCell.msg_cells[this.last_msg_id] = this;
280 CodeCell.msg_cells[this.last_msg_id] = this;
281 };
281 };
282
282
283 /**
283 /**
284 * Construct the default callbacks for
284 * Construct the default callbacks for
285 * @method get_callbacks
285 * @method get_callbacks
286 */
286 */
287 CodeCell.prototype.get_callbacks = function () {
287 CodeCell.prototype.get_callbacks = function () {
288 return {
288 return {
289 shell : {
289 shell : {
290 reply : $.proxy(this._handle_execute_reply, this),
290 reply : $.proxy(this._handle_execute_reply, this),
291 payload : {
291 payload : {
292 set_next_input : $.proxy(this._handle_set_next_input, this),
292 set_next_input : $.proxy(this._handle_set_next_input, this),
293 page : $.proxy(this._open_with_pager, this)
293 page : $.proxy(this._open_with_pager, this)
294 }
294 }
295 },
295 },
296 iopub : {
296 iopub : {
297 output : $.proxy(this.output_area.handle_output, this.output_area),
297 output : $.proxy(this.output_area.handle_output, this.output_area),
298 clear_output : $.proxy(this.output_area.handle_clear_output, this.output_area),
298 clear_output : $.proxy(this.output_area.handle_clear_output, this.output_area),
299 },
299 },
300 input : $.proxy(this._handle_input_request, this)
300 input : $.proxy(this._handle_input_request, this)
301 };
301 };
302 };
302 };
303
303
304 CodeCell.prototype._open_with_pager = function (payload) {
304 CodeCell.prototype._open_with_pager = function (payload) {
305 this.events.trigger('open_with_text.Pager', payload);
305 this.events.trigger('open_with_text.Pager', payload);
306 };
306 };
307
307
308 /**
308 /**
309 * @method _handle_execute_reply
309 * @method _handle_execute_reply
310 * @private
310 * @private
311 */
311 */
312 CodeCell.prototype._handle_execute_reply = function (msg) {
312 CodeCell.prototype._handle_execute_reply = function (msg) {
313 this.set_input_prompt(msg.content.execution_count);
313 this.set_input_prompt(msg.content.execution_count);
314 this.element.removeClass("running");
314 this.element.removeClass("running");
315 this.events.trigger('set_dirty.Notebook', {value: true});
315 this.events.trigger('set_dirty.Notebook', {value: true});
316 };
316 };
317
317
318 /**
318 /**
319 * @method _handle_set_next_input
319 * @method _handle_set_next_input
320 * @private
320 * @private
321 */
321 */
322 CodeCell.prototype._handle_set_next_input = function (payload) {
322 CodeCell.prototype._handle_set_next_input = function (payload) {
323 var data = {'cell': this, 'text': payload.text};
323 var data = {'cell': this, 'text': payload.text};
324 this.events.trigger('set_next_input.Notebook', data);
324 this.events.trigger('set_next_input.Notebook', data);
325 };
325 };
326
326
327 /**
327 /**
328 * @method _handle_input_request
328 * @method _handle_input_request
329 * @private
329 * @private
330 */
330 */
331 CodeCell.prototype._handle_input_request = function (msg) {
331 CodeCell.prototype._handle_input_request = function (msg) {
332 this.output_area.append_raw_input(msg);
332 this.output_area.append_raw_input(msg);
333 };
333 };
334
334
335
335
336 // Basic cell manipulation.
336 // Basic cell manipulation.
337
337
338 CodeCell.prototype.select = function () {
338 CodeCell.prototype.select = function () {
339 var cont = Cell.prototype.select.apply(this);
339 var cont = Cell.prototype.select.apply(this);
340 if (cont) {
340 if (cont) {
341 this.code_mirror.refresh();
341 this.code_mirror.refresh();
342 this.auto_highlight();
342 this.auto_highlight();
343 }
343 }
344 return cont;
344 return cont;
345 };
345 };
346
346
347 CodeCell.prototype.render = function () {
347 CodeCell.prototype.render = function () {
348 var cont = Cell.prototype.render.apply(this);
348 var cont = Cell.prototype.render.apply(this);
349 // Always execute, even if we are already in the rendered state
349 // Always execute, even if we are already in the rendered state
350 return cont;
350 return cont;
351 };
351 };
352
352
353 CodeCell.prototype.unrender = function () {
353 CodeCell.prototype.unrender = function () {
354 // CodeCell is always rendered
354 // CodeCell is always rendered
355 return false;
355 return false;
356 };
356 };
357
357
358 CodeCell.prototype.select_all = function () {
358 CodeCell.prototype.select_all = function () {
359 var start = {line: 0, ch: 0};
359 var start = {line: 0, ch: 0};
360 var nlines = this.code_mirror.lineCount();
360 var nlines = this.code_mirror.lineCount();
361 var last_line = this.code_mirror.getLine(nlines-1);
361 var last_line = this.code_mirror.getLine(nlines-1);
362 var end = {line: nlines-1, ch: last_line.length};
362 var end = {line: nlines-1, ch: last_line.length};
363 this.code_mirror.setSelection(start, end);
363 this.code_mirror.setSelection(start, end);
364 };
364 };
365
365
366
366
367 CodeCell.prototype.collapse_output = function () {
367 CodeCell.prototype.collapse_output = function () {
368 this.collapsed = true;
368 this.collapsed = true;
369 this.output_area.collapse();
369 this.output_area.collapse();
370 };
370 };
371
371
372
372
373 CodeCell.prototype.expand_output = function () {
373 CodeCell.prototype.expand_output = function () {
374 this.collapsed = false;
374 this.collapsed = false;
375 this.output_area.expand();
375 this.output_area.expand();
376 this.output_area.unscroll_area();
376 this.output_area.unscroll_area();
377 };
377 };
378
378
379 CodeCell.prototype.scroll_output = function () {
379 CodeCell.prototype.scroll_output = function () {
380 this.output_area.expand();
380 this.output_area.expand();
381 this.output_area.scroll_if_long();
381 this.output_area.scroll_if_long();
382 };
382 };
383
383
384 CodeCell.prototype.toggle_output = function () {
384 CodeCell.prototype.toggle_output = function () {
385 this.collapsed = Boolean(1 - this.collapsed);
385 this.collapsed = Boolean(1 - this.collapsed);
386 this.output_area.toggle_output();
386 this.output_area.toggle_output();
387 };
387 };
388
388
389 CodeCell.prototype.toggle_output_scroll = function () {
389 CodeCell.prototype.toggle_output_scroll = function () {
390 this.output_area.toggle_scroll();
390 this.output_area.toggle_scroll();
391 };
391 };
392
392
393
393
394 CodeCell.input_prompt_classical = function (prompt_value, lines_number) {
394 CodeCell.input_prompt_classical = function (prompt_value, lines_number) {
395 var ns;
395 var ns;
396 if (prompt_value === undefined) {
396 if (prompt_value === undefined) {
397 ns = "&nbsp;";
397 ns = "&nbsp;";
398 } else {
398 } else {
399 ns = encodeURIComponent(prompt_value);
399 ns = encodeURIComponent(prompt_value);
400 }
400 }
401 return 'In&nbsp;[' + ns + ']:';
401 return 'In&nbsp;[' + ns + ']:';
402 };
402 };
403
403
404 CodeCell.input_prompt_continuation = function (prompt_value, lines_number) {
404 CodeCell.input_prompt_continuation = function (prompt_value, lines_number) {
405 var html = [CodeCell.input_prompt_classical(prompt_value, lines_number)];
405 var html = [CodeCell.input_prompt_classical(prompt_value, lines_number)];
406 for(var i=1; i < lines_number; i++) {
406 for(var i=1; i < lines_number; i++) {
407 html.push(['...:']);
407 html.push(['...:']);
408 }
408 }
409 return html.join('<br/>');
409 return html.join('<br/>');
410 };
410 };
411
411
412 CodeCell.input_prompt_function = CodeCell.input_prompt_classical;
412 CodeCell.input_prompt_function = CodeCell.input_prompt_classical;
413
413
414
414
415 CodeCell.prototype.set_input_prompt = function (number) {
415 CodeCell.prototype.set_input_prompt = function (number) {
416 var nline = 1;
416 var nline = 1;
417 if (this.code_mirror !== undefined) {
417 if (this.code_mirror !== undefined) {
418 nline = this.code_mirror.lineCount();
418 nline = this.code_mirror.lineCount();
419 }
419 }
420 this.input_prompt_number = number;
420 this.input_prompt_number = number;
421 var prompt_html = CodeCell.input_prompt_function(this.input_prompt_number, nline);
421 var prompt_html = CodeCell.input_prompt_function(this.input_prompt_number, nline);
422 // This HTML call is okay because the user contents are escaped.
422 // This HTML call is okay because the user contents are escaped.
423 this.element.find('div.input_prompt').html(prompt_html);
423 this.element.find('div.input_prompt').html(prompt_html);
424 };
424 };
425
425
426
426
427 CodeCell.prototype.clear_input = function () {
427 CodeCell.prototype.clear_input = function () {
428 this.code_mirror.setValue('');
428 this.code_mirror.setValue('');
429 };
429 };
430
430
431
431
432 CodeCell.prototype.get_text = function () {
432 CodeCell.prototype.get_text = function () {
433 return this.code_mirror.getValue();
433 return this.code_mirror.getValue();
434 };
434 };
435
435
436
436
437 CodeCell.prototype.set_text = function (code) {
437 CodeCell.prototype.set_text = function (code) {
438 return this.code_mirror.setValue(code);
438 return this.code_mirror.setValue(code);
439 };
439 };
440
440
441
441
442 CodeCell.prototype.clear_output = function (wait) {
442 CodeCell.prototype.clear_output = function (wait) {
443 this.output_area.clear_output(wait);
443 this.output_area.clear_output(wait);
444 this.set_input_prompt();
444 this.set_input_prompt();
445 };
445 };
446
446
447
447
448 // JSON serialization
448 // JSON serialization
449
449
450 CodeCell.prototype.fromJSON = function (data) {
450 CodeCell.prototype.fromJSON = function (data) {
451 Cell.prototype.fromJSON.apply(this, arguments);
451 Cell.prototype.fromJSON.apply(this, arguments);
452 if (data.cell_type === 'code') {
452 if (data.cell_type === 'code') {
453 if (data.input !== undefined) {
453 if (data.input !== undefined) {
454 this.set_text(data.input);
454 this.set_text(data.input);
455 // make this value the starting point, so that we can only undo
455 // make this value the starting point, so that we can only undo
456 // to this state, instead of a blank cell
456 // to this state, instead of a blank cell
457 this.code_mirror.clearHistory();
457 this.code_mirror.clearHistory();
458 this.auto_highlight();
458 this.auto_highlight();
459 }
459 }
460 if (data.prompt_number !== undefined) {
460 if (data.prompt_number !== undefined) {
461 this.set_input_prompt(data.prompt_number);
461 this.set_input_prompt(data.prompt_number);
462 } else {
462 } else {
463 this.set_input_prompt();
463 this.set_input_prompt();
464 }
464 }
465 this.output_area.trusted = data.trusted || false;
465 this.output_area.trusted = data.trusted || false;
466 this.output_area.fromJSON(data.outputs);
466 this.output_area.fromJSON(data.outputs);
467 if (data.collapsed !== undefined) {
467 if (data.collapsed !== undefined) {
468 if (data.collapsed) {
468 if (data.collapsed) {
469 this.collapse_output();
469 this.collapse_output();
470 } else {
470 } else {
471 this.expand_output();
471 this.expand_output();
472 }
472 }
473 }
473 }
474 }
474 }
475 };
475 };
476
476
477
477
478 CodeCell.prototype.toJSON = function () {
478 CodeCell.prototype.toJSON = function () {
479 var data = Cell.prototype.toJSON.apply(this);
479 var data = Cell.prototype.toJSON.apply(this);
480 data.input = this.get_text();
480 data.input = this.get_text();
481 // is finite protect against undefined and '*' value
481 // is finite protect against undefined and '*' value
482 if (isFinite(this.input_prompt_number)) {
482 if (isFinite(this.input_prompt_number)) {
483 data.prompt_number = this.input_prompt_number;
483 data.prompt_number = this.input_prompt_number;
484 }
484 }
485 var outputs = this.output_area.toJSON();
485 var outputs = this.output_area.toJSON();
486 data.outputs = outputs;
486 data.outputs = outputs;
487 data.language = 'python';
487 data.language = 'python';
488 data.trusted = this.output_area.trusted;
488 data.trusted = this.output_area.trusted;
489 data.collapsed = this.collapsed;
489 data.collapsed = this.collapsed;
490 return data;
490 return data;
491 };
491 };
492
492
493 /**
493 /**
494 * handle cell level logic when a cell is unselected
494 * handle cell level logic when a cell is unselected
495 * @method unselect
495 * @method unselect
496 * @return is the action being taken
496 * @return is the action being taken
497 */
497 */
498 CodeCell.prototype.unselect = function () {
498 CodeCell.prototype.unselect = function () {
499 var cont = Cell.prototype.unselect.apply(this);
499 var cont = Cell.prototype.unselect.apply(this);
500 if (cont) {
500 if (cont) {
501 // When a code cell is usnelected, make sure that the corresponding
501 // When a code cell is usnelected, make sure that the corresponding
502 // tooltip and completer to that cell is closed.
502 // tooltip and completer to that cell is closed.
503 this.tooltip.remove_and_cancel_tooltip(true);
503 this.tooltip.remove_and_cancel_tooltip(true);
504 if (this.completer !== null) {
504 if (this.completer !== null) {
505 this.completer.close();
505 this.completer.close();
506 }
506 }
507 }
507 }
508 return cont;
508 return cont;
509 };
509 };
510
510
511 // Backwards compatability.
511 // Backwards compatability.
512 IPython.CodeCell = CodeCell;
512 IPython.CodeCell = CodeCell;
513
513
514 return CodeCell;
514 return {'CodeCell': CodeCell};
515 });
515 });
@@ -1,387 +1,387 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 define([
4 define([
5 'base/js/namespace',
5 'base/js/namespace',
6 'jquery',
6 'jquery',
7 'base/js/utils',
7 'base/js/utils',
8 'base/js/keyboard',
8 'base/js/keyboard',
9 'notebook/js/contexthint',
9 'notebook/js/contexthint',
10 ], function(IPython, $, utils, keyboard) {
10 ], function(IPython, $, utils, keyboard) {
11 "use strict";
11 "use strict";
12
12
13 // easier key mapping
13 // easier key mapping
14 var keycodes = keyboard.keycodes;
14 var keycodes = keyboard.keycodes;
15
15
16 var prepend_n_prc = function(str, n) {
16 var prepend_n_prc = function(str, n) {
17 for( var i =0 ; i< n ; i++){
17 for( var i =0 ; i< n ; i++){
18 str = '%'+str ;
18 str = '%'+str ;
19 }
19 }
20 return str;
20 return str;
21 };
21 };
22
22
23 var _existing_completion = function(item, completion_array){
23 var _existing_completion = function(item, completion_array){
24 for( var i=0; i < completion_array.length; i++) {
24 for( var i=0; i < completion_array.length; i++) {
25 if (completion_array[i].trim().substr(-item.length) == item) {
25 if (completion_array[i].trim().substr(-item.length) == item) {
26 return true;
26 return true;
27 }
27 }
28 }
28 }
29 return false;
29 return false;
30 };
30 };
31
31
32 // what is the common start of all completions
32 // what is the common start of all completions
33 function shared_start(B, drop_prct) {
33 function shared_start(B, drop_prct) {
34 if (B.length == 1) {
34 if (B.length == 1) {
35 return B[0];
35 return B[0];
36 }
36 }
37 var A = [];
37 var A = [];
38 var common;
38 var common;
39 var min_lead_prct = 10;
39 var min_lead_prct = 10;
40 for (var i = 0; i < B.length; i++) {
40 for (var i = 0; i < B.length; i++) {
41 var str = B[i].str;
41 var str = B[i].str;
42 var localmin = 0;
42 var localmin = 0;
43 if(drop_prct === true){
43 if(drop_prct === true){
44 while ( str.substr(0, 1) == '%') {
44 while ( str.substr(0, 1) == '%') {
45 localmin = localmin+1;
45 localmin = localmin+1;
46 str = str.substring(1);
46 str = str.substring(1);
47 }
47 }
48 }
48 }
49 min_lead_prct = Math.min(min_lead_prct, localmin);
49 min_lead_prct = Math.min(min_lead_prct, localmin);
50 A.push(str);
50 A.push(str);
51 }
51 }
52
52
53 if (A.length > 1) {
53 if (A.length > 1) {
54 var tem1, tem2, s;
54 var tem1, tem2, s;
55 A = A.slice(0).sort();
55 A = A.slice(0).sort();
56 tem1 = A[0];
56 tem1 = A[0];
57 s = tem1.length;
57 s = tem1.length;
58 tem2 = A.pop();
58 tem2 = A.pop();
59 while (s && tem2.indexOf(tem1) == -1) {
59 while (s && tem2.indexOf(tem1) == -1) {
60 tem1 = tem1.substring(0, --s);
60 tem1 = tem1.substring(0, --s);
61 }
61 }
62 if (tem1 === "" || tem2.indexOf(tem1) !== 0) {
62 if (tem1 === "" || tem2.indexOf(tem1) !== 0) {
63 return {
63 return {
64 str:prepend_n_prc('', min_lead_prct),
64 str:prepend_n_prc('', min_lead_prct),
65 type: "computed",
65 type: "computed",
66 from: B[0].from,
66 from: B[0].from,
67 to: B[0].to
67 to: B[0].to
68 };
68 };
69 }
69 }
70 return {
70 return {
71 str: prepend_n_prc(tem1, min_lead_prct),
71 str: prepend_n_prc(tem1, min_lead_prct),
72 type: "computed",
72 type: "computed",
73 from: B[0].from,
73 from: B[0].from,
74 to: B[0].to
74 to: B[0].to
75 };
75 };
76 }
76 }
77 return null;
77 return null;
78 }
78 }
79
79
80
80
81 var Completer = function (cell, events) {
81 var Completer = function (cell, events) {
82 this.cell = cell;
82 this.cell = cell;
83 this.editor = cell.code_mirror;
83 this.editor = cell.code_mirror;
84 var that = this;
84 var that = this;
85 events.on('status_busy.Kernel', function () {
85 events.on('status_busy.Kernel', function () {
86 that.skip_kernel_completion = true;
86 that.skip_kernel_completion = true;
87 });
87 });
88 events.on('status_idle.Kernel', function () {
88 events.on('status_idle.Kernel', function () {
89 that.skip_kernel_completion = false;
89 that.skip_kernel_completion = false;
90 });
90 });
91 };
91 };
92
92
93 Completer.prototype.startCompletion = function () {
93 Completer.prototype.startCompletion = function () {
94 // call for a 'first' completion, that will set the editor and do some
94 // call for a 'first' completion, that will set the editor and do some
95 // special behavior like autopicking if only one completion available.
95 // special behavior like autopicking if only one completion available.
96 if (this.editor.somethingSelected()) return;
96 if (this.editor.somethingSelected()) return;
97 this.done = false;
97 this.done = false;
98 // use to get focus back on opera
98 // use to get focus back on opera
99 this.carry_on_completion(true);
99 this.carry_on_completion(true);
100 };
100 };
101
101
102
102
103 // easy access for julia to monkeypatch
103 // easy access for julia to monkeypatch
104 //
104 //
105 Completer.reinvoke_re = /[%0-9a-z._/\\:~-]/i;
105 Completer.reinvoke_re = /[%0-9a-z._/\\:~-]/i;
106
106
107 Completer.prototype.reinvoke= function(pre_cursor, block, cursor){
107 Completer.prototype.reinvoke= function(pre_cursor, block, cursor){
108 return Completer.reinvoke_re.test(pre_cursor);
108 return Completer.reinvoke_re.test(pre_cursor);
109 };
109 };
110
110
111 /**
111 /**
112 *
112 *
113 * pass true as parameter if this is the first invocation of the completer
113 * pass true as parameter if this is the first invocation of the completer
114 * this will prevent the completer to dissmiss itself if it is not on a
114 * this will prevent the completer to dissmiss itself if it is not on a
115 * word boundary like pressing tab after a space, and make it autopick the
115 * word boundary like pressing tab after a space, and make it autopick the
116 * only choice if there is only one which prevent from popping the UI. as
116 * only choice if there is only one which prevent from popping the UI. as
117 * well as fast-forwarding the typing if all completion have a common
117 * well as fast-forwarding the typing if all completion have a common
118 * shared start
118 * shared start
119 **/
119 **/
120 Completer.prototype.carry_on_completion = function (first_invocation) {
120 Completer.prototype.carry_on_completion = function (first_invocation) {
121 // Pass true as parameter if you want the completer to autopick when
121 // Pass true as parameter if you want the completer to autopick when
122 // only one completion. This function is automatically reinvoked at
122 // only one completion. This function is automatically reinvoked at
123 // each keystroke with first_invocation = false
123 // each keystroke with first_invocation = false
124 var cur = this.editor.getCursor();
124 var cur = this.editor.getCursor();
125 var line = this.editor.getLine(cur.line);
125 var line = this.editor.getLine(cur.line);
126 var pre_cursor = this.editor.getRange({
126 var pre_cursor = this.editor.getRange({
127 line: cur.line,
127 line: cur.line,
128 ch: cur.ch - 1
128 ch: cur.ch - 1
129 }, cur);
129 }, cur);
130
130
131 // we need to check that we are still on a word boundary
131 // we need to check that we are still on a word boundary
132 // because while typing the completer is still reinvoking itself
132 // because while typing the completer is still reinvoking itself
133 // so dismiss if we are on a "bad" caracter
133 // so dismiss if we are on a "bad" caracter
134 if (!this.reinvoke(pre_cursor) && !first_invocation) {
134 if (!this.reinvoke(pre_cursor) && !first_invocation) {
135 this.close();
135 this.close();
136 return;
136 return;
137 }
137 }
138
138
139 this.autopick = false;
139 this.autopick = false;
140 if (first_invocation) {
140 if (first_invocation) {
141 this.autopick = true;
141 this.autopick = true;
142 }
142 }
143
143
144 // We want a single cursor position.
144 // We want a single cursor position.
145 if (this.editor.somethingSelected()) {
145 if (this.editor.somethingSelected()) {
146 return;
146 return;
147 }
147 }
148
148
149 // one kernel completion came back, finish_completing will be called with the results
149 // one kernel completion came back, finish_completing will be called with the results
150 // we fork here and directly call finish completing if kernel is busy
150 // we fork here and directly call finish completing if kernel is busy
151 var cursor_pos = utils.to_absolute_cursor_pos(this.editor, cur);
151 var cursor_pos = utils.to_absolute_cursor_pos(this.editor, cur);
152 if (this.skip_kernel_completion) {
152 if (this.skip_kernel_completion) {
153 this.finish_completing({ content: {
153 this.finish_completing({ content: {
154 matches: [],
154 matches: [],
155 cursor_start: cursor_pos,
155 cursor_start: cursor_pos,
156 cursor_end: cursor_pos,
156 cursor_end: cursor_pos,
157 }});
157 }});
158 } else {
158 } else {
159 this.cell.kernel.complete(this.editor.getValue(), cursor_pos,
159 this.cell.kernel.complete(this.editor.getValue(), cursor_pos,
160 $.proxy(this.finish_completing, this)
160 $.proxy(this.finish_completing, this)
161 );
161 );
162 }
162 }
163 };
163 };
164
164
165 Completer.prototype.finish_completing = function (msg) {
165 Completer.prototype.finish_completing = function (msg) {
166 // let's build a function that wrap all that stuff into what is needed
166 // let's build a function that wrap all that stuff into what is needed
167 // for the new completer:
167 // for the new completer:
168 var content = msg.content;
168 var content = msg.content;
169 var start = content.cursor_start;
169 var start = content.cursor_start;
170 var end = content.cursor_end;
170 var end = content.cursor_end;
171 var matches = content.matches;
171 var matches = content.matches;
172
172
173 var cur = this.editor.getCursor();
173 var cur = this.editor.getCursor();
174 if (end === null) {
174 if (end === null) {
175 // adapted message spec replies don't have cursor position info,
175 // adapted message spec replies don't have cursor position info,
176 // interpret end=null as current position,
176 // interpret end=null as current position,
177 // and negative start relative to that
177 // and negative start relative to that
178 end = utils.to_absolute_cursor_pos(this.editor, cur);
178 end = utils.to_absolute_cursor_pos(this.editor, cur);
179 if (start < 0) {
179 if (start < 0) {
180 start = end + start;
180 start = end + start;
181 }
181 }
182 }
182 }
183 var results = CodeMirror.contextHint(this.editor);
183 var results = CodeMirror.contextHint(this.editor);
184 var filtered_results = [];
184 var filtered_results = [];
185 //remove results from context completion
185 //remove results from context completion
186 //that are already in kernel completion
186 //that are already in kernel completion
187 var i;
187 var i;
188 for (i=0; i < results.length; i++) {
188 for (i=0; i < results.length; i++) {
189 if (!_existing_completion(results[i].str, matches)) {
189 if (!_existing_completion(results[i].str, matches)) {
190 filtered_results.push(results[i]);
190 filtered_results.push(results[i]);
191 }
191 }
192 }
192 }
193
193
194 // append the introspection result, in order, at at the beginning of
194 // append the introspection result, in order, at at the beginning of
195 // the table and compute the replacement range from current cursor
195 // the table and compute the replacement range from current cursor
196 // positon and matched_text length.
196 // positon and matched_text length.
197 for (i = matches.length - 1; i >= 0; --i) {
197 for (i = matches.length - 1; i >= 0; --i) {
198 filtered_results.unshift({
198 filtered_results.unshift({
199 str: matches[i],
199 str: matches[i],
200 type: "introspection",
200 type: "introspection",
201 from: utils.from_absolute_cursor_pos(this.editor, start),
201 from: utils.from_absolute_cursor_pos(this.editor, start),
202 to: utils.from_absolute_cursor_pos(this.editor, end)
202 to: utils.from_absolute_cursor_pos(this.editor, end)
203 });
203 });
204 }
204 }
205
205
206 // one the 2 sources results have been merge, deal with it
206 // one the 2 sources results have been merge, deal with it
207 this.raw_result = filtered_results;
207 this.raw_result = filtered_results;
208
208
209 // if empty result return
209 // if empty result return
210 if (!this.raw_result || !this.raw_result.length) return;
210 if (!this.raw_result || !this.raw_result.length) return;
211
211
212 // When there is only one completion, use it directly.
212 // When there is only one completion, use it directly.
213 if (this.autopick && this.raw_result.length == 1) {
213 if (this.autopick && this.raw_result.length == 1) {
214 this.insert(this.raw_result[0]);
214 this.insert(this.raw_result[0]);
215 return;
215 return;
216 }
216 }
217
217
218 if (this.raw_result.length == 1) {
218 if (this.raw_result.length == 1) {
219 // test if first and only completion totally matches
219 // test if first and only completion totally matches
220 // what is typed, in this case dismiss
220 // what is typed, in this case dismiss
221 var str = this.raw_result[0].str;
221 var str = this.raw_result[0].str;
222 var pre_cursor = this.editor.getRange({
222 var pre_cursor = this.editor.getRange({
223 line: cur.line,
223 line: cur.line,
224 ch: cur.ch - str.length
224 ch: cur.ch - str.length
225 }, cur);
225 }, cur);
226 if (pre_cursor == str) {
226 if (pre_cursor == str) {
227 this.close();
227 this.close();
228 return;
228 return;
229 }
229 }
230 }
230 }
231
231
232 if (!this.visible) {
232 if (!this.visible) {
233 this.complete = $('<div/>').addClass('completions');
233 this.complete = $('<div/>').addClass('completions');
234 this.complete.attr('id', 'complete');
234 this.complete.attr('id', 'complete');
235
235
236 // Currently webkit doesn't use the size attr correctly. See:
236 // Currently webkit doesn't use the size attr correctly. See:
237 // https://code.google.com/p/chromium/issues/detail?id=4579
237 // https://code.google.com/p/chromium/issues/detail?id=4579
238 this.sel = $('<select/>')
238 this.sel = $('<select/>')
239 .attr('tabindex', -1)
239 .attr('tabindex', -1)
240 .attr('multiple', 'true');
240 .attr('multiple', 'true');
241 this.complete.append(this.sel);
241 this.complete.append(this.sel);
242 this.visible = true;
242 this.visible = true;
243 $('body').append(this.complete);
243 $('body').append(this.complete);
244
244
245 //build the container
245 //build the container
246 var that = this;
246 var that = this;
247 this.sel.dblclick(function () {
247 this.sel.dblclick(function () {
248 that.pick();
248 that.pick();
249 });
249 });
250 this.sel.focus(function () {
250 this.sel.focus(function () {
251 that.editor.focus();
251 that.editor.focus();
252 });
252 });
253 this._handle_keydown = function (cm, event) {
253 this._handle_keydown = function (cm, event) {
254 that.keydown(event);
254 that.keydown(event);
255 };
255 };
256 this.editor.on('keydown', this._handle_keydown);
256 this.editor.on('keydown', this._handle_keydown);
257 this._handle_keypress = function (cm, event) {
257 this._handle_keypress = function (cm, event) {
258 that.keypress(event);
258 that.keypress(event);
259 };
259 };
260 this.editor.on('keypress', this._handle_keypress);
260 this.editor.on('keypress', this._handle_keypress);
261 }
261 }
262 this.sel.attr('size', Math.min(10, this.raw_result.length));
262 this.sel.attr('size', Math.min(10, this.raw_result.length));
263
263
264 // After everything is on the page, compute the postion.
264 // After everything is on the page, compute the postion.
265 // We put it above the code if it is too close to the bottom of the page.
265 // We put it above the code if it is too close to the bottom of the page.
266 var pos = this.editor.cursorCoords(
266 var pos = this.editor.cursorCoords(
267 utils.from_absolute_cursor_pos(this.editor, start)
267 utils.from_absolute_cursor_pos(this.editor, start)
268 );
268 );
269 var left = pos.left-3;
269 var left = pos.left-3;
270 var top;
270 var top;
271 var cheight = this.complete.height();
271 var cheight = this.complete.height();
272 var wheight = $(window).height();
272 var wheight = $(window).height();
273 if (pos.bottom+cheight+5 > wheight) {
273 if (pos.bottom+cheight+5 > wheight) {
274 top = pos.top-cheight-4;
274 top = pos.top-cheight-4;
275 } else {
275 } else {
276 top = pos.bottom+1;
276 top = pos.bottom+1;
277 }
277 }
278 this.complete.css('left', left + 'px');
278 this.complete.css('left', left + 'px');
279 this.complete.css('top', top + 'px');
279 this.complete.css('top', top + 'px');
280
280
281 // Clear and fill the list.
281 // Clear and fill the list.
282 this.sel.text('');
282 this.sel.text('');
283 this.build_gui_list(this.raw_result);
283 this.build_gui_list(this.raw_result);
284 return true;
284 return true;
285 };
285 };
286
286
287 Completer.prototype.insert = function (completion) {
287 Completer.prototype.insert = function (completion) {
288 this.editor.replaceRange(completion.str, completion.from, completion.to);
288 this.editor.replaceRange(completion.str, completion.from, completion.to);
289 };
289 };
290
290
291 Completer.prototype.build_gui_list = function (completions) {
291 Completer.prototype.build_gui_list = function (completions) {
292 for (var i = 0; i < completions.length; ++i) {
292 for (var i = 0; i < completions.length; ++i) {
293 var opt = $('<option/>').text(completions[i].str).addClass(completions[i].type);
293 var opt = $('<option/>').text(completions[i].str).addClass(completions[i].type);
294 this.sel.append(opt);
294 this.sel.append(opt);
295 }
295 }
296 this.sel.children().first().attr('selected', 'true');
296 this.sel.children().first().attr('selected', 'true');
297 this.sel.scrollTop(0);
297 this.sel.scrollTop(0);
298 };
298 };
299
299
300 Completer.prototype.close = function () {
300 Completer.prototype.close = function () {
301 this.done = true;
301 this.done = true;
302 $('#complete').remove();
302 $('#complete').remove();
303 this.editor.off('keydown', this._handle_keydown);
303 this.editor.off('keydown', this._handle_keydown);
304 this.editor.off('keypress', this._handle_keypress);
304 this.editor.off('keypress', this._handle_keypress);
305 this.visible = false;
305 this.visible = false;
306 };
306 };
307
307
308 Completer.prototype.pick = function () {
308 Completer.prototype.pick = function () {
309 this.insert(this.raw_result[this.sel[0].selectedIndex]);
309 this.insert(this.raw_result[this.sel[0].selectedIndex]);
310 this.close();
310 this.close();
311 };
311 };
312
312
313 Completer.prototype.keydown = function (event) {
313 Completer.prototype.keydown = function (event) {
314 var code = event.keyCode;
314 var code = event.keyCode;
315 var that = this;
315 var that = this;
316
316
317 // Enter
317 // Enter
318 if (code == keycodes.enter) {
318 if (code == keycodes.enter) {
319 CodeMirror.e_stop(event);
319 CodeMirror.e_stop(event);
320 this.pick();
320 this.pick();
321 // Escape or backspace
321 // Escape or backspace
322 } else if (code == keycodes.esc || code == keycodes.backspace) {
322 } else if (code == keycodes.esc || code == keycodes.backspace) {
323 CodeMirror.e_stop(event);
323 CodeMirror.e_stop(event);
324 this.close();
324 this.close();
325 } else if (code == keycodes.tab) {
325 } else if (code == keycodes.tab) {
326 //all the fastforwarding operation,
326 //all the fastforwarding operation,
327 //Check that shared start is not null which can append with prefixed completion
327 //Check that shared start is not null which can append with prefixed completion
328 // like %pylab , pylab have no shred start, and ff will result in py<tab><tab>
328 // like %pylab , pylab have no shred start, and ff will result in py<tab><tab>
329 // to erase py
329 // to erase py
330 var sh = shared_start(this.raw_result, true);
330 var sh = shared_start(this.raw_result, true);
331 if (sh) {
331 if (sh) {
332 this.insert(sh);
332 this.insert(sh);
333 }
333 }
334 this.close();
334 this.close();
335 //reinvoke self
335 //reinvoke self
336 setTimeout(function () {
336 setTimeout(function () {
337 that.carry_on_completion();
337 that.carry_on_completion();
338 }, 50);
338 }, 50);
339 } else if (code == keycodes.up || code == keycodes.down) {
339 } else if (code == keycodes.up || code == keycodes.down) {
340 // need to do that to be able to move the arrow
340 // need to do that to be able to move the arrow
341 // when on the first or last line ofo a code cell
341 // when on the first or last line ofo a code cell
342 CodeMirror.e_stop(event);
342 CodeMirror.e_stop(event);
343
343
344 var options = this.sel.find('option');
344 var options = this.sel.find('option');
345 var index = this.sel[0].selectedIndex;
345 var index = this.sel[0].selectedIndex;
346 if (code == keycodes.up) {
346 if (code == keycodes.up) {
347 index--;
347 index--;
348 }
348 }
349 if (code == keycodes.down) {
349 if (code == keycodes.down) {
350 index++;
350 index++;
351 }
351 }
352 index = Math.min(Math.max(index, 0), options.length-1);
352 index = Math.min(Math.max(index, 0), options.length-1);
353 this.sel[0].selectedIndex = index;
353 this.sel[0].selectedIndex = index;
354 } else if (code == keycodes.left || code == keycodes.right) {
354 } else if (code == keycodes.left || code == keycodes.right) {
355 this.close();
355 this.close();
356 }
356 }
357 };
357 };
358
358
359 Completer.prototype.keypress = function (event) {
359 Completer.prototype.keypress = function (event) {
360 // FIXME: This is a band-aid.
360 // FIXME: This is a band-aid.
361 // on keypress, trigger insertion of a single character.
361 // on keypress, trigger insertion of a single character.
362 // This simulates the old behavior of completion as you type,
362 // This simulates the old behavior of completion as you type,
363 // before events were disconnected and CodeMirror stopped
363 // before events were disconnected and CodeMirror stopped
364 // receiving events while the completer is focused.
364 // receiving events while the completer is focused.
365
365
366 var that = this;
366 var that = this;
367 var code = event.keyCode;
367 var code = event.keyCode;
368
368
369 // don't handle keypress if it's not a character (arrows on FF)
369 // don't handle keypress if it's not a character (arrows on FF)
370 // or ENTER/TAB
370 // or ENTER/TAB
371 if (event.charCode === 0 ||
371 if (event.charCode === 0 ||
372 code == keycodes.tab ||
372 code == keycodes.tab ||
373 code == keycodes.enter
373 code == keycodes.enter
374 ) return;
374 ) return;
375
375
376 this.close();
376 this.close();
377 this.editor.focus();
377 this.editor.focus();
378 setTimeout(function () {
378 setTimeout(function () {
379 that.carry_on_completion();
379 that.carry_on_completion();
380 }, 50);
380 }, 50);
381 };
381 };
382
382
383 // For backwards compatability.
383 // For backwards compatability.
384 IPython.Completer = Completer;
384 IPython.Completer = Completer;
385
385
386 return Completer;
386 return {'Completer': Completer};
387 });
387 });
@@ -1,98 +1,98 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 // highly adapted for codemiror jshint
4 // highly adapted for codemiror jshint
5 define([], function() {
5 define([], function() {
6 "use strict";
6 "use strict";
7
7
8 var forEach = function(arr, f) {
8 var forEach = function(arr, f) {
9 for (var i = 0, e = arr.length; i < e; ++i) f(arr[i]);
9 for (var i = 0, e = arr.length; i < e; ++i) f(arr[i]);
10 };
10 };
11
11
12 var arrayContains = function(arr, item) {
12 var arrayContains = function(arr, item) {
13 if (!Array.prototype.indexOf) {
13 if (!Array.prototype.indexOf) {
14 var i = arr.length;
14 var i = arr.length;
15 while (i--) {
15 while (i--) {
16 if (arr[i] === item) {
16 if (arr[i] === item) {
17 return true;
17 return true;
18 }
18 }
19 }
19 }
20 return false;
20 return false;
21 }
21 }
22 return arr.indexOf(item) != -1;
22 return arr.indexOf(item) != -1;
23 };
23 };
24
24
25 CodeMirror.contextHint = function (editor) {
25 CodeMirror.contextHint = function (editor) {
26 // Find the token at the cursor
26 // Find the token at the cursor
27 var cur = editor.getCursor(),
27 var cur = editor.getCursor(),
28 token = editor.getTokenAt(cur),
28 token = editor.getTokenAt(cur),
29 tprop = token;
29 tprop = token;
30 // If it's not a 'word-style' token, ignore the token.
30 // If it's not a 'word-style' token, ignore the token.
31 // If it is a property, find out what it is a property of.
31 // If it is a property, find out what it is a property of.
32 var list = [];
32 var list = [];
33 var clist = getCompletions(token, editor);
33 var clist = getCompletions(token, editor);
34 for (var i = 0; i < clist.length; i++) {
34 for (var i = 0; i < clist.length; i++) {
35 list.push({
35 list.push({
36 str: clist[i],
36 str: clist[i],
37 type: "context",
37 type: "context",
38 from: {
38 from: {
39 line: cur.line,
39 line: cur.line,
40 ch: token.start
40 ch: token.start
41 },
41 },
42 to: {
42 to: {
43 line: cur.line,
43 line: cur.line,
44 ch: token.end
44 ch: token.end
45 }
45 }
46 });
46 });
47 }
47 }
48 return list;
48 return list;
49 };
49 };
50
50
51 // find all 'words' of current cell
51 // find all 'words' of current cell
52 var getAllTokens = function (editor) {
52 var getAllTokens = function (editor) {
53 var found = [];
53 var found = [];
54
54
55 // add to found if not already in it
55 // add to found if not already in it
56
56
57
57
58 function maybeAdd(str) {
58 function maybeAdd(str) {
59 if (!arrayContains(found, str)) found.push(str);
59 if (!arrayContains(found, str)) found.push(str);
60 }
60 }
61
61
62 // loop through all token on all lines
62 // loop through all token on all lines
63 var lineCount = editor.lineCount();
63 var lineCount = editor.lineCount();
64 // loop on line
64 // loop on line
65 for (var l = 0; l < lineCount; l++) {
65 for (var l = 0; l < lineCount; l++) {
66 var line = editor.getLine(l);
66 var line = editor.getLine(l);
67 //loop on char
67 //loop on char
68 for (var c = 1; c < line.length; c++) {
68 for (var c = 1; c < line.length; c++) {
69 var tk = editor.getTokenAt({
69 var tk = editor.getTokenAt({
70 line: l,
70 line: l,
71 ch: c
71 ch: c
72 });
72 });
73 // if token has a class, it has geat chances of beeing
73 // if token has a class, it has geat chances of beeing
74 // of interest. Add it to the list of possible completions.
74 // of interest. Add it to the list of possible completions.
75 // we could skip token of ClassName 'comment'
75 // we could skip token of ClassName 'comment'
76 // or 'number' and 'operator'
76 // or 'number' and 'operator'
77 if (tk.className !== null) {
77 if (tk.className !== null) {
78 maybeAdd(tk.string);
78 maybeAdd(tk.string);
79 }
79 }
80 // jump to char after end of current token
80 // jump to char after end of current token
81 c = tk.end;
81 c = tk.end;
82 }
82 }
83 }
83 }
84 return found;
84 return found;
85 };
85 };
86
86
87 var getCompletions = function(token, editor) {
87 var getCompletions = function(token, editor) {
88 var candidates = getAllTokens(editor);
88 var candidates = getAllTokens(editor);
89 // filter all token that have a common start (but nox exactly) the lenght of the current token
89 // filter all token that have a common start (but nox exactly) the lenght of the current token
90 var lambda = function (x) {
90 var lambda = function (x) {
91 return (x.indexOf(token.string) === 0 && x != token.string);
91 return (x.indexOf(token.string) === 0 && x != token.string);
92 };
92 };
93 var filterd = candidates.filter(lambda);
93 var filterd = candidates.filter(lambda);
94 return filterd;
94 return filterd;
95 };
95 };
96
96
97 return CodeMirror.contextHint;
97 return {'contextHint': CodeMirror.contextHint};
98 });
98 });
@@ -1,560 +1,559 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 define([
4 define([
5 'base/js/namespace',
5 'base/js/namespace',
6 'jquery',
6 'jquery',
7 'base/js/utils',
7 'base/js/utils',
8 'base/js/keyboard',
8 'base/js/keyboard',
9 ], function(IPython, $, utils, keyboard) {
9 ], function(IPython, $, utils, keyboard) {
10 "use strict";
10 "use strict";
11
11
12 var browser = utils.browser[0];
12 var browser = utils.browser[0];
13 var platform = utils.platform;
13 var platform = utils.platform;
14
14
15 // Main keyboard manager for the notebook
15 // Main keyboard manager for the notebook
16 var keycodes = keyboard.keycodes;
16 var keycodes = keyboard.keycodes;
17
17
18 var KeyboardManager = function (pager, events) {
18 var KeyboardManager = function (pager, events) {
19 this.mode = 'command';
19 this.mode = 'command';
20 this.enabled = true;
20 this.enabled = true;
21 this.pager = pager;
21 this.pager = pager;
22 this.quick_help = undefined;
22 this.quick_help = undefined;
23 this.notebook = undefined;
23 this.notebook = undefined;
24 this.bind_events();
24 this.bind_events();
25 this.command_shortcuts = new keyboard.ShortcutManager(undefined, events);
25 this.command_shortcuts = new keyboard.ShortcutManager(undefined, events);
26 this.command_shortcuts.add_shortcuts(this.get_default_common_shortcuts());
26 this.command_shortcuts.add_shortcuts(this.get_default_common_shortcuts());
27 this.command_shortcuts.add_shortcuts(this.get_default_command_shortcuts());
27 this.command_shortcuts.add_shortcuts(this.get_default_command_shortcuts());
28 this.edit_shortcuts = new keyboard.ShortcutManager(undefined, events);
28 this.edit_shortcuts = new keyboard.ShortcutManager(undefined, events);
29 this.edit_shortcuts.add_shortcuts(this.get_default_common_shortcuts());
29 this.edit_shortcuts.add_shortcuts(this.get_default_common_shortcuts());
30 this.edit_shortcuts.add_shortcuts(this.get_default_edit_shortcuts());
30 this.edit_shortcuts.add_shortcuts(this.get_default_edit_shortcuts());
31 };
31 };
32
32
33 KeyboardManager.prototype.get_default_common_shortcuts = function() {
33 KeyboardManager.prototype.get_default_common_shortcuts = function() {
34 var that = this;
34 var that = this;
35 var shortcuts = {
35 var shortcuts = {
36 'shift' : {
36 'shift' : {
37 help : '',
37 help : '',
38 help_index : '',
38 help_index : '',
39 handler : function (event) {
39 handler : function (event) {
40 // ignore shift keydown
40 // ignore shift keydown
41 return true;
41 return true;
42 }
42 }
43 },
43 },
44 'shift-enter' : {
44 'shift-enter' : {
45 help : 'run cell, select below',
45 help : 'run cell, select below',
46 help_index : 'ba',
46 help_index : 'ba',
47 handler : function (event) {
47 handler : function (event) {
48 that.notebook.execute_cell_and_select_below();
48 that.notebook.execute_cell_and_select_below();
49 return false;
49 return false;
50 }
50 }
51 },
51 },
52 'ctrl-enter' : {
52 'ctrl-enter' : {
53 help : 'run cell',
53 help : 'run cell',
54 help_index : 'bb',
54 help_index : 'bb',
55 handler : function (event) {
55 handler : function (event) {
56 that.notebook.execute_cell();
56 that.notebook.execute_cell();
57 return false;
57 return false;
58 }
58 }
59 },
59 },
60 'alt-enter' : {
60 'alt-enter' : {
61 help : 'run cell, insert below',
61 help : 'run cell, insert below',
62 help_index : 'bc',
62 help_index : 'bc',
63 handler : function (event) {
63 handler : function (event) {
64 that.notebook.execute_cell_and_insert_below();
64 that.notebook.execute_cell_and_insert_below();
65 return false;
65 return false;
66 }
66 }
67 }
67 }
68 };
68 };
69
69
70 if (platform === 'MacOS') {
70 if (platform === 'MacOS') {
71 shortcuts['cmd-s'] =
71 shortcuts['cmd-s'] =
72 {
72 {
73 help : 'save notebook',
73 help : 'save notebook',
74 help_index : 'fb',
74 help_index : 'fb',
75 handler : function (event) {
75 handler : function (event) {
76 that.notebook.save_checkpoint();
76 that.notebook.save_checkpoint();
77 event.preventDefault();
77 event.preventDefault();
78 return false;
78 return false;
79 }
79 }
80 };
80 };
81 } else {
81 } else {
82 shortcuts['ctrl-s'] =
82 shortcuts['ctrl-s'] =
83 {
83 {
84 help : 'save notebook',
84 help : 'save notebook',
85 help_index : 'fb',
85 help_index : 'fb',
86 handler : function (event) {
86 handler : function (event) {
87 that.notebook.save_checkpoint();
87 that.notebook.save_checkpoint();
88 event.preventDefault();
88 event.preventDefault();
89 return false;
89 return false;
90 }
90 }
91 };
91 };
92 }
92 }
93 return shortcuts;
93 return shortcuts;
94 };
94 };
95
95
96 KeyboardManager.prototype.get_default_edit_shortcuts = function() {
96 KeyboardManager.prototype.get_default_edit_shortcuts = function() {
97 var that = this;
97 var that = this;
98 return {
98 return {
99 'esc' : {
99 'esc' : {
100 help : 'command mode',
100 help : 'command mode',
101 help_index : 'aa',
101 help_index : 'aa',
102 handler : function (event) {
102 handler : function (event) {
103 that.notebook.command_mode();
103 that.notebook.command_mode();
104 return false;
104 return false;
105 }
105 }
106 },
106 },
107 'ctrl-m' : {
107 'ctrl-m' : {
108 help : 'command mode',
108 help : 'command mode',
109 help_index : 'ab',
109 help_index : 'ab',
110 handler : function (event) {
110 handler : function (event) {
111 that.notebook.command_mode();
111 that.notebook.command_mode();
112 return false;
112 return false;
113 }
113 }
114 },
114 },
115 'up' : {
115 'up' : {
116 help : '',
116 help : '',
117 help_index : '',
117 help_index : '',
118 handler : function (event) {
118 handler : function (event) {
119 var index = that.notebook.get_selected_index();
119 var index = that.notebook.get_selected_index();
120 var cell = that.notebook.get_cell(index);
120 var cell = that.notebook.get_cell(index);
121 if (cell && cell.at_top() && index !== 0) {
121 if (cell && cell.at_top() && index !== 0) {
122 event.preventDefault();
122 event.preventDefault();
123 that.notebook.command_mode();
123 that.notebook.command_mode();
124 that.notebook.select_prev();
124 that.notebook.select_prev();
125 that.notebook.edit_mode();
125 that.notebook.edit_mode();
126 var cm = that.notebook.get_selected_cell().code_mirror;
126 var cm = that.notebook.get_selected_cell().code_mirror;
127 cm.setCursor(cm.lastLine(), 0);
127 cm.setCursor(cm.lastLine(), 0);
128 return false;
128 return false;
129 } else if (cell) {
129 } else if (cell) {
130 var cm = cell.code_mirror;
130 var cm = cell.code_mirror;
131 cm.execCommand('goLineUp');
131 cm.execCommand('goLineUp');
132 return false;
132 return false;
133 }
133 }
134 }
134 }
135 },
135 },
136 'down' : {
136 'down' : {
137 help : '',
137 help : '',
138 help_index : '',
138 help_index : '',
139 handler : function (event) {
139 handler : function (event) {
140 var index = that.notebook.get_selected_index();
140 var index = that.notebook.get_selected_index();
141 var cell = that.notebook.get_cell(index);
141 var cell = that.notebook.get_cell(index);
142 if (cell.at_bottom() && index !== (that.notebook.ncells()-1)) {
142 if (cell.at_bottom() && index !== (that.notebook.ncells()-1)) {
143 event.preventDefault();
143 event.preventDefault();
144 that.notebook.command_mode();
144 that.notebook.command_mode();
145 that.notebook.select_next();
145 that.notebook.select_next();
146 that.notebook.edit_mode();
146 that.notebook.edit_mode();
147 var cm = that.notebook.get_selected_cell().code_mirror;
147 var cm = that.notebook.get_selected_cell().code_mirror;
148 cm.setCursor(0, 0);
148 cm.setCursor(0, 0);
149 return false;
149 return false;
150 } else {
150 } else {
151 var cm = cell.code_mirror;
151 var cm = cell.code_mirror;
152 cm.execCommand('goLineDown');
152 cm.execCommand('goLineDown');
153 return false;
153 return false;
154 }
154 }
155 }
155 }
156 },
156 },
157 'ctrl-shift--' : {
157 'ctrl-shift--' : {
158 help : 'split cell',
158 help : 'split cell',
159 help_index : 'ea',
159 help_index : 'ea',
160 handler : function (event) {
160 handler : function (event) {
161 that.notebook.split_cell();
161 that.notebook.split_cell();
162 return false;
162 return false;
163 }
163 }
164 },
164 },
165 'ctrl-shift-subtract' : {
165 'ctrl-shift-subtract' : {
166 help : '',
166 help : '',
167 help_index : 'eb',
167 help_index : 'eb',
168 handler : function (event) {
168 handler : function (event) {
169 that.notebook.split_cell();
169 that.notebook.split_cell();
170 return false;
170 return false;
171 }
171 }
172 },
172 },
173 };
173 };
174 };
174 };
175
175
176 KeyboardManager.prototype.get_default_command_shortcuts = function() {
176 KeyboardManager.prototype.get_default_command_shortcuts = function() {
177 var that = this;
177 var that = this;
178 return {
178 return {
179 'enter' : {
179 'enter' : {
180 help : 'edit mode',
180 help : 'edit mode',
181 help_index : 'aa',
181 help_index : 'aa',
182 handler : function (event) {
182 handler : function (event) {
183 that.notebook.edit_mode();
183 that.notebook.edit_mode();
184 return false;
184 return false;
185 }
185 }
186 },
186 },
187 'up' : {
187 'up' : {
188 help : 'select previous cell',
188 help : 'select previous cell',
189 help_index : 'da',
189 help_index : 'da',
190 handler : function (event) {
190 handler : function (event) {
191 var index = that.notebook.get_selected_index();
191 var index = that.notebook.get_selected_index();
192 if (index !== 0 && index !== null) {
192 if (index !== 0 && index !== null) {
193 that.notebook.select_prev();
193 that.notebook.select_prev();
194 that.notebook.focus_cell();
194 that.notebook.focus_cell();
195 }
195 }
196 return false;
196 return false;
197 }
197 }
198 },
198 },
199 'down' : {
199 'down' : {
200 help : 'select next cell',
200 help : 'select next cell',
201 help_index : 'db',
201 help_index : 'db',
202 handler : function (event) {
202 handler : function (event) {
203 var index = that.notebook.get_selected_index();
203 var index = that.notebook.get_selected_index();
204 if (index !== (that.notebook.ncells()-1) && index !== null) {
204 if (index !== (that.notebook.ncells()-1) && index !== null) {
205 that.notebook.select_next();
205 that.notebook.select_next();
206 that.notebook.focus_cell();
206 that.notebook.focus_cell();
207 }
207 }
208 return false;
208 return false;
209 }
209 }
210 },
210 },
211 'k' : {
211 'k' : {
212 help : 'select previous cell',
212 help : 'select previous cell',
213 help_index : 'dc',
213 help_index : 'dc',
214 handler : function (event) {
214 handler : function (event) {
215 var index = that.notebook.get_selected_index();
215 var index = that.notebook.get_selected_index();
216 if (index !== 0 && index !== null) {
216 if (index !== 0 && index !== null) {
217 that.notebook.select_prev();
217 that.notebook.select_prev();
218 that.notebook.focus_cell();
218 that.notebook.focus_cell();
219 }
219 }
220 return false;
220 return false;
221 }
221 }
222 },
222 },
223 'j' : {
223 'j' : {
224 help : 'select next cell',
224 help : 'select next cell',
225 help_index : 'dd',
225 help_index : 'dd',
226 handler : function (event) {
226 handler : function (event) {
227 var index = that.notebook.get_selected_index();
227 var index = that.notebook.get_selected_index();
228 if (index !== (that.notebook.ncells()-1) && index !== null) {
228 if (index !== (that.notebook.ncells()-1) && index !== null) {
229 that.notebook.select_next();
229 that.notebook.select_next();
230 that.notebook.focus_cell();
230 that.notebook.focus_cell();
231 }
231 }
232 return false;
232 return false;
233 }
233 }
234 },
234 },
235 'x' : {
235 'x' : {
236 help : 'cut cell',
236 help : 'cut cell',
237 help_index : 'ee',
237 help_index : 'ee',
238 handler : function (event) {
238 handler : function (event) {
239 that.notebook.cut_cell();
239 that.notebook.cut_cell();
240 return false;
240 return false;
241 }
241 }
242 },
242 },
243 'c' : {
243 'c' : {
244 help : 'copy cell',
244 help : 'copy cell',
245 help_index : 'ef',
245 help_index : 'ef',
246 handler : function (event) {
246 handler : function (event) {
247 that.notebook.copy_cell();
247 that.notebook.copy_cell();
248 return false;
248 return false;
249 }
249 }
250 },
250 },
251 'shift-v' : {
251 'shift-v' : {
252 help : 'paste cell above',
252 help : 'paste cell above',
253 help_index : 'eg',
253 help_index : 'eg',
254 handler : function (event) {
254 handler : function (event) {
255 that.notebook.paste_cell_above();
255 that.notebook.paste_cell_above();
256 return false;
256 return false;
257 }
257 }
258 },
258 },
259 'v' : {
259 'v' : {
260 help : 'paste cell below',
260 help : 'paste cell below',
261 help_index : 'eh',
261 help_index : 'eh',
262 handler : function (event) {
262 handler : function (event) {
263 that.notebook.paste_cell_below();
263 that.notebook.paste_cell_below();
264 return false;
264 return false;
265 }
265 }
266 },
266 },
267 'd' : {
267 'd' : {
268 help : 'delete cell (press twice)',
268 help : 'delete cell (press twice)',
269 help_index : 'ej',
269 help_index : 'ej',
270 count: 2,
270 count: 2,
271 handler : function (event) {
271 handler : function (event) {
272 that.notebook.delete_cell();
272 that.notebook.delete_cell();
273 return false;
273 return false;
274 }
274 }
275 },
275 },
276 'a' : {
276 'a' : {
277 help : 'insert cell above',
277 help : 'insert cell above',
278 help_index : 'ec',
278 help_index : 'ec',
279 handler : function (event) {
279 handler : function (event) {
280 that.notebook.insert_cell_above();
280 that.notebook.insert_cell_above();
281 that.notebook.select_prev();
281 that.notebook.select_prev();
282 that.notebook.focus_cell();
282 that.notebook.focus_cell();
283 return false;
283 return false;
284 }
284 }
285 },
285 },
286 'b' : {
286 'b' : {
287 help : 'insert cell below',
287 help : 'insert cell below',
288 help_index : 'ed',
288 help_index : 'ed',
289 handler : function (event) {
289 handler : function (event) {
290 that.notebook.insert_cell_below();
290 that.notebook.insert_cell_below();
291 that.notebook.select_next();
291 that.notebook.select_next();
292 that.notebook.focus_cell();
292 that.notebook.focus_cell();
293 return false;
293 return false;
294 }
294 }
295 },
295 },
296 'y' : {
296 'y' : {
297 help : 'to code',
297 help : 'to code',
298 help_index : 'ca',
298 help_index : 'ca',
299 handler : function (event) {
299 handler : function (event) {
300 that.notebook.to_code();
300 that.notebook.to_code();
301 return false;
301 return false;
302 }
302 }
303 },
303 },
304 'm' : {
304 'm' : {
305 help : 'to markdown',
305 help : 'to markdown',
306 help_index : 'cb',
306 help_index : 'cb',
307 handler : function (event) {
307 handler : function (event) {
308 that.notebook.to_markdown();
308 that.notebook.to_markdown();
309 return false;
309 return false;
310 }
310 }
311 },
311 },
312 'r' : {
312 'r' : {
313 help : 'to raw',
313 help : 'to raw',
314 help_index : 'cc',
314 help_index : 'cc',
315 handler : function (event) {
315 handler : function (event) {
316 that.notebook.to_raw();
316 that.notebook.to_raw();
317 return false;
317 return false;
318 }
318 }
319 },
319 },
320 '1' : {
320 '1' : {
321 help : 'to heading 1',
321 help : 'to heading 1',
322 help_index : 'cd',
322 help_index : 'cd',
323 handler : function (event) {
323 handler : function (event) {
324 that.notebook.to_heading(undefined, 1);
324 that.notebook.to_heading(undefined, 1);
325 return false;
325 return false;
326 }
326 }
327 },
327 },
328 '2' : {
328 '2' : {
329 help : 'to heading 2',
329 help : 'to heading 2',
330 help_index : 'ce',
330 help_index : 'ce',
331 handler : function (event) {
331 handler : function (event) {
332 that.notebook.to_heading(undefined, 2);
332 that.notebook.to_heading(undefined, 2);
333 return false;
333 return false;
334 }
334 }
335 },
335 },
336 '3' : {
336 '3' : {
337 help : 'to heading 3',
337 help : 'to heading 3',
338 help_index : 'cf',
338 help_index : 'cf',
339 handler : function (event) {
339 handler : function (event) {
340 that.notebook.to_heading(undefined, 3);
340 that.notebook.to_heading(undefined, 3);
341 return false;
341 return false;
342 }
342 }
343 },
343 },
344 '4' : {
344 '4' : {
345 help : 'to heading 4',
345 help : 'to heading 4',
346 help_index : 'cg',
346 help_index : 'cg',
347 handler : function (event) {
347 handler : function (event) {
348 that.notebook.to_heading(undefined, 4);
348 that.notebook.to_heading(undefined, 4);
349 return false;
349 return false;
350 }
350 }
351 },
351 },
352 '5' : {
352 '5' : {
353 help : 'to heading 5',
353 help : 'to heading 5',
354 help_index : 'ch',
354 help_index : 'ch',
355 handler : function (event) {
355 handler : function (event) {
356 that.notebook.to_heading(undefined, 5);
356 that.notebook.to_heading(undefined, 5);
357 return false;
357 return false;
358 }
358 }
359 },
359 },
360 '6' : {
360 '6' : {
361 help : 'to heading 6',
361 help : 'to heading 6',
362 help_index : 'ci',
362 help_index : 'ci',
363 handler : function (event) {
363 handler : function (event) {
364 that.notebook.to_heading(undefined, 6);
364 that.notebook.to_heading(undefined, 6);
365 return false;
365 return false;
366 }
366 }
367 },
367 },
368 'o' : {
368 'o' : {
369 help : 'toggle output',
369 help : 'toggle output',
370 help_index : 'gb',
370 help_index : 'gb',
371 handler : function (event) {
371 handler : function (event) {
372 that.notebook.toggle_output();
372 that.notebook.toggle_output();
373 return false;
373 return false;
374 }
374 }
375 },
375 },
376 'shift-o' : {
376 'shift-o' : {
377 help : 'toggle output scrolling',
377 help : 'toggle output scrolling',
378 help_index : 'gc',
378 help_index : 'gc',
379 handler : function (event) {
379 handler : function (event) {
380 that.notebook.toggle_output_scroll();
380 that.notebook.toggle_output_scroll();
381 return false;
381 return false;
382 }
382 }
383 },
383 },
384 's' : {
384 's' : {
385 help : 'save notebook',
385 help : 'save notebook',
386 help_index : 'fa',
386 help_index : 'fa',
387 handler : function (event) {
387 handler : function (event) {
388 that.notebook.save_checkpoint();
388 that.notebook.save_checkpoint();
389 return false;
389 return false;
390 }
390 }
391 },
391 },
392 'ctrl-j' : {
392 'ctrl-j' : {
393 help : 'move cell down',
393 help : 'move cell down',
394 help_index : 'eb',
394 help_index : 'eb',
395 handler : function (event) {
395 handler : function (event) {
396 that.notebook.move_cell_down();
396 that.notebook.move_cell_down();
397 return false;
397 return false;
398 }
398 }
399 },
399 },
400 'ctrl-k' : {
400 'ctrl-k' : {
401 help : 'move cell up',
401 help : 'move cell up',
402 help_index : 'ea',
402 help_index : 'ea',
403 handler : function (event) {
403 handler : function (event) {
404 that.notebook.move_cell_up();
404 that.notebook.move_cell_up();
405 return false;
405 return false;
406 }
406 }
407 },
407 },
408 'l' : {
408 'l' : {
409 help : 'toggle line numbers',
409 help : 'toggle line numbers',
410 help_index : 'ga',
410 help_index : 'ga',
411 handler : function (event) {
411 handler : function (event) {
412 that.notebook.cell_toggle_line_numbers();
412 that.notebook.cell_toggle_line_numbers();
413 return false;
413 return false;
414 }
414 }
415 },
415 },
416 'i' : {
416 'i' : {
417 help : 'interrupt kernel (press twice)',
417 help : 'interrupt kernel (press twice)',
418 help_index : 'ha',
418 help_index : 'ha',
419 count: 2,
419 count: 2,
420 handler : function (event) {
420 handler : function (event) {
421 that.notebook.kernel.interrupt();
421 that.notebook.kernel.interrupt();
422 return false;
422 return false;
423 }
423 }
424 },
424 },
425 '0' : {
425 '0' : {
426 help : 'restart kernel (press twice)',
426 help : 'restart kernel (press twice)',
427 help_index : 'hb',
427 help_index : 'hb',
428 count: 2,
428 count: 2,
429 handler : function (event) {
429 handler : function (event) {
430 that.notebook.restart_kernel();
430 that.notebook.restart_kernel();
431 return false;
431 return false;
432 }
432 }
433 },
433 },
434 'h' : {
434 'h' : {
435 help : 'keyboard shortcuts',
435 help : 'keyboard shortcuts',
436 help_index : 'ge',
436 help_index : 'ge',
437 handler : function (event) {
437 handler : function (event) {
438 that.quick_help.show_keyboard_shortcuts();
438 that.quick_help.show_keyboard_shortcuts();
439 return false;
439 return false;
440 }
440 }
441 },
441 },
442 'z' : {
442 'z' : {
443 help : 'undo last delete',
443 help : 'undo last delete',
444 help_index : 'ei',
444 help_index : 'ei',
445 handler : function (event) {
445 handler : function (event) {
446 that.notebook.undelete_cell();
446 that.notebook.undelete_cell();
447 return false;
447 return false;
448 }
448 }
449 },
449 },
450 'shift-m' : {
450 'shift-m' : {
451 help : 'merge cell below',
451 help : 'merge cell below',
452 help_index : 'ek',
452 help_index : 'ek',
453 handler : function (event) {
453 handler : function (event) {
454 that.notebook.merge_cell_below();
454 that.notebook.merge_cell_below();
455 return false;
455 return false;
456 }
456 }
457 },
457 },
458 'q' : {
458 'q' : {
459 help : 'close pager',
459 help : 'close pager',
460 help_index : 'gd',
460 help_index : 'gd',
461 handler : function (event) {
461 handler : function (event) {
462 that.pager.collapse();
462 that.pager.collapse();
463 return false;
463 return false;
464 }
464 }
465 },
465 },
466 };
466 };
467 };
467 };
468
468
469 KeyboardManager.prototype.bind_events = function () {
469 KeyboardManager.prototype.bind_events = function () {
470 var that = this;
470 var that = this;
471 $(document).keydown(function (event) {
471 $(document).keydown(function (event) {
472 return that.handle_keydown(event);
472 return that.handle_keydown(event);
473 });
473 });
474 };
474 };
475
475
476 KeyboardManager.prototype.handle_keydown = function (event) {
476 KeyboardManager.prototype.handle_keydown = function (event) {
477 var notebook = this.notebook;
477 var notebook = this.notebook;
478
478
479 if (event.which === keycodes.esc) {
479 if (event.which === keycodes.esc) {
480 // Intercept escape at highest level to avoid closing
480 // Intercept escape at highest level to avoid closing
481 // websocket connection with firefox
481 // websocket connection with firefox
482 event.preventDefault();
482 event.preventDefault();
483 }
483 }
484
484
485 if (!this.enabled) {
485 if (!this.enabled) {
486 if (event.which === keycodes.esc) {
486 if (event.which === keycodes.esc) {
487 // ESC
487 // ESC
488 notebook.command_mode();
488 notebook.command_mode();
489 return false;
489 return false;
490 }
490 }
491 return true;
491 return true;
492 }
492 }
493
493
494 if (this.mode === 'edit') {
494 if (this.mode === 'edit') {
495 return this.edit_shortcuts.call_handler(event);
495 return this.edit_shortcuts.call_handler(event);
496 } else if (this.mode === 'command') {
496 } else if (this.mode === 'command') {
497 return this.command_shortcuts.call_handler(event);
497 return this.command_shortcuts.call_handler(event);
498 }
498 }
499 return true;
499 return true;
500 };
500 };
501
501
502 KeyboardManager.prototype.edit_mode = function () {
502 KeyboardManager.prototype.edit_mode = function () {
503 this.last_mode = this.mode;
503 this.last_mode = this.mode;
504 this.mode = 'edit';
504 this.mode = 'edit';
505 };
505 };
506
506
507 KeyboardManager.prototype.command_mode = function () {
507 KeyboardManager.prototype.command_mode = function () {
508 this.last_mode = this.mode;
508 this.last_mode = this.mode;
509 this.mode = 'command';
509 this.mode = 'command';
510 };
510 };
511
511
512 KeyboardManager.prototype.enable = function () {
512 KeyboardManager.prototype.enable = function () {
513 this.enabled = true;
513 this.enabled = true;
514 };
514 };
515
515
516 KeyboardManager.prototype.disable = function () {
516 KeyboardManager.prototype.disable = function () {
517 this.enabled = false;
517 this.enabled = false;
518 };
518 };
519
519
520 KeyboardManager.prototype.register_events = function (e) {
520 KeyboardManager.prototype.register_events = function (e) {
521 var that = this;
521 var that = this;
522 var handle_focus = function () {
522 var handle_focus = function () {
523 that.disable();
523 that.disable();
524 };
524 };
525 var handle_blur = function () {
525 var handle_blur = function () {
526 that.enable();
526 that.enable();
527 };
527 };
528 e.on('focusin', handle_focus);
528 e.on('focusin', handle_focus);
529 e.on('focusout', handle_blur);
529 e.on('focusout', handle_blur);
530 // TODO: Very strange. The focusout event does not seem fire for the
530 // TODO: Very strange. The focusout event does not seem fire for the
531 // bootstrap textboxes on FF25&26... This works around that by
531 // bootstrap textboxes on FF25&26... This works around that by
532 // registering focus and blur events recursively on all inputs within
532 // registering focus and blur events recursively on all inputs within
533 // registered element.
533 // registered element.
534 e.find('input').blur(handle_blur);
534 e.find('input').blur(handle_blur);
535 e.on('DOMNodeInserted', function (event) {
535 e.on('DOMNodeInserted', function (event) {
536 var target = $(event.target);
536 var target = $(event.target);
537 if (target.is('input')) {
537 if (target.is('input')) {
538 target.blur(handle_blur);
538 target.blur(handle_blur);
539 } else {
539 } else {
540 target.find('input').blur(handle_blur);
540 target.find('input').blur(handle_blur);
541 }
541 }
542 });
542 });
543 // There are times (raw_input) where we remove the element from the DOM before
543 // There are times (raw_input) where we remove the element from the DOM before
544 // focusout is called. In this case we bind to the remove event of jQueryUI,
544 // focusout is called. In this case we bind to the remove event of jQueryUI,
545 // which gets triggered upon removal, iff it is focused at the time.
545 // which gets triggered upon removal, iff it is focused at the time.
546 // is_focused must be used to check for the case where an element within
546 // is_focused must be used to check for the case where an element within
547 // the element being removed is focused.
547 // the element being removed is focused.
548 e.on('remove', function () {
548 e.on('remove', function () {
549 if (utils.is_focused(e[0])) {
549 if (utils.is_focused(e[0])) {
550 that.enable();
550 that.enable();
551 }
551 }
552 });
552 });
553 };
553 };
554
554
555 // For backwards compatability.
555 // For backwards compatability.
556 IPython.KeyboardManager = KeyboardManager;
556 IPython.KeyboardManager = KeyboardManager;
557
557
558 return KeyboardManager;
558 return {'KeyboardManager': KeyboardManager};
559
560 });
559 });
@@ -1,58 +1,58 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 define([
4 define([
5 'base/js/namespace',
5 'base/js/namespace',
6 'jquery',
6 'jquery',
7 ], function(IPython, $) {
7 ], function(IPython, $) {
8 "use strict";
8 "use strict";
9
9
10 var LayoutManager = function () {
10 var LayoutManager = function () {
11 this.bind_events();
11 this.bind_events();
12 this.pager = undefined;
12 this.pager = undefined;
13 };
13 };
14
14
15 LayoutManager.prototype.bind_events = function () {
15 LayoutManager.prototype.bind_events = function () {
16 $(window).resize($.proxy(this.do_resize,this));
16 $(window).resize($.proxy(this.do_resize,this));
17 };
17 };
18
18
19 LayoutManager.prototype.app_height = function() {
19 LayoutManager.prototype.app_height = function() {
20 var win = $(window);
20 var win = $(window);
21 var w = win.width();
21 var w = win.width();
22 var h = win.height();
22 var h = win.height();
23 var header_height;
23 var header_height;
24 if ($('div#header').css('display') === 'none') {
24 if ($('div#header').css('display') === 'none') {
25 header_height = 0;
25 header_height = 0;
26 } else {
26 } else {
27 header_height = $('div#header').outerHeight(true);
27 header_height = $('div#header').outerHeight(true);
28 }
28 }
29 var menubar_height;
29 var menubar_height;
30 if ($('div#menubar-container').css('display') === 'none') {
30 if ($('div#menubar-container').css('display') === 'none') {
31 menubar_height = 0;
31 menubar_height = 0;
32 } else {
32 } else {
33 menubar_height = $('div#menubar-container').outerHeight(true);
33 menubar_height = $('div#menubar-container').outerHeight(true);
34 }
34 }
35 return h-header_height-menubar_height; // content height
35 return h-header_height-menubar_height; // content height
36 };
36 };
37
37
38 LayoutManager.prototype.do_resize = function () {
38 LayoutManager.prototype.do_resize = function () {
39 var app_height = this.app_height(); // content height
39 var app_height = this.app_height(); // content height
40
40
41 $('#ipython-main-app').height(app_height); // content+padding+border height
41 $('#ipython-main-app').height(app_height); // content+padding+border height
42 if (this.pager) {
42 if (this.pager) {
43 var pager_height = this.pager.percentage_height*app_height;
43 var pager_height = this.pager.percentage_height*app_height;
44 var pager_splitter_height = $('div#pager_splitter').outerHeight(true);
44 var pager_splitter_height = $('div#pager_splitter').outerHeight(true);
45 $('div#pager').outerHeight(pager_height);
45 $('div#pager').outerHeight(pager_height);
46 if (this.pager.expanded) {
46 if (this.pager.expanded) {
47 $('div#notebook').outerHeight(app_height-pager_height-pager_splitter_height);
47 $('div#notebook').outerHeight(app_height-pager_height-pager_splitter_height);
48 } else {
48 } else {
49 $('div#notebook').outerHeight(app_height-pager_splitter_height);
49 $('div#notebook').outerHeight(app_height-pager_splitter_height);
50 }
50 }
51 }
51 }
52 };
52 };
53
53
54 // Backwards compatability.
54 // Backwards compatability.
55 IPython.LayoutManager = LayoutManager;
55 IPython.LayoutManager = LayoutManager;
56
56
57 return LayoutManager;
57 return {'LayoutManager': LayoutManager};
58 });
58 });
@@ -1,220 +1,220 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 define([
4 define([
5 'base/js/namespace',
5 'base/js/namespace',
6 'jquery',
6 'jquery',
7 'notebook/js/toolbar',
7 'notebook/js/toolbar',
8 'notebook/js/celltoolbar',
8 'notebook/js/celltoolbar',
9 ], function(IPython, $, Toolbar, CellToolbar) {
9 ], function(IPython, $, Toolbar, CellToolbar) {
10 "use strict";
10 "use strict";
11
11
12 var MainToolBar = function (selector, layout_manager, notebook, events) {
12 var MainToolBar = function (selector, layout_manager, notebook, events) {
13 Toolbar.apply(this, arguments);
13 Toolbar.apply(this, arguments);
14 this.events = events;
14 this.events = events;
15 this.notebook = notebook;
15 this.notebook = notebook;
16 this.construct();
16 this.construct();
17 this.add_celltype_list();
17 this.add_celltype_list();
18 this.add_celltoolbar_list();
18 this.add_celltoolbar_list();
19 this.bind_events();
19 this.bind_events();
20 };
20 };
21
21
22 MainToolBar.prototype = new Toolbar();
22 MainToolBar.prototype = new Toolbar();
23
23
24 MainToolBar.prototype.construct = function () {
24 MainToolBar.prototype.construct = function () {
25 this.add_buttons_group([
25 this.add_buttons_group([
26 {
26 {
27 id : 'save_b',
27 id : 'save_b',
28 label : 'Save and Checkpoint',
28 label : 'Save and Checkpoint',
29 icon : 'icon-save',
29 icon : 'icon-save',
30 callback : function () {
30 callback : function () {
31 this.notebook.save_checkpoint();
31 this.notebook.save_checkpoint();
32 }
32 }
33 }
33 }
34 ]);
34 ]);
35
35
36 this.add_buttons_group([
36 this.add_buttons_group([
37 {
37 {
38 id : 'insert_below_b',
38 id : 'insert_below_b',
39 label : 'Insert Cell Below',
39 label : 'Insert Cell Below',
40 icon : 'icon-plus-sign',
40 icon : 'icon-plus-sign',
41 callback : function () {
41 callback : function () {
42 this.notebook.insert_cell_below('code');
42 this.notebook.insert_cell_below('code');
43 this.notebook.select_next();
43 this.notebook.select_next();
44 this.notebook.focus_cell();
44 this.notebook.focus_cell();
45 }
45 }
46 }
46 }
47 ],'insert_above_below');
47 ],'insert_above_below');
48
48
49 this.add_buttons_group([
49 this.add_buttons_group([
50 {
50 {
51 id : 'cut_b',
51 id : 'cut_b',
52 label : 'Cut Cell',
52 label : 'Cut Cell',
53 icon : 'icon-cut',
53 icon : 'icon-cut',
54 callback : function () {
54 callback : function () {
55 this.notebook.cut_cell();
55 this.notebook.cut_cell();
56 }
56 }
57 },
57 },
58 {
58 {
59 id : 'copy_b',
59 id : 'copy_b',
60 label : 'Copy Cell',
60 label : 'Copy Cell',
61 icon : 'icon-copy',
61 icon : 'icon-copy',
62 callback : function () {
62 callback : function () {
63 this.notebook.copy_cell();
63 this.notebook.copy_cell();
64 }
64 }
65 },
65 },
66 {
66 {
67 id : 'paste_b',
67 id : 'paste_b',
68 label : 'Paste Cell Below',
68 label : 'Paste Cell Below',
69 icon : 'icon-paste',
69 icon : 'icon-paste',
70 callback : function () {
70 callback : function () {
71 this.notebook.paste_cell_below();
71 this.notebook.paste_cell_below();
72 }
72 }
73 }
73 }
74 ],'cut_copy_paste');
74 ],'cut_copy_paste');
75
75
76 this.add_buttons_group([
76 this.add_buttons_group([
77 {
77 {
78 id : 'move_up_b',
78 id : 'move_up_b',
79 label : 'Move Cell Up',
79 label : 'Move Cell Up',
80 icon : 'icon-arrow-up',
80 icon : 'icon-arrow-up',
81 callback : function () {
81 callback : function () {
82 this.notebook.move_cell_up();
82 this.notebook.move_cell_up();
83 }
83 }
84 },
84 },
85 {
85 {
86 id : 'move_down_b',
86 id : 'move_down_b',
87 label : 'Move Cell Down',
87 label : 'Move Cell Down',
88 icon : 'icon-arrow-down',
88 icon : 'icon-arrow-down',
89 callback : function () {
89 callback : function () {
90 this.notebook.move_cell_down();
90 this.notebook.move_cell_down();
91 }
91 }
92 }
92 }
93 ],'move_up_down');
93 ],'move_up_down');
94
94
95
95
96 this.add_buttons_group([
96 this.add_buttons_group([
97 {
97 {
98 id : 'run_b',
98 id : 'run_b',
99 label : 'Run Cell',
99 label : 'Run Cell',
100 icon : 'icon-play',
100 icon : 'icon-play',
101 callback : function () {
101 callback : function () {
102 // emulate default shift-enter behavior
102 // emulate default shift-enter behavior
103 this.notebook.execute_cell_and_select_below();
103 this.notebook.execute_cell_and_select_below();
104 }
104 }
105 },
105 },
106 {
106 {
107 id : 'interrupt_b',
107 id : 'interrupt_b',
108 label : 'Interrupt',
108 label : 'Interrupt',
109 icon : 'icon-stop',
109 icon : 'icon-stop',
110 callback : function () {
110 callback : function () {
111 this.notebook.session.interrupt_kernel();
111 this.notebook.session.interrupt_kernel();
112 }
112 }
113 },
113 },
114 {
114 {
115 id : 'repeat_b',
115 id : 'repeat_b',
116 label : 'Restart Kernel',
116 label : 'Restart Kernel',
117 icon : 'icon-repeat',
117 icon : 'icon-repeat',
118 callback : function () {
118 callback : function () {
119 this.notebook.restart_kernel();
119 this.notebook.restart_kernel();
120 }
120 }
121 }
121 }
122 ],'run_int');
122 ],'run_int');
123 };
123 };
124
124
125 MainToolBar.prototype.add_celltype_list = function () {
125 MainToolBar.prototype.add_celltype_list = function () {
126 this.element
126 this.element
127 .append($('<select/>')
127 .append($('<select/>')
128 .attr('id','cell_type')
128 .attr('id','cell_type')
129 .addClass('form-control select-xs')
129 .addClass('form-control select-xs')
130 // .addClass('ui-widget-content')
130 // .addClass('ui-widget-content')
131 .append($('<option/>').attr('value','code').text('Code'))
131 .append($('<option/>').attr('value','code').text('Code'))
132 .append($('<option/>').attr('value','markdown').text('Markdown'))
132 .append($('<option/>').attr('value','markdown').text('Markdown'))
133 .append($('<option/>').attr('value','raw').text('Raw NBConvert'))
133 .append($('<option/>').attr('value','raw').text('Raw NBConvert'))
134 .append($('<option/>').attr('value','heading1').text('Heading 1'))
134 .append($('<option/>').attr('value','heading1').text('Heading 1'))
135 .append($('<option/>').attr('value','heading2').text('Heading 2'))
135 .append($('<option/>').attr('value','heading2').text('Heading 2'))
136 .append($('<option/>').attr('value','heading3').text('Heading 3'))
136 .append($('<option/>').attr('value','heading3').text('Heading 3'))
137 .append($('<option/>').attr('value','heading4').text('Heading 4'))
137 .append($('<option/>').attr('value','heading4').text('Heading 4'))
138 .append($('<option/>').attr('value','heading5').text('Heading 5'))
138 .append($('<option/>').attr('value','heading5').text('Heading 5'))
139 .append($('<option/>').attr('value','heading6').text('Heading 6'))
139 .append($('<option/>').attr('value','heading6').text('Heading 6'))
140 );
140 );
141 };
141 };
142
142
143
143
144 MainToolBar.prototype.add_celltoolbar_list = function () {
144 MainToolBar.prototype.add_celltoolbar_list = function () {
145 var label = $('<span/>').addClass("navbar-text").text('Cell Toolbar:');
145 var label = $('<span/>').addClass("navbar-text").text('Cell Toolbar:');
146 var select = $('<select/>')
146 var select = $('<select/>')
147 // .addClass('ui-widget-content')
147 // .addClass('ui-widget-content')
148 .attr('id', 'ctb_select')
148 .attr('id', 'ctb_select')
149 .addClass('form-control select-xs')
149 .addClass('form-control select-xs')
150 .append($('<option/>').attr('value', '').text('None'));
150 .append($('<option/>').attr('value', '').text('None'));
151 this.element.append(label).append(select);
151 this.element.append(label).append(select);
152 select.change(function() {
152 select.change(function() {
153 var val = $(this).val();
153 var val = $(this).val();
154 if (val ==='') {
154 if (val ==='') {
155 CellToolbar.global_hide();
155 CellToolbar.global_hide();
156 delete this.notebook.metadata.celltoolbar;
156 delete this.notebook.metadata.celltoolbar;
157 } else {
157 } else {
158 CellToolbar.global_show();
158 CellToolbar.global_show();
159 CellToolbar.activate_preset(val);
159 CellToolbar.activate_preset(val);
160 this.notebook.metadata.celltoolbar = val;
160 this.notebook.metadata.celltoolbar = val;
161 }
161 }
162 });
162 });
163 // Setup the currently registered presets.
163 // Setup the currently registered presets.
164 var presets = CellToolbar.list_presets();
164 var presets = CellToolbar.list_presets();
165 for (var i=0; i<presets.length; i++) {
165 for (var i=0; i<presets.length; i++) {
166 var name = presets[i];
166 var name = presets[i];
167 select.append($('<option/>').attr('value', name).text(name));
167 select.append($('<option/>').attr('value', name).text(name));
168 }
168 }
169 // Setup future preset registrations.
169 // Setup future preset registrations.
170 this.events.on('preset_added.CellToolbar', function (event, data) {
170 this.events.on('preset_added.CellToolbar', function (event, data) {
171 var name = data.name;
171 var name = data.name;
172 select.append($('<option/>').attr('value', name).text(name));
172 select.append($('<option/>').attr('value', name).text(name));
173 });
173 });
174 // Update select value when a preset is activated.
174 // Update select value when a preset is activated.
175 this.events.on('preset_activated.CellToolbar', function (event, data) {
175 this.events.on('preset_activated.CellToolbar', function (event, data) {
176 if (select.val() !== data.name)
176 if (select.val() !== data.name)
177 select.val(data.name);
177 select.val(data.name);
178 });
178 });
179 };
179 };
180
180
181
181
182 MainToolBar.prototype.bind_events = function () {
182 MainToolBar.prototype.bind_events = function () {
183 var that = this;
183 var that = this;
184
184
185 this.element.find('#cell_type').change(function () {
185 this.element.find('#cell_type').change(function () {
186 var cell_type = $(this).val();
186 var cell_type = $(this).val();
187 if (cell_type === 'code') {
187 if (cell_type === 'code') {
188 this.notebook.to_code();
188 this.notebook.to_code();
189 } else if (cell_type === 'markdown') {
189 } else if (cell_type === 'markdown') {
190 this.notebook.to_markdown();
190 this.notebook.to_markdown();
191 } else if (cell_type === 'raw') {
191 } else if (cell_type === 'raw') {
192 this.notebook.to_raw();
192 this.notebook.to_raw();
193 } else if (cell_type === 'heading1') {
193 } else if (cell_type === 'heading1') {
194 this.notebook.to_heading(undefined, 1);
194 this.notebook.to_heading(undefined, 1);
195 } else if (cell_type === 'heading2') {
195 } else if (cell_type === 'heading2') {
196 this.notebook.to_heading(undefined, 2);
196 this.notebook.to_heading(undefined, 2);
197 } else if (cell_type === 'heading3') {
197 } else if (cell_type === 'heading3') {
198 this.notebook.to_heading(undefined, 3);
198 this.notebook.to_heading(undefined, 3);
199 } else if (cell_type === 'heading4') {
199 } else if (cell_type === 'heading4') {
200 this.notebook.to_heading(undefined, 4);
200 this.notebook.to_heading(undefined, 4);
201 } else if (cell_type === 'heading5') {
201 } else if (cell_type === 'heading5') {
202 this.notebook.to_heading(undefined, 5);
202 this.notebook.to_heading(undefined, 5);
203 } else if (cell_type === 'heading6') {
203 } else if (cell_type === 'heading6') {
204 this.notebook.to_heading(undefined, 6);
204 this.notebook.to_heading(undefined, 6);
205 }
205 }
206 });
206 });
207 this.events.on('selected_cell_type_changed.Notebook', function (event, data) {
207 this.events.on('selected_cell_type_changed.Notebook', function (event, data) {
208 if (data.cell_type === 'heading') {
208 if (data.cell_type === 'heading') {
209 that.element.find('#cell_type').val(data.cell_type+data.level);
209 that.element.find('#cell_type').val(data.cell_type+data.level);
210 } else {
210 } else {
211 that.element.find('#cell_type').val(data.cell_type);
211 that.element.find('#cell_type').val(data.cell_type);
212 }
212 }
213 });
213 });
214 };
214 };
215
215
216 // Backwards compatability.
216 // Backwards compatability.
217 IPython.MainToolBar = MainToolBar;
217 IPython.MainToolBar = MainToolBar;
218
218
219 return MainToolBar;
219 return {'MainToolBar': MainToolBar};
220 });
220 });
@@ -1,347 +1,347 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 define([
4 define([
5 'base/js/namespace',
5 'base/js/namespace',
6 'jquery',
6 'jquery',
7 'base/js/utils',
7 'base/js/utils',
8 'notebook/js/tour',
8 'notebook/js/tour',
9 'components/bootstrap-tour/build/js/bootstrap-tour.min',
9 'components/bootstrap-tour/build/js/bootstrap-tour.min',
10 ], function(IPython, $, utils, Tour) {
10 ], function(IPython, $, utils, Tour) {
11 "use strict";
11 "use strict";
12
12
13 /**
13 /**
14 * A MenuBar Class to generate the menubar of IPython notebook
14 * A MenuBar Class to generate the menubar of IPython notebook
15 * @Class MenuBar
15 * @Class MenuBar
16 *
16 *
17 * @constructor
17 * @constructor
18 *
18 *
19 *
19 *
20 * @param selector {string} selector for the menubar element in DOM
20 * @param selector {string} selector for the menubar element in DOM
21 * @param {object} [options]
21 * @param {object} [options]
22 * @param [options.base_url] {String} String to use for the
22 * @param [options.base_url] {String} String to use for the
23 * base project url. Default is to inspect
23 * base project url. Default is to inspect
24 * $('body').data('baseUrl');
24 * $('body').data('baseUrl');
25 * does not support change for now is set through this option
25 * does not support change for now is set through this option
26 */
26 */
27 var MenuBar = function (selector, options, notebook, layout_manager, events, save_widget, quick_help) {
27 var MenuBar = function (selector, options, notebook, layout_manager, events, save_widget, quick_help) {
28 options = options || {};
28 options = options || {};
29 this.base_url = options.base_url || utils.get_body_data("baseUrl");
29 this.base_url = options.base_url || utils.get_body_data("baseUrl");
30 this.selector = selector;
30 this.selector = selector;
31 this.notebook = notebook;
31 this.notebook = notebook;
32 this.layout_manager = layout_manager;
32 this.layout_manager = layout_manager;
33 this.events = events;
33 this.events = events;
34 this.save_widget = save_widget;
34 this.save_widget = save_widget;
35 this.quick_help = quick_help;
35 this.quick_help = quick_help;
36
36
37 try {
37 try {
38 this.tour = new Tour(notebook, events);
38 this.tour = new Tour(notebook, events);
39 } catch (e) {
39 } catch (e) {
40 this.tour = undefined;
40 this.tour = undefined;
41 console.log("Failed to instantiate Notebook Tour", e);
41 console.log("Failed to instantiate Notebook Tour", e);
42 }
42 }
43
43
44 if (this.selector !== undefined) {
44 if (this.selector !== undefined) {
45 this.element = $(selector);
45 this.element = $(selector);
46 this.style();
46 this.style();
47 this.bind_events();
47 this.bind_events();
48 }
48 }
49 };
49 };
50
50
51 MenuBar.prototype.style = function () {
51 MenuBar.prototype.style = function () {
52 var that = this;
52 var that = this;
53 this.element.addClass('border-box-sizing');
53 this.element.addClass('border-box-sizing');
54 this.element.find("li").click(function (event, ui) {
54 this.element.find("li").click(function (event, ui) {
55 // The selected cell loses focus when the menu is entered, so we
55 // The selected cell loses focus when the menu is entered, so we
56 // re-select it upon selection.
56 // re-select it upon selection.
57 var i = that.notebook.get_selected_index();
57 var i = that.notebook.get_selected_index();
58 that.notebook.select(i);
58 that.notebook.select(i);
59 }
59 }
60 );
60 );
61 };
61 };
62
62
63 MenuBar.prototype._nbconvert = function (format, download) {
63 MenuBar.prototype._nbconvert = function (format, download) {
64 download = download || false;
64 download = download || false;
65 var notebook_path = this.notebook.notebook_path;
65 var notebook_path = this.notebook.notebook_path;
66 var notebook_name = this.notebook.notebook_name;
66 var notebook_name = this.notebook.notebook_name;
67 if (this.notebook.dirty) {
67 if (this.notebook.dirty) {
68 this.notebook.save_notebook({async : false});
68 this.notebook.save_notebook({async : false});
69 }
69 }
70 var url = utils.url_join_encode(
70 var url = utils.url_join_encode(
71 this.base_url,
71 this.base_url,
72 'nbconvert',
72 'nbconvert',
73 format,
73 format,
74 notebook_path,
74 notebook_path,
75 notebook_name
75 notebook_name
76 ) + "?download=" + download.toString();
76 ) + "?download=" + download.toString();
77
77
78 window.open(url);
78 window.open(url);
79 };
79 };
80
80
81 MenuBar.prototype.bind_events = function () {
81 MenuBar.prototype.bind_events = function () {
82 // File
82 // File
83 var that = this;
83 var that = this;
84 this.element.find('#new_notebook').click(function () {
84 this.element.find('#new_notebook').click(function () {
85 that.notebook.new_notebook();
85 that.notebook.new_notebook();
86 });
86 });
87 this.element.find('#open_notebook').click(function () {
87 this.element.find('#open_notebook').click(function () {
88 window.open(utils.url_join_encode(
88 window.open(utils.url_join_encode(
89 that.notebook.base_url,
89 that.notebook.base_url,
90 'tree',
90 'tree',
91 that.notebook.notebook_path
91 that.notebook.notebook_path
92 ));
92 ));
93 });
93 });
94 this.element.find('#copy_notebook').click(function () {
94 this.element.find('#copy_notebook').click(function () {
95 that.notebook.copy_notebook();
95 that.notebook.copy_notebook();
96 return false;
96 return false;
97 });
97 });
98 this.element.find('#download_ipynb').click(function () {
98 this.element.find('#download_ipynb').click(function () {
99 var base_url = that.notebook.base_url;
99 var base_url = that.notebook.base_url;
100 var notebook_path = that.notebook.notebook_path;
100 var notebook_path = that.notebook.notebook_path;
101 var notebook_name = that.notebook.notebook_name;
101 var notebook_name = that.notebook.notebook_name;
102 if (that.notebook.dirty) {
102 if (that.notebook.dirty) {
103 that.notebook.save_notebook({async : false});
103 that.notebook.save_notebook({async : false});
104 }
104 }
105
105
106 var url = utils.url_join_encode(
106 var url = utils.url_join_encode(
107 base_url,
107 base_url,
108 'files',
108 'files',
109 notebook_path,
109 notebook_path,
110 notebook_name
110 notebook_name
111 );
111 );
112 window.location.assign(url);
112 window.location.assign(url);
113 });
113 });
114
114
115 this.element.find('#print_preview').click(function () {
115 this.element.find('#print_preview').click(function () {
116 that._nbconvert('html', false);
116 that._nbconvert('html', false);
117 });
117 });
118
118
119 this.element.find('#download_py').click(function () {
119 this.element.find('#download_py').click(function () {
120 that._nbconvert('python', true);
120 that._nbconvert('python', true);
121 });
121 });
122
122
123 this.element.find('#download_html').click(function () {
123 this.element.find('#download_html').click(function () {
124 that._nbconvert('html', true);
124 that._nbconvert('html', true);
125 });
125 });
126
126
127 this.element.find('#download_rst').click(function () {
127 this.element.find('#download_rst').click(function () {
128 that._nbconvert('rst', true);
128 that._nbconvert('rst', true);
129 });
129 });
130
130
131 this.element.find('#download_pdf').click(function () {
131 this.element.find('#download_pdf').click(function () {
132 that._nbconvert('pdf', true);
132 that._nbconvert('pdf', true);
133 });
133 });
134
134
135 this.element.find('#rename_notebook').click(function () {
135 this.element.find('#rename_notebook').click(function () {
136 that.save_widget.rename_notebook();
136 that.save_widget.rename_notebook();
137 });
137 });
138 this.element.find('#save_checkpoint').click(function () {
138 this.element.find('#save_checkpoint').click(function () {
139 that.notebook.save_checkpoint();
139 that.notebook.save_checkpoint();
140 });
140 });
141 this.element.find('#restore_checkpoint').click(function () {
141 this.element.find('#restore_checkpoint').click(function () {
142 });
142 });
143 this.element.find('#trust_notebook').click(function () {
143 this.element.find('#trust_notebook').click(function () {
144 that.notebook.trust_notebook();
144 that.notebook.trust_notebook();
145 });
145 });
146 this.events.on('trust_changed.Notebook', function (event, trusted) {
146 this.events.on('trust_changed.Notebook', function (event, trusted) {
147 if (trusted) {
147 if (trusted) {
148 that.element.find('#trust_notebook')
148 that.element.find('#trust_notebook')
149 .addClass("disabled")
149 .addClass("disabled")
150 .find("a").text("Trusted Notebook");
150 .find("a").text("Trusted Notebook");
151 } else {
151 } else {
152 that.element.find('#trust_notebook')
152 that.element.find('#trust_notebook')
153 .removeClass("disabled")
153 .removeClass("disabled")
154 .find("a").text("Trust Notebook");
154 .find("a").text("Trust Notebook");
155 }
155 }
156 });
156 });
157 this.element.find('#kill_and_exit').click(function () {
157 this.element.find('#kill_and_exit').click(function () {
158 that.notebook.session.delete();
158 that.notebook.session.delete();
159 setTimeout(function(){
159 setTimeout(function(){
160 // allow closing of new tabs in Chromium, impossible in FF
160 // allow closing of new tabs in Chromium, impossible in FF
161 window.open('', '_self', '');
161 window.open('', '_self', '');
162 window.close();
162 window.close();
163 }, 500);
163 }, 500);
164 });
164 });
165 // Edit
165 // Edit
166 this.element.find('#cut_cell').click(function () {
166 this.element.find('#cut_cell').click(function () {
167 that.notebook.cut_cell();
167 that.notebook.cut_cell();
168 });
168 });
169 this.element.find('#copy_cell').click(function () {
169 this.element.find('#copy_cell').click(function () {
170 that.notebook.copy_cell();
170 that.notebook.copy_cell();
171 });
171 });
172 this.element.find('#delete_cell').click(function () {
172 this.element.find('#delete_cell').click(function () {
173 that.notebook.delete_cell();
173 that.notebook.delete_cell();
174 });
174 });
175 this.element.find('#undelete_cell').click(function () {
175 this.element.find('#undelete_cell').click(function () {
176 that.notebook.undelete_cell();
176 that.notebook.undelete_cell();
177 });
177 });
178 this.element.find('#split_cell').click(function () {
178 this.element.find('#split_cell').click(function () {
179 that.notebook.split_cell();
179 that.notebook.split_cell();
180 });
180 });
181 this.element.find('#merge_cell_above').click(function () {
181 this.element.find('#merge_cell_above').click(function () {
182 that.notebook.merge_cell_above();
182 that.notebook.merge_cell_above();
183 });
183 });
184 this.element.find('#merge_cell_below').click(function () {
184 this.element.find('#merge_cell_below').click(function () {
185 that.notebook.merge_cell_below();
185 that.notebook.merge_cell_below();
186 });
186 });
187 this.element.find('#move_cell_up').click(function () {
187 this.element.find('#move_cell_up').click(function () {
188 that.notebook.move_cell_up();
188 that.notebook.move_cell_up();
189 });
189 });
190 this.element.find('#move_cell_down').click(function () {
190 this.element.find('#move_cell_down').click(function () {
191 that.notebook.move_cell_down();
191 that.notebook.move_cell_down();
192 });
192 });
193 this.element.find('#edit_nb_metadata').click(function () {
193 this.element.find('#edit_nb_metadata').click(function () {
194 that.notebook.edit_metadata();
194 that.notebook.edit_metadata();
195 });
195 });
196
196
197 // View
197 // View
198 this.element.find('#toggle_header').click(function () {
198 this.element.find('#toggle_header').click(function () {
199 $('div#header').toggle();
199 $('div#header').toggle();
200 that.layout_manager.do_resize();
200 that.layout_manager.do_resize();
201 });
201 });
202 this.element.find('#toggle_toolbar').click(function () {
202 this.element.find('#toggle_toolbar').click(function () {
203 $('div#maintoolbar').toggle();
203 $('div#maintoolbar').toggle();
204 that.layout_manager.do_resize();
204 that.layout_manager.do_resize();
205 });
205 });
206 // Insert
206 // Insert
207 this.element.find('#insert_cell_above').click(function () {
207 this.element.find('#insert_cell_above').click(function () {
208 that.notebook.insert_cell_above('code');
208 that.notebook.insert_cell_above('code');
209 that.notebook.select_prev();
209 that.notebook.select_prev();
210 });
210 });
211 this.element.find('#insert_cell_below').click(function () {
211 this.element.find('#insert_cell_below').click(function () {
212 that.notebook.insert_cell_below('code');
212 that.notebook.insert_cell_below('code');
213 that.notebook.select_next();
213 that.notebook.select_next();
214 });
214 });
215 // Cell
215 // Cell
216 this.element.find('#run_cell').click(function () {
216 this.element.find('#run_cell').click(function () {
217 that.notebook.execute_cell();
217 that.notebook.execute_cell();
218 });
218 });
219 this.element.find('#run_cell_select_below').click(function () {
219 this.element.find('#run_cell_select_below').click(function () {
220 that.notebook.execute_cell_and_select_below();
220 that.notebook.execute_cell_and_select_below();
221 });
221 });
222 this.element.find('#run_cell_insert_below').click(function () {
222 this.element.find('#run_cell_insert_below').click(function () {
223 that.notebook.execute_cell_and_insert_below();
223 that.notebook.execute_cell_and_insert_below();
224 });
224 });
225 this.element.find('#run_all_cells').click(function () {
225 this.element.find('#run_all_cells').click(function () {
226 that.notebook.execute_all_cells();
226 that.notebook.execute_all_cells();
227 });
227 });
228 this.element.find('#run_all_cells_above').click(function () {
228 this.element.find('#run_all_cells_above').click(function () {
229 that.notebook.execute_cells_above();
229 that.notebook.execute_cells_above();
230 });
230 });
231 this.element.find('#run_all_cells_below').click(function () {
231 this.element.find('#run_all_cells_below').click(function () {
232 that.notebook.execute_cells_below();
232 that.notebook.execute_cells_below();
233 });
233 });
234 this.element.find('#to_code').click(function () {
234 this.element.find('#to_code').click(function () {
235 that.notebook.to_code();
235 that.notebook.to_code();
236 });
236 });
237 this.element.find('#to_markdown').click(function () {
237 this.element.find('#to_markdown').click(function () {
238 that.notebook.to_markdown();
238 that.notebook.to_markdown();
239 });
239 });
240 this.element.find('#to_raw').click(function () {
240 this.element.find('#to_raw').click(function () {
241 that.notebook.to_raw();
241 that.notebook.to_raw();
242 });
242 });
243 this.element.find('#to_heading1').click(function () {
243 this.element.find('#to_heading1').click(function () {
244 that.notebook.to_heading(undefined, 1);
244 that.notebook.to_heading(undefined, 1);
245 });
245 });
246 this.element.find('#to_heading2').click(function () {
246 this.element.find('#to_heading2').click(function () {
247 that.notebook.to_heading(undefined, 2);
247 that.notebook.to_heading(undefined, 2);
248 });
248 });
249 this.element.find('#to_heading3').click(function () {
249 this.element.find('#to_heading3').click(function () {
250 that.notebook.to_heading(undefined, 3);
250 that.notebook.to_heading(undefined, 3);
251 });
251 });
252 this.element.find('#to_heading4').click(function () {
252 this.element.find('#to_heading4').click(function () {
253 that.notebook.to_heading(undefined, 4);
253 that.notebook.to_heading(undefined, 4);
254 });
254 });
255 this.element.find('#to_heading5').click(function () {
255 this.element.find('#to_heading5').click(function () {
256 that.notebook.to_heading(undefined, 5);
256 that.notebook.to_heading(undefined, 5);
257 });
257 });
258 this.element.find('#to_heading6').click(function () {
258 this.element.find('#to_heading6').click(function () {
259 that.notebook.to_heading(undefined, 6);
259 that.notebook.to_heading(undefined, 6);
260 });
260 });
261
261
262 this.element.find('#toggle_current_output').click(function () {
262 this.element.find('#toggle_current_output').click(function () {
263 that.notebook.toggle_output();
263 that.notebook.toggle_output();
264 });
264 });
265 this.element.find('#toggle_current_output_scroll').click(function () {
265 this.element.find('#toggle_current_output_scroll').click(function () {
266 that.notebook.toggle_output_scroll();
266 that.notebook.toggle_output_scroll();
267 });
267 });
268 this.element.find('#clear_current_output').click(function () {
268 this.element.find('#clear_current_output').click(function () {
269 that.notebook.clear_output();
269 that.notebook.clear_output();
270 });
270 });
271
271
272 this.element.find('#toggle_all_output').click(function () {
272 this.element.find('#toggle_all_output').click(function () {
273 that.notebook.toggle_all_output();
273 that.notebook.toggle_all_output();
274 });
274 });
275 this.element.find('#toggle_all_output_scroll').click(function () {
275 this.element.find('#toggle_all_output_scroll').click(function () {
276 that.notebook.toggle_all_output_scroll();
276 that.notebook.toggle_all_output_scroll();
277 });
277 });
278 this.element.find('#clear_all_output').click(function () {
278 this.element.find('#clear_all_output').click(function () {
279 that.notebook.clear_all_output();
279 that.notebook.clear_all_output();
280 });
280 });
281
281
282 // Kernel
282 // Kernel
283 this.element.find('#int_kernel').click(function () {
283 this.element.find('#int_kernel').click(function () {
284 that.notebook.session.interrupt_kernel();
284 that.notebook.session.interrupt_kernel();
285 });
285 });
286 this.element.find('#restart_kernel').click(function () {
286 this.element.find('#restart_kernel').click(function () {
287 that.notebook.restart_kernel();
287 that.notebook.restart_kernel();
288 });
288 });
289 // Help
289 // Help
290 if (this.tour) {
290 if (this.tour) {
291 this.element.find('#notebook_tour').click(function () {
291 this.element.find('#notebook_tour').click(function () {
292 that.tour.start();
292 that.tour.start();
293 });
293 });
294 } else {
294 } else {
295 this.element.find('#notebook_tour').addClass("disabled");
295 this.element.find('#notebook_tour').addClass("disabled");
296 }
296 }
297 this.element.find('#keyboard_shortcuts').click(function () {
297 this.element.find('#keyboard_shortcuts').click(function () {
298 that.quick_help.show_keyboard_shortcuts();
298 that.quick_help.show_keyboard_shortcuts();
299 });
299 });
300
300
301 this.update_restore_checkpoint(null);
301 this.update_restore_checkpoint(null);
302
302
303 this.events.on('checkpoints_listed.Notebook', function (event, data) {
303 this.events.on('checkpoints_listed.Notebook', function (event, data) {
304 that.update_restore_checkpoint(that.notebook.checkpoints);
304 that.update_restore_checkpoint(that.notebook.checkpoints);
305 });
305 });
306
306
307 this.events.on('checkpoint_created.Notebook', function (event, data) {
307 this.events.on('checkpoint_created.Notebook', function (event, data) {
308 that.update_restore_checkpoint(that.notebook.checkpoints);
308 that.update_restore_checkpoint(that.notebook.checkpoints);
309 });
309 });
310 };
310 };
311
311
312 MenuBar.prototype.update_restore_checkpoint = function(checkpoints) {
312 MenuBar.prototype.update_restore_checkpoint = function(checkpoints) {
313 var ul = this.element.find("#restore_checkpoint").find("ul");
313 var ul = this.element.find("#restore_checkpoint").find("ul");
314 ul.empty();
314 ul.empty();
315 if (!checkpoints || checkpoints.length === 0) {
315 if (!checkpoints || checkpoints.length === 0) {
316 ul.append(
316 ul.append(
317 $("<li/>")
317 $("<li/>")
318 .addClass("disabled")
318 .addClass("disabled")
319 .append(
319 .append(
320 $("<a/>")
320 $("<a/>")
321 .text("No checkpoints")
321 .text("No checkpoints")
322 )
322 )
323 );
323 );
324 return;
324 return;
325 }
325 }
326
326
327 var that = this;
327 var that = this;
328 checkpoints.map(function (checkpoint) {
328 checkpoints.map(function (checkpoint) {
329 var d = new Date(checkpoint.last_modified);
329 var d = new Date(checkpoint.last_modified);
330 ul.append(
330 ul.append(
331 $("<li/>").append(
331 $("<li/>").append(
332 $("<a/>")
332 $("<a/>")
333 .attr("href", "#")
333 .attr("href", "#")
334 .text(d.format("mmm dd HH:MM:ss"))
334 .text(d.format("mmm dd HH:MM:ss"))
335 .click(function () {
335 .click(function () {
336 that.notebook.restore_checkpoint_dialog(checkpoint);
336 that.notebook.restore_checkpoint_dialog(checkpoint);
337 })
337 })
338 )
338 )
339 );
339 );
340 });
340 });
341 };
341 };
342
342
343 // Backwards compatability.
343 // Backwards compatability.
344 IPython.MenuBar = MenuBar;
344 IPython.MenuBar = MenuBar;
345
345
346 return MenuBar;
346 return {'MenuBar': MenuBar};
347 });
347 });
@@ -1,2481 +1,2481 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 define([
4 define([
5 'base/js/namespace',
5 'base/js/namespace',
6 'jquery',
6 'jquery',
7 'base/js/utils',
7 'base/js/utils',
8 'base/js/dialog',
8 'base/js/dialog',
9 'notebook/js/textcell',
9 'notebook/js/textcell',
10 'notebook/js/codecell',
10 'notebook/js/codecell',
11 'services/sessions/js/session',
11 'services/sessions/js/session',
12 'notebook/js/celltoolbar',
12 'notebook/js/celltoolbar',
13 'components/marked/lib/marked',
13 'components/marked/lib/marked',
14 'notebook/js/mathjaxutils',
14 'notebook/js/mathjaxutils',
15 'base/js/keyboard',
15 'base/js/keyboard',
16 'components/jquery-ui/ui/minified/jquery-ui.min',
16 'components/jquery-ui/ui/minified/jquery-ui.min',
17 'components/bootstrap/js/bootstrap.min',
17 'components/bootstrap/js/bootstrap.min',
18 ], function (
18 ], function (
19 IPython,
19 IPython,
20 $,
20 $,
21 utils,
21 utils,
22 Dialog,
22 Dialog,
23 Cells,
23 Cells,
24 CodeCell,
24 CodeCell,
25 Session,
25 Session,
26 CellToolbar,
26 CellToolbar,
27 marked,
27 marked,
28 mathjaxutils,
28 mathjaxutils,
29 keyboard
29 keyboard
30 ) {
30 ) {
31
31
32 /**
32 /**
33 * A notebook contains and manages cells.
33 * A notebook contains and manages cells.
34 *
34 *
35 * @class Notebook
35 * @class Notebook
36 * @constructor
36 * @constructor
37 * @param {String} selector A jQuery selector for the notebook's DOM element
37 * @param {String} selector A jQuery selector for the notebook's DOM element
38 * @param {Object} [options] A config object
38 * @param {Object} [options] A config object
39 * @param {Object} [events] An events object
39 * @param {Object} [events] An events object
40 */
40 */
41 var Notebook = function (selector, options, events, keyboard_manager, save_widget, config) {
41 var Notebook = function (selector, options, events, keyboard_manager, save_widget, config) {
42 this.config = config;
42 this.config = config;
43 this.events = events;
43 this.events = events;
44 this.keyboard_manager = keyboard_manager;
44 this.keyboard_manager = keyboard_manager;
45 keyboard_manager.notebook = this;
45 keyboard_manager.notebook = this;
46 this.save_widget = save_widget;
46 this.save_widget = save_widget;
47 save_widget.notebook = this;
47 save_widget.notebook = this;
48
48
49 mathjaxutils.init();
49 mathjaxutils.init();
50
50
51
51
52 window.marked = window.marked || marked;
52 window.marked = window.marked || marked;
53 if (marked) {
53 if (marked) {
54 marked.setOptions({
54 marked.setOptions({
55 gfm : true,
55 gfm : true,
56 tables: true,
56 tables: true,
57 langPrefix: "language-",
57 langPrefix: "language-",
58 highlight: function(code, lang) {
58 highlight: function(code, lang) {
59 if (!lang) {
59 if (!lang) {
60 // no language, no highlight
60 // no language, no highlight
61 return code;
61 return code;
62 }
62 }
63 var highlighted;
63 var highlighted;
64 try {
64 try {
65 highlighted = hljs.highlight(lang, code, false);
65 highlighted = hljs.highlight(lang, code, false);
66 } catch(err) {
66 } catch(err) {
67 highlighted = hljs.highlightAuto(code);
67 highlighted = hljs.highlightAuto(code);
68 }
68 }
69 return highlighted.value;
69 return highlighted.value;
70 }
70 }
71 });
71 });
72 }
72 }
73
73
74 // Backwards compatability.
74 // Backwards compatability.
75 IPython.keyboard_manager = this.keyboard_manager;
75 IPython.keyboard_manager = this.keyboard_manager;
76 IPython.save_widget = this.save_widget;
76 IPython.save_widget = this.save_widget;
77 IPython.keyboard = this.keyboard;
77 IPython.keyboard = this.keyboard;
78
78
79 this.options = options = options || {};
79 this.options = options = options || {};
80 this.base_url = options.base_url;
80 this.base_url = options.base_url;
81 this.notebook_path = options.notebook_path;
81 this.notebook_path = options.notebook_path;
82 this.notebook_name = options.notebook_name;
82 this.notebook_name = options.notebook_name;
83 this.element = $(selector);
83 this.element = $(selector);
84 this.element.scroll();
84 this.element.scroll();
85 this.element.data("notebook", this);
85 this.element.data("notebook", this);
86 this.next_prompt_number = 1;
86 this.next_prompt_number = 1;
87 this.session = null;
87 this.session = null;
88 this.kernel = null;
88 this.kernel = null;
89 this.clipboard = null;
89 this.clipboard = null;
90 this.undelete_backup = null;
90 this.undelete_backup = null;
91 this.undelete_index = null;
91 this.undelete_index = null;
92 this.undelete_below = false;
92 this.undelete_below = false;
93 this.paste_enabled = false;
93 this.paste_enabled = false;
94 // It is important to start out in command mode to match the intial mode
94 // It is important to start out in command mode to match the intial mode
95 // of the KeyboardManager.
95 // of the KeyboardManager.
96 this.mode = 'command';
96 this.mode = 'command';
97 this.set_dirty(false);
97 this.set_dirty(false);
98 this.metadata = {};
98 this.metadata = {};
99 this._checkpoint_after_save = false;
99 this._checkpoint_after_save = false;
100 this.last_checkpoint = null;
100 this.last_checkpoint = null;
101 this.checkpoints = [];
101 this.checkpoints = [];
102 this.autosave_interval = 0;
102 this.autosave_interval = 0;
103 this.autosave_timer = null;
103 this.autosave_timer = null;
104 // autosave *at most* every two minutes
104 // autosave *at most* every two minutes
105 this.minimum_autosave_interval = 120000;
105 this.minimum_autosave_interval = 120000;
106 // single worksheet for now
106 // single worksheet for now
107 this.worksheet_metadata = {};
107 this.worksheet_metadata = {};
108 this.notebook_name_blacklist_re = /[\/\\:]/;
108 this.notebook_name_blacklist_re = /[\/\\:]/;
109 this.nbformat = 3; // Increment this when changing the nbformat
109 this.nbformat = 3; // Increment this when changing the nbformat
110 this.nbformat_minor = 0; // Increment this when changing the nbformat
110 this.nbformat_minor = 0; // Increment this when changing the nbformat
111 this.style();
111 this.style();
112 this.create_elements();
112 this.create_elements();
113 this.bind_events();
113 this.bind_events();
114 this.save_notebook = function() { // don't allow save until notebook_loaded
114 this.save_notebook = function() { // don't allow save until notebook_loaded
115 this.save_notebook_error(null, null, "Load failed, save is disabled");
115 this.save_notebook_error(null, null, "Load failed, save is disabled");
116 };
116 };
117 };
117 };
118
118
119 /**
119 /**
120 * Tweak the notebook's CSS style.
120 * Tweak the notebook's CSS style.
121 *
121 *
122 * @method style
122 * @method style
123 */
123 */
124 Notebook.prototype.style = function () {
124 Notebook.prototype.style = function () {
125 $('div#notebook').addClass('border-box-sizing');
125 $('div#notebook').addClass('border-box-sizing');
126 };
126 };
127
127
128 /**
128 /**
129 * Create an HTML and CSS representation of the notebook.
129 * Create an HTML and CSS representation of the notebook.
130 *
130 *
131 * @method create_elements
131 * @method create_elements
132 */
132 */
133 Notebook.prototype.create_elements = function () {
133 Notebook.prototype.create_elements = function () {
134 var that = this;
134 var that = this;
135 this.element.attr('tabindex','-1');
135 this.element.attr('tabindex','-1');
136 this.container = $("<div/>").addClass("container").attr("id", "notebook-container");
136 this.container = $("<div/>").addClass("container").attr("id", "notebook-container");
137 // We add this end_space div to the end of the notebook div to:
137 // We add this end_space div to the end of the notebook div to:
138 // i) provide a margin between the last cell and the end of the notebook
138 // i) provide a margin between the last cell and the end of the notebook
139 // ii) to prevent the div from scrolling up when the last cell is being
139 // ii) to prevent the div from scrolling up when the last cell is being
140 // edited, but is too low on the page, which browsers will do automatically.
140 // edited, but is too low on the page, which browsers will do automatically.
141 var end_space = $('<div/>').addClass('end_space');
141 var end_space = $('<div/>').addClass('end_space');
142 end_space.dblclick(function (e) {
142 end_space.dblclick(function (e) {
143 var ncells = that.ncells();
143 var ncells = that.ncells();
144 that.insert_cell_below('code',ncells-1);
144 that.insert_cell_below('code',ncells-1);
145 });
145 });
146 this.element.append(this.container);
146 this.element.append(this.container);
147 this.container.append(end_space);
147 this.container.append(end_space);
148 };
148 };
149
149
150 /**
150 /**
151 * Bind JavaScript events: key presses and custom IPython events.
151 * Bind JavaScript events: key presses and custom IPython events.
152 *
152 *
153 * @method bind_events
153 * @method bind_events
154 */
154 */
155 Notebook.prototype.bind_events = function () {
155 Notebook.prototype.bind_events = function () {
156 var that = this;
156 var that = this;
157
157
158 this.events.on('set_next_input.Notebook', function (event, data) {
158 this.events.on('set_next_input.Notebook', function (event, data) {
159 var index = that.find_cell_index(data.cell);
159 var index = that.find_cell_index(data.cell);
160 var new_cell = that.insert_cell_below('code',index);
160 var new_cell = that.insert_cell_below('code',index);
161 new_cell.set_text(data.text);
161 new_cell.set_text(data.text);
162 that.dirty = true;
162 that.dirty = true;
163 });
163 });
164
164
165 this.events.on('set_dirty.Notebook', function (event, data) {
165 this.events.on('set_dirty.Notebook', function (event, data) {
166 that.dirty = data.value;
166 that.dirty = data.value;
167 });
167 });
168
168
169 this.events.on('trust_changed.Notebook', function (event, data) {
169 this.events.on('trust_changed.Notebook', function (event, data) {
170 that.trusted = data.value;
170 that.trusted = data.value;
171 });
171 });
172
172
173 this.events.on('select.Cell', function (event, data) {
173 this.events.on('select.Cell', function (event, data) {
174 var index = that.find_cell_index(data.cell);
174 var index = that.find_cell_index(data.cell);
175 that.select(index);
175 that.select(index);
176 });
176 });
177
177
178 this.events.on('edit_mode.Cell', function (event, data) {
178 this.events.on('edit_mode.Cell', function (event, data) {
179 that.handle_edit_mode(data.cell);
179 that.handle_edit_mode(data.cell);
180 });
180 });
181
181
182 this.events.on('command_mode.Cell', function (event, data) {
182 this.events.on('command_mode.Cell', function (event, data) {
183 that.handle_command_mode(data.cell);
183 that.handle_command_mode(data.cell);
184 });
184 });
185
185
186 this.events.on('status_autorestarting.Kernel', function () {
186 this.events.on('status_autorestarting.Kernel', function () {
187 Dialog.modal({
187 Dialog.modal({
188 title: "Kernel Restarting",
188 title: "Kernel Restarting",
189 body: "The kernel appears to have died. It will restart automatically.",
189 body: "The kernel appears to have died. It will restart automatically.",
190 buttons: {
190 buttons: {
191 OK : {
191 OK : {
192 class : "btn-primary"
192 class : "btn-primary"
193 }
193 }
194 }
194 }
195 });
195 });
196 });
196 });
197
197
198 var collapse_time = function (time) {
198 var collapse_time = function (time) {
199 var app_height = $('#ipython-main-app').height(); // content height
199 var app_height = $('#ipython-main-app').height(); // content height
200 var splitter_height = $('div#pager_splitter').outerHeight(true);
200 var splitter_height = $('div#pager_splitter').outerHeight(true);
201 var new_height = app_height - splitter_height;
201 var new_height = app_height - splitter_height;
202 that.element.animate({height : new_height + 'px'}, time);
202 that.element.animate({height : new_height + 'px'}, time);
203 };
203 };
204
204
205 this.element.bind('collapse_pager', function (event, extrap) {
205 this.element.bind('collapse_pager', function (event, extrap) {
206 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
206 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
207 collapse_time(time);
207 collapse_time(time);
208 });
208 });
209
209
210 var expand_time = function (time) {
210 var expand_time = function (time) {
211 var app_height = $('#ipython-main-app').height(); // content height
211 var app_height = $('#ipython-main-app').height(); // content height
212 var splitter_height = $('div#pager_splitter').outerHeight(true);
212 var splitter_height = $('div#pager_splitter').outerHeight(true);
213 var pager_height = $('div#pager').outerHeight(true);
213 var pager_height = $('div#pager').outerHeight(true);
214 var new_height = app_height - pager_height - splitter_height;
214 var new_height = app_height - pager_height - splitter_height;
215 that.element.animate({height : new_height + 'px'}, time);
215 that.element.animate({height : new_height + 'px'}, time);
216 };
216 };
217
217
218 this.element.bind('expand_pager', function (event, extrap) {
218 this.element.bind('expand_pager', function (event, extrap) {
219 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
219 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
220 expand_time(time);
220 expand_time(time);
221 });
221 });
222
222
223 // Firefox 22 broke $(window).on("beforeunload")
223 // Firefox 22 broke $(window).on("beforeunload")
224 // I'm not sure why or how.
224 // I'm not sure why or how.
225 window.onbeforeunload = function (e) {
225 window.onbeforeunload = function (e) {
226 // TODO: Make killing the kernel configurable.
226 // TODO: Make killing the kernel configurable.
227 var kill_kernel = false;
227 var kill_kernel = false;
228 if (kill_kernel) {
228 if (kill_kernel) {
229 that.session.kill_kernel();
229 that.session.kill_kernel();
230 }
230 }
231 // if we are autosaving, trigger an autosave on nav-away.
231 // if we are autosaving, trigger an autosave on nav-away.
232 // still warn, because if we don't the autosave may fail.
232 // still warn, because if we don't the autosave may fail.
233 if (that.dirty) {
233 if (that.dirty) {
234 if ( that.autosave_interval ) {
234 if ( that.autosave_interval ) {
235 // schedule autosave in a timeout
235 // schedule autosave in a timeout
236 // this gives you a chance to forcefully discard changes
236 // this gives you a chance to forcefully discard changes
237 // by reloading the page if you *really* want to.
237 // by reloading the page if you *really* want to.
238 // the timer doesn't start until you *dismiss* the dialog.
238 // the timer doesn't start until you *dismiss* the dialog.
239 setTimeout(function () {
239 setTimeout(function () {
240 if (that.dirty) {
240 if (that.dirty) {
241 that.save_notebook();
241 that.save_notebook();
242 }
242 }
243 }, 1000);
243 }, 1000);
244 return "Autosave in progress, latest changes may be lost.";
244 return "Autosave in progress, latest changes may be lost.";
245 } else {
245 } else {
246 return "Unsaved changes will be lost.";
246 return "Unsaved changes will be lost.";
247 }
247 }
248 }
248 }
249 // Null is the *only* return value that will make the browser not
249 // Null is the *only* return value that will make the browser not
250 // pop up the "don't leave" dialog.
250 // pop up the "don't leave" dialog.
251 return null;
251 return null;
252 };
252 };
253 };
253 };
254
254
255 /**
255 /**
256 * Set the dirty flag, and trigger the set_dirty.Notebook event
256 * Set the dirty flag, and trigger the set_dirty.Notebook event
257 *
257 *
258 * @method set_dirty
258 * @method set_dirty
259 */
259 */
260 Notebook.prototype.set_dirty = function (value) {
260 Notebook.prototype.set_dirty = function (value) {
261 if (value === undefined) {
261 if (value === undefined) {
262 value = true;
262 value = true;
263 }
263 }
264 if (this.dirty == value) {
264 if (this.dirty == value) {
265 return;
265 return;
266 }
266 }
267 this.events.trigger('set_dirty.Notebook', {value: value});
267 this.events.trigger('set_dirty.Notebook', {value: value});
268 };
268 };
269
269
270 /**
270 /**
271 * Scroll the top of the page to a given cell.
271 * Scroll the top of the page to a given cell.
272 *
272 *
273 * @method scroll_to_cell
273 * @method scroll_to_cell
274 * @param {Number} cell_number An index of the cell to view
274 * @param {Number} cell_number An index of the cell to view
275 * @param {Number} time Animation time in milliseconds
275 * @param {Number} time Animation time in milliseconds
276 * @return {Number} Pixel offset from the top of the container
276 * @return {Number} Pixel offset from the top of the container
277 */
277 */
278 Notebook.prototype.scroll_to_cell = function (cell_number, time) {
278 Notebook.prototype.scroll_to_cell = function (cell_number, time) {
279 var cells = this.get_cells();
279 var cells = this.get_cells();
280 time = time || 0;
280 time = time || 0;
281 cell_number = Math.min(cells.length-1,cell_number);
281 cell_number = Math.min(cells.length-1,cell_number);
282 cell_number = Math.max(0 ,cell_number);
282 cell_number = Math.max(0 ,cell_number);
283 var scroll_value = cells[cell_number].element.position().top-cells[0].element.position().top ;
283 var scroll_value = cells[cell_number].element.position().top-cells[0].element.position().top ;
284 this.element.animate({scrollTop:scroll_value}, time);
284 this.element.animate({scrollTop:scroll_value}, time);
285 return scroll_value;
285 return scroll_value;
286 };
286 };
287
287
288 /**
288 /**
289 * Scroll to the bottom of the page.
289 * Scroll to the bottom of the page.
290 *
290 *
291 * @method scroll_to_bottom
291 * @method scroll_to_bottom
292 */
292 */
293 Notebook.prototype.scroll_to_bottom = function () {
293 Notebook.prototype.scroll_to_bottom = function () {
294 this.element.animate({scrollTop:this.element.get(0).scrollHeight}, 0);
294 this.element.animate({scrollTop:this.element.get(0).scrollHeight}, 0);
295 };
295 };
296
296
297 /**
297 /**
298 * Scroll to the top of the page.
298 * Scroll to the top of the page.
299 *
299 *
300 * @method scroll_to_top
300 * @method scroll_to_top
301 */
301 */
302 Notebook.prototype.scroll_to_top = function () {
302 Notebook.prototype.scroll_to_top = function () {
303 this.element.animate({scrollTop:0}, 0);
303 this.element.animate({scrollTop:0}, 0);
304 };
304 };
305
305
306 // Edit Notebook metadata
306 // Edit Notebook metadata
307
307
308 Notebook.prototype.edit_metadata = function () {
308 Notebook.prototype.edit_metadata = function () {
309 var that = this;
309 var that = this;
310 Dialog.edit_metadata(this.metadata, function (md) {
310 Dialog.edit_metadata(this.metadata, function (md) {
311 that.metadata = md;
311 that.metadata = md;
312 }, 'Notebook');
312 }, 'Notebook');
313 };
313 };
314
314
315 // Cell indexing, retrieval, etc.
315 // Cell indexing, retrieval, etc.
316
316
317 /**
317 /**
318 * Get all cell elements in the notebook.
318 * Get all cell elements in the notebook.
319 *
319 *
320 * @method get_cell_elements
320 * @method get_cell_elements
321 * @return {jQuery} A selector of all cell elements
321 * @return {jQuery} A selector of all cell elements
322 */
322 */
323 Notebook.prototype.get_cell_elements = function () {
323 Notebook.prototype.get_cell_elements = function () {
324 return this.container.children("div.cell");
324 return this.container.children("div.cell");
325 };
325 };
326
326
327 /**
327 /**
328 * Get a particular cell element.
328 * Get a particular cell element.
329 *
329 *
330 * @method get_cell_element
330 * @method get_cell_element
331 * @param {Number} index An index of a cell to select
331 * @param {Number} index An index of a cell to select
332 * @return {jQuery} A selector of the given cell.
332 * @return {jQuery} A selector of the given cell.
333 */
333 */
334 Notebook.prototype.get_cell_element = function (index) {
334 Notebook.prototype.get_cell_element = function (index) {
335 var result = null;
335 var result = null;
336 var e = this.get_cell_elements().eq(index);
336 var e = this.get_cell_elements().eq(index);
337 if (e.length !== 0) {
337 if (e.length !== 0) {
338 result = e;
338 result = e;
339 }
339 }
340 return result;
340 return result;
341 };
341 };
342
342
343 /**
343 /**
344 * Try to get a particular cell by msg_id.
344 * Try to get a particular cell by msg_id.
345 *
345 *
346 * @method get_msg_cell
346 * @method get_msg_cell
347 * @param {String} msg_id A message UUID
347 * @param {String} msg_id A message UUID
348 * @return {Cell} Cell or null if no cell was found.
348 * @return {Cell} Cell or null if no cell was found.
349 */
349 */
350 Notebook.prototype.get_msg_cell = function (msg_id) {
350 Notebook.prototype.get_msg_cell = function (msg_id) {
351 return CodeCell.msg_cells[msg_id] || null;
351 return CodeCell.msg_cells[msg_id] || null;
352 };
352 };
353
353
354 /**
354 /**
355 * Count the cells in this notebook.
355 * Count the cells in this notebook.
356 *
356 *
357 * @method ncells
357 * @method ncells
358 * @return {Number} The number of cells in this notebook
358 * @return {Number} The number of cells in this notebook
359 */
359 */
360 Notebook.prototype.ncells = function () {
360 Notebook.prototype.ncells = function () {
361 return this.get_cell_elements().length;
361 return this.get_cell_elements().length;
362 };
362 };
363
363
364 /**
364 /**
365 * Get all Cell objects in this notebook.
365 * Get all Cell objects in this notebook.
366 *
366 *
367 * @method get_cells
367 * @method get_cells
368 * @return {Array} This notebook's Cell objects
368 * @return {Array} This notebook's Cell objects
369 */
369 */
370 // TODO: we are often calling cells as cells()[i], which we should optimize
370 // TODO: we are often calling cells as cells()[i], which we should optimize
371 // to cells(i) or a new method.
371 // to cells(i) or a new method.
372 Notebook.prototype.get_cells = function () {
372 Notebook.prototype.get_cells = function () {
373 return this.get_cell_elements().toArray().map(function (e) {
373 return this.get_cell_elements().toArray().map(function (e) {
374 return $(e).data("cell");
374 return $(e).data("cell");
375 });
375 });
376 };
376 };
377
377
378 /**
378 /**
379 * Get a Cell object from this notebook.
379 * Get a Cell object from this notebook.
380 *
380 *
381 * @method get_cell
381 * @method get_cell
382 * @param {Number} index An index of a cell to retrieve
382 * @param {Number} index An index of a cell to retrieve
383 * @return {Cell} A particular cell
383 * @return {Cell} A particular cell
384 */
384 */
385 Notebook.prototype.get_cell = function (index) {
385 Notebook.prototype.get_cell = function (index) {
386 var result = null;
386 var result = null;
387 var ce = this.get_cell_element(index);
387 var ce = this.get_cell_element(index);
388 if (ce !== null) {
388 if (ce !== null) {
389 result = ce.data('cell');
389 result = ce.data('cell');
390 }
390 }
391 return result;
391 return result;
392 };
392 };
393
393
394 /**
394 /**
395 * Get the cell below a given cell.
395 * Get the cell below a given cell.
396 *
396 *
397 * @method get_next_cell
397 * @method get_next_cell
398 * @param {Cell} cell The provided cell
398 * @param {Cell} cell The provided cell
399 * @return {Cell} The next cell
399 * @return {Cell} The next cell
400 */
400 */
401 Notebook.prototype.get_next_cell = function (cell) {
401 Notebook.prototype.get_next_cell = function (cell) {
402 var result = null;
402 var result = null;
403 var index = this.find_cell_index(cell);
403 var index = this.find_cell_index(cell);
404 if (this.is_valid_cell_index(index+1)) {
404 if (this.is_valid_cell_index(index+1)) {
405 result = this.get_cell(index+1);
405 result = this.get_cell(index+1);
406 }
406 }
407 return result;
407 return result;
408 };
408 };
409
409
410 /**
410 /**
411 * Get the cell above a given cell.
411 * Get the cell above a given cell.
412 *
412 *
413 * @method get_prev_cell
413 * @method get_prev_cell
414 * @param {Cell} cell The provided cell
414 * @param {Cell} cell The provided cell
415 * @return {Cell} The previous cell
415 * @return {Cell} The previous cell
416 */
416 */
417 Notebook.prototype.get_prev_cell = function (cell) {
417 Notebook.prototype.get_prev_cell = function (cell) {
418 // TODO: off-by-one
418 // TODO: off-by-one
419 // nb.get_prev_cell(nb.get_cell(1)) is null
419 // nb.get_prev_cell(nb.get_cell(1)) is null
420 var result = null;
420 var result = null;
421 var index = this.find_cell_index(cell);
421 var index = this.find_cell_index(cell);
422 if (index !== null && index > 1) {
422 if (index !== null && index > 1) {
423 result = this.get_cell(index-1);
423 result = this.get_cell(index-1);
424 }
424 }
425 return result;
425 return result;
426 };
426 };
427
427
428 /**
428 /**
429 * Get the numeric index of a given cell.
429 * Get the numeric index of a given cell.
430 *
430 *
431 * @method find_cell_index
431 * @method find_cell_index
432 * @param {Cell} cell The provided cell
432 * @param {Cell} cell The provided cell
433 * @return {Number} The cell's numeric index
433 * @return {Number} The cell's numeric index
434 */
434 */
435 Notebook.prototype.find_cell_index = function (cell) {
435 Notebook.prototype.find_cell_index = function (cell) {
436 var result = null;
436 var result = null;
437 this.get_cell_elements().filter(function (index) {
437 this.get_cell_elements().filter(function (index) {
438 if ($(this).data("cell") === cell) {
438 if ($(this).data("cell") === cell) {
439 result = index;
439 result = index;
440 }
440 }
441 });
441 });
442 return result;
442 return result;
443 };
443 };
444
444
445 /**
445 /**
446 * Get a given index , or the selected index if none is provided.
446 * Get a given index , or the selected index if none is provided.
447 *
447 *
448 * @method index_or_selected
448 * @method index_or_selected
449 * @param {Number} index A cell's index
449 * @param {Number} index A cell's index
450 * @return {Number} The given index, or selected index if none is provided.
450 * @return {Number} The given index, or selected index if none is provided.
451 */
451 */
452 Notebook.prototype.index_or_selected = function (index) {
452 Notebook.prototype.index_or_selected = function (index) {
453 var i;
453 var i;
454 if (index === undefined || index === null) {
454 if (index === undefined || index === null) {
455 i = this.get_selected_index();
455 i = this.get_selected_index();
456 if (i === null) {
456 if (i === null) {
457 i = 0;
457 i = 0;
458 }
458 }
459 } else {
459 } else {
460 i = index;
460 i = index;
461 }
461 }
462 return i;
462 return i;
463 };
463 };
464
464
465 /**
465 /**
466 * Get the currently selected cell.
466 * Get the currently selected cell.
467 * @method get_selected_cell
467 * @method get_selected_cell
468 * @return {Cell} The selected cell
468 * @return {Cell} The selected cell
469 */
469 */
470 Notebook.prototype.get_selected_cell = function () {
470 Notebook.prototype.get_selected_cell = function () {
471 var index = this.get_selected_index();
471 var index = this.get_selected_index();
472 return this.get_cell(index);
472 return this.get_cell(index);
473 };
473 };
474
474
475 /**
475 /**
476 * Check whether a cell index is valid.
476 * Check whether a cell index is valid.
477 *
477 *
478 * @method is_valid_cell_index
478 * @method is_valid_cell_index
479 * @param {Number} index A cell index
479 * @param {Number} index A cell index
480 * @return True if the index is valid, false otherwise
480 * @return True if the index is valid, false otherwise
481 */
481 */
482 Notebook.prototype.is_valid_cell_index = function (index) {
482 Notebook.prototype.is_valid_cell_index = function (index) {
483 if (index !== null && index >= 0 && index < this.ncells()) {
483 if (index !== null && index >= 0 && index < this.ncells()) {
484 return true;
484 return true;
485 } else {
485 } else {
486 return false;
486 return false;
487 }
487 }
488 };
488 };
489
489
490 /**
490 /**
491 * Get the index of the currently selected cell.
491 * Get the index of the currently selected cell.
492
492
493 * @method get_selected_index
493 * @method get_selected_index
494 * @return {Number} The selected cell's numeric index
494 * @return {Number} The selected cell's numeric index
495 */
495 */
496 Notebook.prototype.get_selected_index = function () {
496 Notebook.prototype.get_selected_index = function () {
497 var result = null;
497 var result = null;
498 this.get_cell_elements().filter(function (index) {
498 this.get_cell_elements().filter(function (index) {
499 if ($(this).data("cell").selected === true) {
499 if ($(this).data("cell").selected === true) {
500 result = index;
500 result = index;
501 }
501 }
502 });
502 });
503 return result;
503 return result;
504 };
504 };
505
505
506
506
507 // Cell selection.
507 // Cell selection.
508
508
509 /**
509 /**
510 * Programmatically select a cell.
510 * Programmatically select a cell.
511 *
511 *
512 * @method select
512 * @method select
513 * @param {Number} index A cell's index
513 * @param {Number} index A cell's index
514 * @return {Notebook} This notebook
514 * @return {Notebook} This notebook
515 */
515 */
516 Notebook.prototype.select = function (index) {
516 Notebook.prototype.select = function (index) {
517 if (this.is_valid_cell_index(index)) {
517 if (this.is_valid_cell_index(index)) {
518 var sindex = this.get_selected_index();
518 var sindex = this.get_selected_index();
519 if (sindex !== null && index !== sindex) {
519 if (sindex !== null && index !== sindex) {
520 // If we are about to select a different cell, make sure we are
520 // If we are about to select a different cell, make sure we are
521 // first in command mode.
521 // first in command mode.
522 if (this.mode !== 'command') {
522 if (this.mode !== 'command') {
523 this.command_mode();
523 this.command_mode();
524 }
524 }
525 this.get_cell(sindex).unselect();
525 this.get_cell(sindex).unselect();
526 }
526 }
527 var cell = this.get_cell(index);
527 var cell = this.get_cell(index);
528 cell.select();
528 cell.select();
529 if (cell.cell_type === 'heading') {
529 if (cell.cell_type === 'heading') {
530 this.events.trigger('selected_cell_type_changed.Notebook',
530 this.events.trigger('selected_cell_type_changed.Notebook',
531 {'cell_type':cell.cell_type,level:cell.level}
531 {'cell_type':cell.cell_type,level:cell.level}
532 );
532 );
533 } else {
533 } else {
534 this.events.trigger('selected_cell_type_changed.Notebook',
534 this.events.trigger('selected_cell_type_changed.Notebook',
535 {'cell_type':cell.cell_type}
535 {'cell_type':cell.cell_type}
536 );
536 );
537 }
537 }
538 }
538 }
539 return this;
539 return this;
540 };
540 };
541
541
542 /**
542 /**
543 * Programmatically select the next cell.
543 * Programmatically select the next cell.
544 *
544 *
545 * @method select_next
545 * @method select_next
546 * @return {Notebook} This notebook
546 * @return {Notebook} This notebook
547 */
547 */
548 Notebook.prototype.select_next = function () {
548 Notebook.prototype.select_next = function () {
549 var index = this.get_selected_index();
549 var index = this.get_selected_index();
550 this.select(index+1);
550 this.select(index+1);
551 return this;
551 return this;
552 };
552 };
553
553
554 /**
554 /**
555 * Programmatically select the previous cell.
555 * Programmatically select the previous cell.
556 *
556 *
557 * @method select_prev
557 * @method select_prev
558 * @return {Notebook} This notebook
558 * @return {Notebook} This notebook
559 */
559 */
560 Notebook.prototype.select_prev = function () {
560 Notebook.prototype.select_prev = function () {
561 var index = this.get_selected_index();
561 var index = this.get_selected_index();
562 this.select(index-1);
562 this.select(index-1);
563 return this;
563 return this;
564 };
564 };
565
565
566
566
567 // Edit/Command mode
567 // Edit/Command mode
568
568
569 /**
569 /**
570 * Gets the index of the cell that is in edit mode.
570 * Gets the index of the cell that is in edit mode.
571 *
571 *
572 * @method get_edit_index
572 * @method get_edit_index
573 *
573 *
574 * @return index {int}
574 * @return index {int}
575 **/
575 **/
576 Notebook.prototype.get_edit_index = function () {
576 Notebook.prototype.get_edit_index = function () {
577 var result = null;
577 var result = null;
578 this.get_cell_elements().filter(function (index) {
578 this.get_cell_elements().filter(function (index) {
579 if ($(this).data("cell").mode === 'edit') {
579 if ($(this).data("cell").mode === 'edit') {
580 result = index;
580 result = index;
581 }
581 }
582 });
582 });
583 return result;
583 return result;
584 };
584 };
585
585
586 /**
586 /**
587 * Handle when a a cell blurs and the notebook should enter command mode.
587 * Handle when a a cell blurs and the notebook should enter command mode.
588 *
588 *
589 * @method handle_command_mode
589 * @method handle_command_mode
590 * @param [cell] {Cell} Cell to enter command mode on.
590 * @param [cell] {Cell} Cell to enter command mode on.
591 **/
591 **/
592 Notebook.prototype.handle_command_mode = function (cell) {
592 Notebook.prototype.handle_command_mode = function (cell) {
593 if (this.mode !== 'command') {
593 if (this.mode !== 'command') {
594 cell.command_mode();
594 cell.command_mode();
595 this.mode = 'command';
595 this.mode = 'command';
596 this.events.trigger('command_mode.Notebook');
596 this.events.trigger('command_mode.Notebook');
597 this.keyboard_manager.command_mode();
597 this.keyboard_manager.command_mode();
598 }
598 }
599 };
599 };
600
600
601 /**
601 /**
602 * Make the notebook enter command mode.
602 * Make the notebook enter command mode.
603 *
603 *
604 * @method command_mode
604 * @method command_mode
605 **/
605 **/
606 Notebook.prototype.command_mode = function () {
606 Notebook.prototype.command_mode = function () {
607 var cell = this.get_cell(this.get_edit_index());
607 var cell = this.get_cell(this.get_edit_index());
608 if (cell && this.mode !== 'command') {
608 if (cell && this.mode !== 'command') {
609 // We don't call cell.command_mode, but rather call cell.focus_cell()
609 // We don't call cell.command_mode, but rather call cell.focus_cell()
610 // which will blur and CM editor and trigger the call to
610 // which will blur and CM editor and trigger the call to
611 // handle_command_mode.
611 // handle_command_mode.
612 cell.focus_cell();
612 cell.focus_cell();
613 }
613 }
614 };
614 };
615
615
616 /**
616 /**
617 * Handle when a cell fires it's edit_mode event.
617 * Handle when a cell fires it's edit_mode event.
618 *
618 *
619 * @method handle_edit_mode
619 * @method handle_edit_mode
620 * @param [cell] {Cell} Cell to enter edit mode on.
620 * @param [cell] {Cell} Cell to enter edit mode on.
621 **/
621 **/
622 Notebook.prototype.handle_edit_mode = function (cell) {
622 Notebook.prototype.handle_edit_mode = function (cell) {
623 if (cell && this.mode !== 'edit') {
623 if (cell && this.mode !== 'edit') {
624 cell.edit_mode();
624 cell.edit_mode();
625 this.mode = 'edit';
625 this.mode = 'edit';
626 this.events.trigger('edit_mode.Notebook');
626 this.events.trigger('edit_mode.Notebook');
627 this.keyboard_manager.edit_mode();
627 this.keyboard_manager.edit_mode();
628 }
628 }
629 };
629 };
630
630
631 /**
631 /**
632 * Make a cell enter edit mode.
632 * Make a cell enter edit mode.
633 *
633 *
634 * @method edit_mode
634 * @method edit_mode
635 **/
635 **/
636 Notebook.prototype.edit_mode = function () {
636 Notebook.prototype.edit_mode = function () {
637 var cell = this.get_selected_cell();
637 var cell = this.get_selected_cell();
638 if (cell && this.mode !== 'edit') {
638 if (cell && this.mode !== 'edit') {
639 cell.unrender();
639 cell.unrender();
640 cell.focus_editor();
640 cell.focus_editor();
641 }
641 }
642 };
642 };
643
643
644 /**
644 /**
645 * Focus the currently selected cell.
645 * Focus the currently selected cell.
646 *
646 *
647 * @method focus_cell
647 * @method focus_cell
648 **/
648 **/
649 Notebook.prototype.focus_cell = function () {
649 Notebook.prototype.focus_cell = function () {
650 var cell = this.get_selected_cell();
650 var cell = this.get_selected_cell();
651 if (cell === null) {return;} // No cell is selected
651 if (cell === null) {return;} // No cell is selected
652 cell.focus_cell();
652 cell.focus_cell();
653 };
653 };
654
654
655 // Cell movement
655 // Cell movement
656
656
657 /**
657 /**
658 * Move given (or selected) cell up and select it.
658 * Move given (or selected) cell up and select it.
659 *
659 *
660 * @method move_cell_up
660 * @method move_cell_up
661 * @param [index] {integer} cell index
661 * @param [index] {integer} cell index
662 * @return {Notebook} This notebook
662 * @return {Notebook} This notebook
663 **/
663 **/
664 Notebook.prototype.move_cell_up = function (index) {
664 Notebook.prototype.move_cell_up = function (index) {
665 var i = this.index_or_selected(index);
665 var i = this.index_or_selected(index);
666 if (this.is_valid_cell_index(i) && i > 0) {
666 if (this.is_valid_cell_index(i) && i > 0) {
667 var pivot = this.get_cell_element(i-1);
667 var pivot = this.get_cell_element(i-1);
668 var tomove = this.get_cell_element(i);
668 var tomove = this.get_cell_element(i);
669 if (pivot !== null && tomove !== null) {
669 if (pivot !== null && tomove !== null) {
670 tomove.detach();
670 tomove.detach();
671 pivot.before(tomove);
671 pivot.before(tomove);
672 this.select(i-1);
672 this.select(i-1);
673 var cell = this.get_selected_cell();
673 var cell = this.get_selected_cell();
674 cell.focus_cell();
674 cell.focus_cell();
675 }
675 }
676 this.set_dirty(true);
676 this.set_dirty(true);
677 }
677 }
678 return this;
678 return this;
679 };
679 };
680
680
681
681
682 /**
682 /**
683 * Move given (or selected) cell down and select it
683 * Move given (or selected) cell down and select it
684 *
684 *
685 * @method move_cell_down
685 * @method move_cell_down
686 * @param [index] {integer} cell index
686 * @param [index] {integer} cell index
687 * @return {Notebook} This notebook
687 * @return {Notebook} This notebook
688 **/
688 **/
689 Notebook.prototype.move_cell_down = function (index) {
689 Notebook.prototype.move_cell_down = function (index) {
690 var i = this.index_or_selected(index);
690 var i = this.index_or_selected(index);
691 if (this.is_valid_cell_index(i) && this.is_valid_cell_index(i+1)) {
691 if (this.is_valid_cell_index(i) && this.is_valid_cell_index(i+1)) {
692 var pivot = this.get_cell_element(i+1);
692 var pivot = this.get_cell_element(i+1);
693 var tomove = this.get_cell_element(i);
693 var tomove = this.get_cell_element(i);
694 if (pivot !== null && tomove !== null) {
694 if (pivot !== null && tomove !== null) {
695 tomove.detach();
695 tomove.detach();
696 pivot.after(tomove);
696 pivot.after(tomove);
697 this.select(i+1);
697 this.select(i+1);
698 var cell = this.get_selected_cell();
698 var cell = this.get_selected_cell();
699 cell.focus_cell();
699 cell.focus_cell();
700 }
700 }
701 }
701 }
702 this.set_dirty();
702 this.set_dirty();
703 return this;
703 return this;
704 };
704 };
705
705
706
706
707 // Insertion, deletion.
707 // Insertion, deletion.
708
708
709 /**
709 /**
710 * Delete a cell from the notebook.
710 * Delete a cell from the notebook.
711 *
711 *
712 * @method delete_cell
712 * @method delete_cell
713 * @param [index] A cell's numeric index
713 * @param [index] A cell's numeric index
714 * @return {Notebook} This notebook
714 * @return {Notebook} This notebook
715 */
715 */
716 Notebook.prototype.delete_cell = function (index) {
716 Notebook.prototype.delete_cell = function (index) {
717 var i = this.index_or_selected(index);
717 var i = this.index_or_selected(index);
718 var cell = this.get_selected_cell();
718 var cell = this.get_selected_cell();
719 this.undelete_backup = cell.toJSON();
719 this.undelete_backup = cell.toJSON();
720 $('#undelete_cell').removeClass('disabled');
720 $('#undelete_cell').removeClass('disabled');
721 if (this.is_valid_cell_index(i)) {
721 if (this.is_valid_cell_index(i)) {
722 var old_ncells = this.ncells();
722 var old_ncells = this.ncells();
723 var ce = this.get_cell_element(i);
723 var ce = this.get_cell_element(i);
724 ce.remove();
724 ce.remove();
725 if (i === 0) {
725 if (i === 0) {
726 // Always make sure we have at least one cell.
726 // Always make sure we have at least one cell.
727 if (old_ncells === 1) {
727 if (old_ncells === 1) {
728 this.insert_cell_below('code');
728 this.insert_cell_below('code');
729 }
729 }
730 this.select(0);
730 this.select(0);
731 this.undelete_index = 0;
731 this.undelete_index = 0;
732 this.undelete_below = false;
732 this.undelete_below = false;
733 } else if (i === old_ncells-1 && i !== 0) {
733 } else if (i === old_ncells-1 && i !== 0) {
734 this.select(i-1);
734 this.select(i-1);
735 this.undelete_index = i - 1;
735 this.undelete_index = i - 1;
736 this.undelete_below = true;
736 this.undelete_below = true;
737 } else {
737 } else {
738 this.select(i);
738 this.select(i);
739 this.undelete_index = i;
739 this.undelete_index = i;
740 this.undelete_below = false;
740 this.undelete_below = false;
741 }
741 }
742 this.events.trigger('delete.Cell', {'cell': cell, 'index': i});
742 this.events.trigger('delete.Cell', {'cell': cell, 'index': i});
743 this.set_dirty(true);
743 this.set_dirty(true);
744 }
744 }
745 return this;
745 return this;
746 };
746 };
747
747
748 /**
748 /**
749 * Restore the most recently deleted cell.
749 * Restore the most recently deleted cell.
750 *
750 *
751 * @method undelete
751 * @method undelete
752 */
752 */
753 Notebook.prototype.undelete_cell = function() {
753 Notebook.prototype.undelete_cell = function() {
754 if (this.undelete_backup !== null && this.undelete_index !== null) {
754 if (this.undelete_backup !== null && this.undelete_index !== null) {
755 var current_index = this.get_selected_index();
755 var current_index = this.get_selected_index();
756 if (this.undelete_index < current_index) {
756 if (this.undelete_index < current_index) {
757 current_index = current_index + 1;
757 current_index = current_index + 1;
758 }
758 }
759 if (this.undelete_index >= this.ncells()) {
759 if (this.undelete_index >= this.ncells()) {
760 this.select(this.ncells() - 1);
760 this.select(this.ncells() - 1);
761 }
761 }
762 else {
762 else {
763 this.select(this.undelete_index);
763 this.select(this.undelete_index);
764 }
764 }
765 var cell_data = this.undelete_backup;
765 var cell_data = this.undelete_backup;
766 var new_cell = null;
766 var new_cell = null;
767 if (this.undelete_below) {
767 if (this.undelete_below) {
768 new_cell = this.insert_cell_below(cell_data.cell_type);
768 new_cell = this.insert_cell_below(cell_data.cell_type);
769 } else {
769 } else {
770 new_cell = this.insert_cell_above(cell_data.cell_type);
770 new_cell = this.insert_cell_above(cell_data.cell_type);
771 }
771 }
772 new_cell.fromJSON(cell_data);
772 new_cell.fromJSON(cell_data);
773 if (this.undelete_below) {
773 if (this.undelete_below) {
774 this.select(current_index+1);
774 this.select(current_index+1);
775 } else {
775 } else {
776 this.select(current_index);
776 this.select(current_index);
777 }
777 }
778 this.undelete_backup = null;
778 this.undelete_backup = null;
779 this.undelete_index = null;
779 this.undelete_index = null;
780 }
780 }
781 $('#undelete_cell').addClass('disabled');
781 $('#undelete_cell').addClass('disabled');
782 };
782 };
783
783
784 /**
784 /**
785 * Insert a cell so that after insertion the cell is at given index.
785 * Insert a cell so that after insertion the cell is at given index.
786 *
786 *
787 * If cell type is not provided, it will default to the type of the
787 * If cell type is not provided, it will default to the type of the
788 * currently active cell.
788 * currently active cell.
789 *
789 *
790 * Similar to insert_above, but index parameter is mandatory
790 * Similar to insert_above, but index parameter is mandatory
791 *
791 *
792 * Index will be brought back into the accessible range [0,n]
792 * Index will be brought back into the accessible range [0,n]
793 *
793 *
794 * @method insert_cell_at_index
794 * @method insert_cell_at_index
795 * @param [type] {string} in ['code','markdown','heading'], defaults to 'code'
795 * @param [type] {string} in ['code','markdown','heading'], defaults to 'code'
796 * @param [index] {int} a valid index where to insert cell
796 * @param [index] {int} a valid index where to insert cell
797 *
797 *
798 * @return cell {cell|null} created cell or null
798 * @return cell {cell|null} created cell or null
799 **/
799 **/
800 Notebook.prototype.insert_cell_at_index = function(type, index){
800 Notebook.prototype.insert_cell_at_index = function(type, index){
801
801
802 var ncells = this.ncells();
802 var ncells = this.ncells();
803 index = Math.min(index,ncells);
803 index = Math.min(index,ncells);
804 index = Math.max(index,0);
804 index = Math.max(index,0);
805 var cell = null;
805 var cell = null;
806 type = type || this.get_selected_cell().cell_type;
806 type = type || this.get_selected_cell().cell_type;
807
807
808 if (ncells === 0 || this.is_valid_cell_index(index) || index === ncells) {
808 if (ncells === 0 || this.is_valid_cell_index(index) || index === ncells) {
809 if (type === 'code') {
809 if (type === 'code') {
810 cell = new CodeCell(this.kernel, this.options, this.events, this.config, this.keyboard_manager, this);
810 cell = new CodeCell(this.kernel, this.options, this.events, this.config, this.keyboard_manager, this);
811 cell.set_input_prompt();
811 cell.set_input_prompt();
812 } else if (type === 'markdown') {
812 } else if (type === 'markdown') {
813 cell = new Cells.MarkdownCell(this.options, this.events, this.config, this.keyboard_manager, this);
813 cell = new Cells.MarkdownCell(this.options, this.events, this.config, this.keyboard_manager, this);
814 } else if (type === 'raw') {
814 } else if (type === 'raw') {
815 cell = new Cells.RawCell(this.options, this.events, this.config, this.keyboard_manager, this);
815 cell = new Cells.RawCell(this.options, this.events, this.config, this.keyboard_manager, this);
816 } else if (type === 'heading') {
816 } else if (type === 'heading') {
817 cell = new Cells.HeadingCell(this.options, this.events, this.config, this.keyboard_manager, this);
817 cell = new Cells.HeadingCell(this.options, this.events, this.config, this.keyboard_manager, this);
818 }
818 }
819
819
820 if(this._insert_element_at_index(cell.element,index)) {
820 if(this._insert_element_at_index(cell.element,index)) {
821 cell.render();
821 cell.render();
822 this.events.trigger('create.Cell', {'cell': cell, 'index': index});
822 this.events.trigger('create.Cell', {'cell': cell, 'index': index});
823 cell.refresh();
823 cell.refresh();
824 // We used to select the cell after we refresh it, but there
824 // We used to select the cell after we refresh it, but there
825 // are now cases were this method is called where select is
825 // are now cases were this method is called where select is
826 // not appropriate. The selection logic should be handled by the
826 // not appropriate. The selection logic should be handled by the
827 // caller of the the top level insert_cell methods.
827 // caller of the the top level insert_cell methods.
828 this.set_dirty(true);
828 this.set_dirty(true);
829 }
829 }
830 }
830 }
831 return cell;
831 return cell;
832
832
833 };
833 };
834
834
835 /**
835 /**
836 * Insert an element at given cell index.
836 * Insert an element at given cell index.
837 *
837 *
838 * @method _insert_element_at_index
838 * @method _insert_element_at_index
839 * @param element {dom element} a cell element
839 * @param element {dom element} a cell element
840 * @param [index] {int} a valid index where to inser cell
840 * @param [index] {int} a valid index where to inser cell
841 * @private
841 * @private
842 *
842 *
843 * return true if everything whent fine.
843 * return true if everything whent fine.
844 **/
844 **/
845 Notebook.prototype._insert_element_at_index = function(element, index){
845 Notebook.prototype._insert_element_at_index = function(element, index){
846 if (element === undefined){
846 if (element === undefined){
847 return false;
847 return false;
848 }
848 }
849
849
850 var ncells = this.ncells();
850 var ncells = this.ncells();
851
851
852 if (ncells === 0) {
852 if (ncells === 0) {
853 // special case append if empty
853 // special case append if empty
854 this.element.find('div.end_space').before(element);
854 this.element.find('div.end_space').before(element);
855 } else if ( ncells === index ) {
855 } else if ( ncells === index ) {
856 // special case append it the end, but not empty
856 // special case append it the end, but not empty
857 this.get_cell_element(index-1).after(element);
857 this.get_cell_element(index-1).after(element);
858 } else if (this.is_valid_cell_index(index)) {
858 } else if (this.is_valid_cell_index(index)) {
859 // otherwise always somewhere to append to
859 // otherwise always somewhere to append to
860 this.get_cell_element(index).before(element);
860 this.get_cell_element(index).before(element);
861 } else {
861 } else {
862 return false;
862 return false;
863 }
863 }
864
864
865 if (this.undelete_index !== null && index <= this.undelete_index) {
865 if (this.undelete_index !== null && index <= this.undelete_index) {
866 this.undelete_index = this.undelete_index + 1;
866 this.undelete_index = this.undelete_index + 1;
867 this.set_dirty(true);
867 this.set_dirty(true);
868 }
868 }
869 return true;
869 return true;
870 };
870 };
871
871
872 /**
872 /**
873 * Insert a cell of given type above given index, or at top
873 * Insert a cell of given type above given index, or at top
874 * of notebook if index smaller than 0.
874 * of notebook if index smaller than 0.
875 *
875 *
876 * default index value is the one of currently selected cell
876 * default index value is the one of currently selected cell
877 *
877 *
878 * @method insert_cell_above
878 * @method insert_cell_above
879 * @param [type] {string} cell type
879 * @param [type] {string} cell type
880 * @param [index] {integer}
880 * @param [index] {integer}
881 *
881 *
882 * @return handle to created cell or null
882 * @return handle to created cell or null
883 **/
883 **/
884 Notebook.prototype.insert_cell_above = function (type, index) {
884 Notebook.prototype.insert_cell_above = function (type, index) {
885 index = this.index_or_selected(index);
885 index = this.index_or_selected(index);
886 return this.insert_cell_at_index(type, index);
886 return this.insert_cell_at_index(type, index);
887 };
887 };
888
888
889 /**
889 /**
890 * Insert a cell of given type below given index, or at bottom
890 * Insert a cell of given type below given index, or at bottom
891 * of notebook if index greater than number of cells
891 * of notebook if index greater than number of cells
892 *
892 *
893 * default index value is the one of currently selected cell
893 * default index value is the one of currently selected cell
894 *
894 *
895 * @method insert_cell_below
895 * @method insert_cell_below
896 * @param [type] {string} cell type
896 * @param [type] {string} cell type
897 * @param [index] {integer}
897 * @param [index] {integer}
898 *
898 *
899 * @return handle to created cell or null
899 * @return handle to created cell or null
900 *
900 *
901 **/
901 **/
902 Notebook.prototype.insert_cell_below = function (type, index) {
902 Notebook.prototype.insert_cell_below = function (type, index) {
903 index = this.index_or_selected(index);
903 index = this.index_or_selected(index);
904 return this.insert_cell_at_index(type, index+1);
904 return this.insert_cell_at_index(type, index+1);
905 };
905 };
906
906
907
907
908 /**
908 /**
909 * Insert cell at end of notebook
909 * Insert cell at end of notebook
910 *
910 *
911 * @method insert_cell_at_bottom
911 * @method insert_cell_at_bottom
912 * @param {String} type cell type
912 * @param {String} type cell type
913 *
913 *
914 * @return the added cell; or null
914 * @return the added cell; or null
915 **/
915 **/
916 Notebook.prototype.insert_cell_at_bottom = function (type){
916 Notebook.prototype.insert_cell_at_bottom = function (type){
917 var len = this.ncells();
917 var len = this.ncells();
918 return this.insert_cell_below(type,len-1);
918 return this.insert_cell_below(type,len-1);
919 };
919 };
920
920
921 /**
921 /**
922 * Turn a cell into a code cell.
922 * Turn a cell into a code cell.
923 *
923 *
924 * @method to_code
924 * @method to_code
925 * @param {Number} [index] A cell's index
925 * @param {Number} [index] A cell's index
926 */
926 */
927 Notebook.prototype.to_code = function (index) {
927 Notebook.prototype.to_code = function (index) {
928 var i = this.index_or_selected(index);
928 var i = this.index_or_selected(index);
929 if (this.is_valid_cell_index(i)) {
929 if (this.is_valid_cell_index(i)) {
930 var source_element = this.get_cell_element(i);
930 var source_element = this.get_cell_element(i);
931 var source_cell = source_element.data("cell");
931 var source_cell = source_element.data("cell");
932 if (!(source_cell instanceof CodeCell)) {
932 if (!(source_cell instanceof CodeCell)) {
933 var target_cell = this.insert_cell_below('code',i);
933 var target_cell = this.insert_cell_below('code',i);
934 var text = source_cell.get_text();
934 var text = source_cell.get_text();
935 if (text === source_cell.placeholder) {
935 if (text === source_cell.placeholder) {
936 text = '';
936 text = '';
937 }
937 }
938 target_cell.set_text(text);
938 target_cell.set_text(text);
939 // make this value the starting point, so that we can only undo
939 // make this value the starting point, so that we can only undo
940 // to this state, instead of a blank cell
940 // to this state, instead of a blank cell
941 target_cell.code_mirror.clearHistory();
941 target_cell.code_mirror.clearHistory();
942 source_element.remove();
942 source_element.remove();
943 this.select(i);
943 this.select(i);
944 var cursor = source_cell.code_mirror.getCursor();
944 var cursor = source_cell.code_mirror.getCursor();
945 target_cell.code_mirror.setCursor(cursor);
945 target_cell.code_mirror.setCursor(cursor);
946 this.set_dirty(true);
946 this.set_dirty(true);
947 }
947 }
948 }
948 }
949 };
949 };
950
950
951 /**
951 /**
952 * Turn a cell into a Markdown cell.
952 * Turn a cell into a Markdown cell.
953 *
953 *
954 * @method to_markdown
954 * @method to_markdown
955 * @param {Number} [index] A cell's index
955 * @param {Number} [index] A cell's index
956 */
956 */
957 Notebook.prototype.to_markdown = function (index) {
957 Notebook.prototype.to_markdown = function (index) {
958 var i = this.index_or_selected(index);
958 var i = this.index_or_selected(index);
959 if (this.is_valid_cell_index(i)) {
959 if (this.is_valid_cell_index(i)) {
960 var source_element = this.get_cell_element(i);
960 var source_element = this.get_cell_element(i);
961 var source_cell = source_element.data("cell");
961 var source_cell = source_element.data("cell");
962 if (!(source_cell instanceof Cells.MarkdownCell)) {
962 if (!(source_cell instanceof Cells.MarkdownCell)) {
963 var target_cell = this.insert_cell_below('markdown',i);
963 var target_cell = this.insert_cell_below('markdown',i);
964 var text = source_cell.get_text();
964 var text = source_cell.get_text();
965 if (text === source_cell.placeholder) {
965 if (text === source_cell.placeholder) {
966 text = '';
966 text = '';
967 }
967 }
968 // We must show the editor before setting its contents
968 // We must show the editor before setting its contents
969 target_cell.unrender();
969 target_cell.unrender();
970 target_cell.set_text(text);
970 target_cell.set_text(text);
971 // make this value the starting point, so that we can only undo
971 // make this value the starting point, so that we can only undo
972 // to this state, instead of a blank cell
972 // to this state, instead of a blank cell
973 target_cell.code_mirror.clearHistory();
973 target_cell.code_mirror.clearHistory();
974 source_element.remove();
974 source_element.remove();
975 this.select(i);
975 this.select(i);
976 if ((source_cell instanceof Cells.TextCell) && source_cell.rendered) {
976 if ((source_cell instanceof Cells.TextCell) && source_cell.rendered) {
977 target_cell.render();
977 target_cell.render();
978 }
978 }
979 var cursor = source_cell.code_mirror.getCursor();
979 var cursor = source_cell.code_mirror.getCursor();
980 target_cell.code_mirror.setCursor(cursor);
980 target_cell.code_mirror.setCursor(cursor);
981 this.set_dirty(true);
981 this.set_dirty(true);
982 }
982 }
983 }
983 }
984 };
984 };
985
985
986 /**
986 /**
987 * Turn a cell into a raw text cell.
987 * Turn a cell into a raw text cell.
988 *
988 *
989 * @method to_raw
989 * @method to_raw
990 * @param {Number} [index] A cell's index
990 * @param {Number} [index] A cell's index
991 */
991 */
992 Notebook.prototype.to_raw = function (index) {
992 Notebook.prototype.to_raw = function (index) {
993 var i = this.index_or_selected(index);
993 var i = this.index_or_selected(index);
994 if (this.is_valid_cell_index(i)) {
994 if (this.is_valid_cell_index(i)) {
995 var source_element = this.get_cell_element(i);
995 var source_element = this.get_cell_element(i);
996 var source_cell = source_element.data("cell");
996 var source_cell = source_element.data("cell");
997 var target_cell = null;
997 var target_cell = null;
998 if (!(source_cell instanceof Cells.RawCell)) {
998 if (!(source_cell instanceof Cells.RawCell)) {
999 target_cell = this.insert_cell_below('raw',i);
999 target_cell = this.insert_cell_below('raw',i);
1000 var text = source_cell.get_text();
1000 var text = source_cell.get_text();
1001 if (text === source_cell.placeholder) {
1001 if (text === source_cell.placeholder) {
1002 text = '';
1002 text = '';
1003 }
1003 }
1004 // We must show the editor before setting its contents
1004 // We must show the editor before setting its contents
1005 target_cell.unrender();
1005 target_cell.unrender();
1006 target_cell.set_text(text);
1006 target_cell.set_text(text);
1007 // make this value the starting point, so that we can only undo
1007 // make this value the starting point, so that we can only undo
1008 // to this state, instead of a blank cell
1008 // to this state, instead of a blank cell
1009 target_cell.code_mirror.clearHistory();
1009 target_cell.code_mirror.clearHistory();
1010 source_element.remove();
1010 source_element.remove();
1011 this.select(i);
1011 this.select(i);
1012 var cursor = source_cell.code_mirror.getCursor();
1012 var cursor = source_cell.code_mirror.getCursor();
1013 target_cell.code_mirror.setCursor(cursor);
1013 target_cell.code_mirror.setCursor(cursor);
1014 this.set_dirty(true);
1014 this.set_dirty(true);
1015 }
1015 }
1016 }
1016 }
1017 };
1017 };
1018
1018
1019 /**
1019 /**
1020 * Turn a cell into a heading cell.
1020 * Turn a cell into a heading cell.
1021 *
1021 *
1022 * @method to_heading
1022 * @method to_heading
1023 * @param {Number} [index] A cell's index
1023 * @param {Number} [index] A cell's index
1024 * @param {Number} [level] A heading level (e.g., 1 becomes &lt;h1&gt;)
1024 * @param {Number} [level] A heading level (e.g., 1 becomes &lt;h1&gt;)
1025 */
1025 */
1026 Notebook.prototype.to_heading = function (index, level) {
1026 Notebook.prototype.to_heading = function (index, level) {
1027 level = level || 1;
1027 level = level || 1;
1028 var i = this.index_or_selected(index);
1028 var i = this.index_or_selected(index);
1029 if (this.is_valid_cell_index(i)) {
1029 if (this.is_valid_cell_index(i)) {
1030 var source_element = this.get_cell_element(i);
1030 var source_element = this.get_cell_element(i);
1031 var source_cell = source_element.data("cell");
1031 var source_cell = source_element.data("cell");
1032 var target_cell = null;
1032 var target_cell = null;
1033 if (source_cell instanceof Cells.HeadingCell) {
1033 if (source_cell instanceof Cells.HeadingCell) {
1034 source_cell.set_level(level);
1034 source_cell.set_level(level);
1035 } else {
1035 } else {
1036 target_cell = this.insert_cell_below('heading',i);
1036 target_cell = this.insert_cell_below('heading',i);
1037 var text = source_cell.get_text();
1037 var text = source_cell.get_text();
1038 if (text === source_cell.placeholder) {
1038 if (text === source_cell.placeholder) {
1039 text = '';
1039 text = '';
1040 }
1040 }
1041 // We must show the editor before setting its contents
1041 // We must show the editor before setting its contents
1042 target_cell.set_level(level);
1042 target_cell.set_level(level);
1043 target_cell.unrender();
1043 target_cell.unrender();
1044 target_cell.set_text(text);
1044 target_cell.set_text(text);
1045 // make this value the starting point, so that we can only undo
1045 // make this value the starting point, so that we can only undo
1046 // to this state, instead of a blank cell
1046 // to this state, instead of a blank cell
1047 target_cell.code_mirror.clearHistory();
1047 target_cell.code_mirror.clearHistory();
1048 source_element.remove();
1048 source_element.remove();
1049 this.select(i);
1049 this.select(i);
1050 var cursor = source_cell.code_mirror.getCursor();
1050 var cursor = source_cell.code_mirror.getCursor();
1051 target_cell.code_mirror.setCursor(cursor);
1051 target_cell.code_mirror.setCursor(cursor);
1052 if ((source_cell instanceof Cells.TextCell) && source_cell.rendered) {
1052 if ((source_cell instanceof Cells.TextCell) && source_cell.rendered) {
1053 target_cell.render();
1053 target_cell.render();
1054 }
1054 }
1055 }
1055 }
1056 this.set_dirty(true);
1056 this.set_dirty(true);
1057 this.events.trigger('selected_cell_type_changed.Notebook',
1057 this.events.trigger('selected_cell_type_changed.Notebook',
1058 {'cell_type':'heading',level:level}
1058 {'cell_type':'heading',level:level}
1059 );
1059 );
1060 }
1060 }
1061 };
1061 };
1062
1062
1063
1063
1064 // Cut/Copy/Paste
1064 // Cut/Copy/Paste
1065
1065
1066 /**
1066 /**
1067 * Enable UI elements for pasting cells.
1067 * Enable UI elements for pasting cells.
1068 *
1068 *
1069 * @method enable_paste
1069 * @method enable_paste
1070 */
1070 */
1071 Notebook.prototype.enable_paste = function () {
1071 Notebook.prototype.enable_paste = function () {
1072 var that = this;
1072 var that = this;
1073 if (!this.paste_enabled) {
1073 if (!this.paste_enabled) {
1074 $('#paste_cell_replace').removeClass('disabled')
1074 $('#paste_cell_replace').removeClass('disabled')
1075 .on('click', function () {that.paste_cell_replace();});
1075 .on('click', function () {that.paste_cell_replace();});
1076 $('#paste_cell_above').removeClass('disabled')
1076 $('#paste_cell_above').removeClass('disabled')
1077 .on('click', function () {that.paste_cell_above();});
1077 .on('click', function () {that.paste_cell_above();});
1078 $('#paste_cell_below').removeClass('disabled')
1078 $('#paste_cell_below').removeClass('disabled')
1079 .on('click', function () {that.paste_cell_below();});
1079 .on('click', function () {that.paste_cell_below();});
1080 this.paste_enabled = true;
1080 this.paste_enabled = true;
1081 }
1081 }
1082 };
1082 };
1083
1083
1084 /**
1084 /**
1085 * Disable UI elements for pasting cells.
1085 * Disable UI elements for pasting cells.
1086 *
1086 *
1087 * @method disable_paste
1087 * @method disable_paste
1088 */
1088 */
1089 Notebook.prototype.disable_paste = function () {
1089 Notebook.prototype.disable_paste = function () {
1090 if (this.paste_enabled) {
1090 if (this.paste_enabled) {
1091 $('#paste_cell_replace').addClass('disabled').off('click');
1091 $('#paste_cell_replace').addClass('disabled').off('click');
1092 $('#paste_cell_above').addClass('disabled').off('click');
1092 $('#paste_cell_above').addClass('disabled').off('click');
1093 $('#paste_cell_below').addClass('disabled').off('click');
1093 $('#paste_cell_below').addClass('disabled').off('click');
1094 this.paste_enabled = false;
1094 this.paste_enabled = false;
1095 }
1095 }
1096 };
1096 };
1097
1097
1098 /**
1098 /**
1099 * Cut a cell.
1099 * Cut a cell.
1100 *
1100 *
1101 * @method cut_cell
1101 * @method cut_cell
1102 */
1102 */
1103 Notebook.prototype.cut_cell = function () {
1103 Notebook.prototype.cut_cell = function () {
1104 this.copy_cell();
1104 this.copy_cell();
1105 this.delete_cell();
1105 this.delete_cell();
1106 };
1106 };
1107
1107
1108 /**
1108 /**
1109 * Copy a cell.
1109 * Copy a cell.
1110 *
1110 *
1111 * @method copy_cell
1111 * @method copy_cell
1112 */
1112 */
1113 Notebook.prototype.copy_cell = function () {
1113 Notebook.prototype.copy_cell = function () {
1114 var cell = this.get_selected_cell();
1114 var cell = this.get_selected_cell();
1115 this.clipboard = cell.toJSON();
1115 this.clipboard = cell.toJSON();
1116 this.enable_paste();
1116 this.enable_paste();
1117 };
1117 };
1118
1118
1119 /**
1119 /**
1120 * Replace the selected cell with a cell in the clipboard.
1120 * Replace the selected cell with a cell in the clipboard.
1121 *
1121 *
1122 * @method paste_cell_replace
1122 * @method paste_cell_replace
1123 */
1123 */
1124 Notebook.prototype.paste_cell_replace = function () {
1124 Notebook.prototype.paste_cell_replace = function () {
1125 if (this.clipboard !== null && this.paste_enabled) {
1125 if (this.clipboard !== null && this.paste_enabled) {
1126 var cell_data = this.clipboard;
1126 var cell_data = this.clipboard;
1127 var new_cell = this.insert_cell_above(cell_data.cell_type);
1127 var new_cell = this.insert_cell_above(cell_data.cell_type);
1128 new_cell.fromJSON(cell_data);
1128 new_cell.fromJSON(cell_data);
1129 var old_cell = this.get_next_cell(new_cell);
1129 var old_cell = this.get_next_cell(new_cell);
1130 this.delete_cell(this.find_cell_index(old_cell));
1130 this.delete_cell(this.find_cell_index(old_cell));
1131 this.select(this.find_cell_index(new_cell));
1131 this.select(this.find_cell_index(new_cell));
1132 }
1132 }
1133 };
1133 };
1134
1134
1135 /**
1135 /**
1136 * Paste a cell from the clipboard above the selected cell.
1136 * Paste a cell from the clipboard above the selected cell.
1137 *
1137 *
1138 * @method paste_cell_above
1138 * @method paste_cell_above
1139 */
1139 */
1140 Notebook.prototype.paste_cell_above = function () {
1140 Notebook.prototype.paste_cell_above = function () {
1141 if (this.clipboard !== null && this.paste_enabled) {
1141 if (this.clipboard !== null && this.paste_enabled) {
1142 var cell_data = this.clipboard;
1142 var cell_data = this.clipboard;
1143 var new_cell = this.insert_cell_above(cell_data.cell_type);
1143 var new_cell = this.insert_cell_above(cell_data.cell_type);
1144 new_cell.fromJSON(cell_data);
1144 new_cell.fromJSON(cell_data);
1145 new_cell.focus_cell();
1145 new_cell.focus_cell();
1146 }
1146 }
1147 };
1147 };
1148
1148
1149 /**
1149 /**
1150 * Paste a cell from the clipboard below the selected cell.
1150 * Paste a cell from the clipboard below the selected cell.
1151 *
1151 *
1152 * @method paste_cell_below
1152 * @method paste_cell_below
1153 */
1153 */
1154 Notebook.prototype.paste_cell_below = function () {
1154 Notebook.prototype.paste_cell_below = function () {
1155 if (this.clipboard !== null && this.paste_enabled) {
1155 if (this.clipboard !== null && this.paste_enabled) {
1156 var cell_data = this.clipboard;
1156 var cell_data = this.clipboard;
1157 var new_cell = this.insert_cell_below(cell_data.cell_type);
1157 var new_cell = this.insert_cell_below(cell_data.cell_type);
1158 new_cell.fromJSON(cell_data);
1158 new_cell.fromJSON(cell_data);
1159 new_cell.focus_cell();
1159 new_cell.focus_cell();
1160 }
1160 }
1161 };
1161 };
1162
1162
1163 // Split/merge
1163 // Split/merge
1164
1164
1165 /**
1165 /**
1166 * Split the selected cell into two, at the cursor.
1166 * Split the selected cell into two, at the cursor.
1167 *
1167 *
1168 * @method split_cell
1168 * @method split_cell
1169 */
1169 */
1170 Notebook.prototype.split_cell = function () {
1170 Notebook.prototype.split_cell = function () {
1171 var mdc = Cells.MarkdownCell;
1171 var mdc = Cells.MarkdownCell;
1172 var rc = Cells.RawCell;
1172 var rc = Cells.RawCell;
1173 var cell = this.get_selected_cell();
1173 var cell = this.get_selected_cell();
1174 if (cell.is_splittable()) {
1174 if (cell.is_splittable()) {
1175 var texta = cell.get_pre_cursor();
1175 var texta = cell.get_pre_cursor();
1176 var textb = cell.get_post_cursor();
1176 var textb = cell.get_post_cursor();
1177 if (cell instanceof CodeCell) {
1177 if (cell instanceof CodeCell) {
1178 // In this case the operations keep the notebook in its existing mode
1178 // In this case the operations keep the notebook in its existing mode
1179 // so we don't need to do any post-op mode changes.
1179 // so we don't need to do any post-op mode changes.
1180 cell.set_text(textb);
1180 cell.set_text(textb);
1181 var new_cell = this.insert_cell_above('code');
1181 var new_cell = this.insert_cell_above('code');
1182 new_cell.set_text(texta);
1182 new_cell.set_text(texta);
1183 } else if ((cell instanceof mdc && !cell.rendered) || (cell instanceof rc)) {
1183 } else if ((cell instanceof mdc && !cell.rendered) || (cell instanceof rc)) {
1184 // We know cell is !rendered so we can use set_text.
1184 // We know cell is !rendered so we can use set_text.
1185 cell.set_text(textb);
1185 cell.set_text(textb);
1186 var new_cell = this.insert_cell_above(cell.cell_type);
1186 var new_cell = this.insert_cell_above(cell.cell_type);
1187 // Unrender the new cell so we can call set_text.
1187 // Unrender the new cell so we can call set_text.
1188 new_cell.unrender();
1188 new_cell.unrender();
1189 new_cell.set_text(texta);
1189 new_cell.set_text(texta);
1190 }
1190 }
1191 }
1191 }
1192 };
1192 };
1193
1193
1194 /**
1194 /**
1195 * Combine the selected cell into the cell above it.
1195 * Combine the selected cell into the cell above it.
1196 *
1196 *
1197 * @method merge_cell_above
1197 * @method merge_cell_above
1198 */
1198 */
1199 Notebook.prototype.merge_cell_above = function () {
1199 Notebook.prototype.merge_cell_above = function () {
1200 var mdc = Cells.MarkdownCell;
1200 var mdc = Cells.MarkdownCell;
1201 var rc = Cells.RawCell;
1201 var rc = Cells.RawCell;
1202 var index = this.get_selected_index();
1202 var index = this.get_selected_index();
1203 var cell = this.get_cell(index);
1203 var cell = this.get_cell(index);
1204 var render = cell.rendered;
1204 var render = cell.rendered;
1205 if (!cell.is_mergeable()) {
1205 if (!cell.is_mergeable()) {
1206 return;
1206 return;
1207 }
1207 }
1208 if (index > 0) {
1208 if (index > 0) {
1209 var upper_cell = this.get_cell(index-1);
1209 var upper_cell = this.get_cell(index-1);
1210 if (!upper_cell.is_mergeable()) {
1210 if (!upper_cell.is_mergeable()) {
1211 return;
1211 return;
1212 }
1212 }
1213 var upper_text = upper_cell.get_text();
1213 var upper_text = upper_cell.get_text();
1214 var text = cell.get_text();
1214 var text = cell.get_text();
1215 if (cell instanceof CodeCell) {
1215 if (cell instanceof CodeCell) {
1216 cell.set_text(upper_text+'\n'+text);
1216 cell.set_text(upper_text+'\n'+text);
1217 } else if ((cell instanceof mdc) || (cell instanceof rc)) {
1217 } else if ((cell instanceof mdc) || (cell instanceof rc)) {
1218 cell.unrender(); // Must unrender before we set_text.
1218 cell.unrender(); // Must unrender before we set_text.
1219 cell.set_text(upper_text+'\n\n'+text);
1219 cell.set_text(upper_text+'\n\n'+text);
1220 if (render) {
1220 if (render) {
1221 // The rendered state of the final cell should match
1221 // The rendered state of the final cell should match
1222 // that of the original selected cell;
1222 // that of the original selected cell;
1223 cell.render();
1223 cell.render();
1224 }
1224 }
1225 }
1225 }
1226 this.delete_cell(index-1);
1226 this.delete_cell(index-1);
1227 this.select(this.find_cell_index(cell));
1227 this.select(this.find_cell_index(cell));
1228 }
1228 }
1229 };
1229 };
1230
1230
1231 /**
1231 /**
1232 * Combine the selected cell into the cell below it.
1232 * Combine the selected cell into the cell below it.
1233 *
1233 *
1234 * @method merge_cell_below
1234 * @method merge_cell_below
1235 */
1235 */
1236 Notebook.prototype.merge_cell_below = function () {
1236 Notebook.prototype.merge_cell_below = function () {
1237 var mdc = Cells.MarkdownCell;
1237 var mdc = Cells.MarkdownCell;
1238 var rc = Cells.RawCell;
1238 var rc = Cells.RawCell;
1239 var index = this.get_selected_index();
1239 var index = this.get_selected_index();
1240 var cell = this.get_cell(index);
1240 var cell = this.get_cell(index);
1241 var render = cell.rendered;
1241 var render = cell.rendered;
1242 if (!cell.is_mergeable()) {
1242 if (!cell.is_mergeable()) {
1243 return;
1243 return;
1244 }
1244 }
1245 if (index < this.ncells()-1) {
1245 if (index < this.ncells()-1) {
1246 var lower_cell = this.get_cell(index+1);
1246 var lower_cell = this.get_cell(index+1);
1247 if (!lower_cell.is_mergeable()) {
1247 if (!lower_cell.is_mergeable()) {
1248 return;
1248 return;
1249 }
1249 }
1250 var lower_text = lower_cell.get_text();
1250 var lower_text = lower_cell.get_text();
1251 var text = cell.get_text();
1251 var text = cell.get_text();
1252 if (cell instanceof CodeCell) {
1252 if (cell instanceof CodeCell) {
1253 cell.set_text(text+'\n'+lower_text);
1253 cell.set_text(text+'\n'+lower_text);
1254 } else if ((cell instanceof mdc) || (cell instanceof rc)) {
1254 } else if ((cell instanceof mdc) || (cell instanceof rc)) {
1255 cell.unrender(); // Must unrender before we set_text.
1255 cell.unrender(); // Must unrender before we set_text.
1256 cell.set_text(text+'\n\n'+lower_text);
1256 cell.set_text(text+'\n\n'+lower_text);
1257 if (render) {
1257 if (render) {
1258 // The rendered state of the final cell should match
1258 // The rendered state of the final cell should match
1259 // that of the original selected cell;
1259 // that of the original selected cell;
1260 cell.render();
1260 cell.render();
1261 }
1261 }
1262 }
1262 }
1263 this.delete_cell(index+1);
1263 this.delete_cell(index+1);
1264 this.select(this.find_cell_index(cell));
1264 this.select(this.find_cell_index(cell));
1265 }
1265 }
1266 };
1266 };
1267
1267
1268
1268
1269 // Cell collapsing and output clearing
1269 // Cell collapsing and output clearing
1270
1270
1271 /**
1271 /**
1272 * Hide a cell's output.
1272 * Hide a cell's output.
1273 *
1273 *
1274 * @method collapse_output
1274 * @method collapse_output
1275 * @param {Number} index A cell's numeric index
1275 * @param {Number} index A cell's numeric index
1276 */
1276 */
1277 Notebook.prototype.collapse_output = function (index) {
1277 Notebook.prototype.collapse_output = function (index) {
1278 var i = this.index_or_selected(index);
1278 var i = this.index_or_selected(index);
1279 var cell = this.get_cell(i);
1279 var cell = this.get_cell(i);
1280 if (cell !== null && (cell instanceof CodeCell)) {
1280 if (cell !== null && (cell instanceof CodeCell)) {
1281 cell.collapse_output();
1281 cell.collapse_output();
1282 this.set_dirty(true);
1282 this.set_dirty(true);
1283 }
1283 }
1284 };
1284 };
1285
1285
1286 /**
1286 /**
1287 * Hide each code cell's output area.
1287 * Hide each code cell's output area.
1288 *
1288 *
1289 * @method collapse_all_output
1289 * @method collapse_all_output
1290 */
1290 */
1291 Notebook.prototype.collapse_all_output = function () {
1291 Notebook.prototype.collapse_all_output = function () {
1292 $.map(this.get_cells(), function (cell, i) {
1292 $.map(this.get_cells(), function (cell, i) {
1293 if (cell instanceof CodeCell) {
1293 if (cell instanceof CodeCell) {
1294 cell.collapse_output();
1294 cell.collapse_output();
1295 }
1295 }
1296 });
1296 });
1297 // this should not be set if the `collapse` key is removed from nbformat
1297 // this should not be set if the `collapse` key is removed from nbformat
1298 this.set_dirty(true);
1298 this.set_dirty(true);
1299 };
1299 };
1300
1300
1301 /**
1301 /**
1302 * Show a cell's output.
1302 * Show a cell's output.
1303 *
1303 *
1304 * @method expand_output
1304 * @method expand_output
1305 * @param {Number} index A cell's numeric index
1305 * @param {Number} index A cell's numeric index
1306 */
1306 */
1307 Notebook.prototype.expand_output = function (index) {
1307 Notebook.prototype.expand_output = function (index) {
1308 var i = this.index_or_selected(index);
1308 var i = this.index_or_selected(index);
1309 var cell = this.get_cell(i);
1309 var cell = this.get_cell(i);
1310 if (cell !== null && (cell instanceof CodeCell)) {
1310 if (cell !== null && (cell instanceof CodeCell)) {
1311 cell.expand_output();
1311 cell.expand_output();
1312 this.set_dirty(true);
1312 this.set_dirty(true);
1313 }
1313 }
1314 };
1314 };
1315
1315
1316 /**
1316 /**
1317 * Expand each code cell's output area, and remove scrollbars.
1317 * Expand each code cell's output area, and remove scrollbars.
1318 *
1318 *
1319 * @method expand_all_output
1319 * @method expand_all_output
1320 */
1320 */
1321 Notebook.prototype.expand_all_output = function () {
1321 Notebook.prototype.expand_all_output = function () {
1322 $.map(this.get_cells(), function (cell, i) {
1322 $.map(this.get_cells(), function (cell, i) {
1323 if (cell instanceof CodeCell) {
1323 if (cell instanceof CodeCell) {
1324 cell.expand_output();
1324 cell.expand_output();
1325 }
1325 }
1326 });
1326 });
1327 // this should not be set if the `collapse` key is removed from nbformat
1327 // this should not be set if the `collapse` key is removed from nbformat
1328 this.set_dirty(true);
1328 this.set_dirty(true);
1329 };
1329 };
1330
1330
1331 /**
1331 /**
1332 * Clear the selected CodeCell's output area.
1332 * Clear the selected CodeCell's output area.
1333 *
1333 *
1334 * @method clear_output
1334 * @method clear_output
1335 * @param {Number} index A cell's numeric index
1335 * @param {Number} index A cell's numeric index
1336 */
1336 */
1337 Notebook.prototype.clear_output = function (index) {
1337 Notebook.prototype.clear_output = function (index) {
1338 var i = this.index_or_selected(index);
1338 var i = this.index_or_selected(index);
1339 var cell = this.get_cell(i);
1339 var cell = this.get_cell(i);
1340 if (cell !== null && (cell instanceof CodeCell)) {
1340 if (cell !== null && (cell instanceof CodeCell)) {
1341 cell.clear_output();
1341 cell.clear_output();
1342 this.set_dirty(true);
1342 this.set_dirty(true);
1343 }
1343 }
1344 };
1344 };
1345
1345
1346 /**
1346 /**
1347 * Clear each code cell's output area.
1347 * Clear each code cell's output area.
1348 *
1348 *
1349 * @method clear_all_output
1349 * @method clear_all_output
1350 */
1350 */
1351 Notebook.prototype.clear_all_output = function () {
1351 Notebook.prototype.clear_all_output = function () {
1352 $.map(this.get_cells(), function (cell, i) {
1352 $.map(this.get_cells(), function (cell, i) {
1353 if (cell instanceof CodeCell) {
1353 if (cell instanceof CodeCell) {
1354 cell.clear_output();
1354 cell.clear_output();
1355 }
1355 }
1356 });
1356 });
1357 this.set_dirty(true);
1357 this.set_dirty(true);
1358 };
1358 };
1359
1359
1360 /**
1360 /**
1361 * Scroll the selected CodeCell's output area.
1361 * Scroll the selected CodeCell's output area.
1362 *
1362 *
1363 * @method scroll_output
1363 * @method scroll_output
1364 * @param {Number} index A cell's numeric index
1364 * @param {Number} index A cell's numeric index
1365 */
1365 */
1366 Notebook.prototype.scroll_output = function (index) {
1366 Notebook.prototype.scroll_output = function (index) {
1367 var i = this.index_or_selected(index);
1367 var i = this.index_or_selected(index);
1368 var cell = this.get_cell(i);
1368 var cell = this.get_cell(i);
1369 if (cell !== null && (cell instanceof CodeCell)) {
1369 if (cell !== null && (cell instanceof CodeCell)) {
1370 cell.scroll_output();
1370 cell.scroll_output();
1371 this.set_dirty(true);
1371 this.set_dirty(true);
1372 }
1372 }
1373 };
1373 };
1374
1374
1375 /**
1375 /**
1376 * Expand each code cell's output area, and add a scrollbar for long output.
1376 * Expand each code cell's output area, and add a scrollbar for long output.
1377 *
1377 *
1378 * @method scroll_all_output
1378 * @method scroll_all_output
1379 */
1379 */
1380 Notebook.prototype.scroll_all_output = function () {
1380 Notebook.prototype.scroll_all_output = function () {
1381 $.map(this.get_cells(), function (cell, i) {
1381 $.map(this.get_cells(), function (cell, i) {
1382 if (cell instanceof CodeCell) {
1382 if (cell instanceof CodeCell) {
1383 cell.scroll_output();
1383 cell.scroll_output();
1384 }
1384 }
1385 });
1385 });
1386 // this should not be set if the `collapse` key is removed from nbformat
1386 // this should not be set if the `collapse` key is removed from nbformat
1387 this.set_dirty(true);
1387 this.set_dirty(true);
1388 };
1388 };
1389
1389
1390 /** Toggle whether a cell's output is collapsed or expanded.
1390 /** Toggle whether a cell's output is collapsed or expanded.
1391 *
1391 *
1392 * @method toggle_output
1392 * @method toggle_output
1393 * @param {Number} index A cell's numeric index
1393 * @param {Number} index A cell's numeric index
1394 */
1394 */
1395 Notebook.prototype.toggle_output = function (index) {
1395 Notebook.prototype.toggle_output = function (index) {
1396 var i = this.index_or_selected(index);
1396 var i = this.index_or_selected(index);
1397 var cell = this.get_cell(i);
1397 var cell = this.get_cell(i);
1398 if (cell !== null && (cell instanceof CodeCell)) {
1398 if (cell !== null && (cell instanceof CodeCell)) {
1399 cell.toggle_output();
1399 cell.toggle_output();
1400 this.set_dirty(true);
1400 this.set_dirty(true);
1401 }
1401 }
1402 };
1402 };
1403
1403
1404 /**
1404 /**
1405 * Hide/show the output of all cells.
1405 * Hide/show the output of all cells.
1406 *
1406 *
1407 * @method toggle_all_output
1407 * @method toggle_all_output
1408 */
1408 */
1409 Notebook.prototype.toggle_all_output = function () {
1409 Notebook.prototype.toggle_all_output = function () {
1410 $.map(this.get_cells(), function (cell, i) {
1410 $.map(this.get_cells(), function (cell, i) {
1411 if (cell instanceof CodeCell) {
1411 if (cell instanceof CodeCell) {
1412 cell.toggle_output();
1412 cell.toggle_output();
1413 }
1413 }
1414 });
1414 });
1415 // this should not be set if the `collapse` key is removed from nbformat
1415 // this should not be set if the `collapse` key is removed from nbformat
1416 this.set_dirty(true);
1416 this.set_dirty(true);
1417 };
1417 };
1418
1418
1419 /**
1419 /**
1420 * Toggle a scrollbar for long cell outputs.
1420 * Toggle a scrollbar for long cell outputs.
1421 *
1421 *
1422 * @method toggle_output_scroll
1422 * @method toggle_output_scroll
1423 * @param {Number} index A cell's numeric index
1423 * @param {Number} index A cell's numeric index
1424 */
1424 */
1425 Notebook.prototype.toggle_output_scroll = function (index) {
1425 Notebook.prototype.toggle_output_scroll = function (index) {
1426 var i = this.index_or_selected(index);
1426 var i = this.index_or_selected(index);
1427 var cell = this.get_cell(i);
1427 var cell = this.get_cell(i);
1428 if (cell !== null && (cell instanceof CodeCell)) {
1428 if (cell !== null && (cell instanceof CodeCell)) {
1429 cell.toggle_output_scroll();
1429 cell.toggle_output_scroll();
1430 this.set_dirty(true);
1430 this.set_dirty(true);
1431 }
1431 }
1432 };
1432 };
1433
1433
1434 /**
1434 /**
1435 * Toggle the scrolling of long output on all cells.
1435 * Toggle the scrolling of long output on all cells.
1436 *
1436 *
1437 * @method toggle_all_output_scrolling
1437 * @method toggle_all_output_scrolling
1438 */
1438 */
1439 Notebook.prototype.toggle_all_output_scroll = function () {
1439 Notebook.prototype.toggle_all_output_scroll = function () {
1440 $.map(this.get_cells(), function (cell, i) {
1440 $.map(this.get_cells(), function (cell, i) {
1441 if (cell instanceof CodeCell) {
1441 if (cell instanceof CodeCell) {
1442 cell.toggle_output_scroll();
1442 cell.toggle_output_scroll();
1443 }
1443 }
1444 });
1444 });
1445 // this should not be set if the `collapse` key is removed from nbformat
1445 // this should not be set if the `collapse` key is removed from nbformat
1446 this.set_dirty(true);
1446 this.set_dirty(true);
1447 };
1447 };
1448
1448
1449 // Other cell functions: line numbers, ...
1449 // Other cell functions: line numbers, ...
1450
1450
1451 /**
1451 /**
1452 * Toggle line numbers in the selected cell's input area.
1452 * Toggle line numbers in the selected cell's input area.
1453 *
1453 *
1454 * @method cell_toggle_line_numbers
1454 * @method cell_toggle_line_numbers
1455 */
1455 */
1456 Notebook.prototype.cell_toggle_line_numbers = function() {
1456 Notebook.prototype.cell_toggle_line_numbers = function() {
1457 this.get_selected_cell().toggle_line_numbers();
1457 this.get_selected_cell().toggle_line_numbers();
1458 };
1458 };
1459
1459
1460 // Session related things
1460 // Session related things
1461
1461
1462 /**
1462 /**
1463 * Start a new session and set it on each code cell.
1463 * Start a new session and set it on each code cell.
1464 *
1464 *
1465 * @method start_session
1465 * @method start_session
1466 */
1466 */
1467 Notebook.prototype.start_session = function () {
1467 Notebook.prototype.start_session = function () {
1468 this.session = new Session(this, this.options);
1468 this.session = new Session(this, this.options);
1469 this.session.start($.proxy(this._session_started, this));
1469 this.session.start($.proxy(this._session_started, this));
1470 };
1470 };
1471
1471
1472
1472
1473 /**
1473 /**
1474 * Once a session is started, link the code cells to the kernel and pass the
1474 * Once a session is started, link the code cells to the kernel and pass the
1475 * comm manager to the widget manager
1475 * comm manager to the widget manager
1476 *
1476 *
1477 */
1477 */
1478 Notebook.prototype._session_started = function(){
1478 Notebook.prototype._session_started = function(){
1479 this.kernel = this.session.kernel;
1479 this.kernel = this.session.kernel;
1480 var ncells = this.ncells();
1480 var ncells = this.ncells();
1481 for (var i=0; i<ncells; i++) {
1481 for (var i=0; i<ncells; i++) {
1482 var cell = this.get_cell(i);
1482 var cell = this.get_cell(i);
1483 if (cell instanceof CodeCell) {
1483 if (cell instanceof CodeCell) {
1484 cell.set_kernel(this.session.kernel);
1484 cell.set_kernel(this.session.kernel);
1485 }
1485 }
1486 }
1486 }
1487 };
1487 };
1488
1488
1489 /**
1489 /**
1490 * Prompt the user to restart the IPython kernel.
1490 * Prompt the user to restart the IPython kernel.
1491 *
1491 *
1492 * @method restart_kernel
1492 * @method restart_kernel
1493 */
1493 */
1494 Notebook.prototype.restart_kernel = function () {
1494 Notebook.prototype.restart_kernel = function () {
1495 var that = this;
1495 var that = this;
1496 Dialog.modal({
1496 Dialog.modal({
1497 title : "Restart kernel or continue running?",
1497 title : "Restart kernel or continue running?",
1498 body : $("<p/>").text(
1498 body : $("<p/>").text(
1499 'Do you want to restart the current kernel? You will lose all variables defined in it.'
1499 'Do you want to restart the current kernel? You will lose all variables defined in it.'
1500 ),
1500 ),
1501 buttons : {
1501 buttons : {
1502 "Continue running" : {},
1502 "Continue running" : {},
1503 "Restart" : {
1503 "Restart" : {
1504 "class" : "btn-danger",
1504 "class" : "btn-danger",
1505 "click" : function() {
1505 "click" : function() {
1506 that.session.restart_kernel();
1506 that.session.restart_kernel();
1507 }
1507 }
1508 }
1508 }
1509 }
1509 }
1510 });
1510 });
1511 };
1511 };
1512
1512
1513 /**
1513 /**
1514 * Execute or render cell outputs and go into command mode.
1514 * Execute or render cell outputs and go into command mode.
1515 *
1515 *
1516 * @method execute_cell
1516 * @method execute_cell
1517 */
1517 */
1518 Notebook.prototype.execute_cell = function () {
1518 Notebook.prototype.execute_cell = function () {
1519 // mode = shift, ctrl, alt
1519 // mode = shift, ctrl, alt
1520 var cell = this.get_selected_cell();
1520 var cell = this.get_selected_cell();
1521 var cell_index = this.find_cell_index(cell);
1521 var cell_index = this.find_cell_index(cell);
1522
1522
1523 cell.execute();
1523 cell.execute();
1524 this.command_mode();
1524 this.command_mode();
1525 this.set_dirty(true);
1525 this.set_dirty(true);
1526 };
1526 };
1527
1527
1528 /**
1528 /**
1529 * Execute or render cell outputs and insert a new cell below.
1529 * Execute or render cell outputs and insert a new cell below.
1530 *
1530 *
1531 * @method execute_cell_and_insert_below
1531 * @method execute_cell_and_insert_below
1532 */
1532 */
1533 Notebook.prototype.execute_cell_and_insert_below = function () {
1533 Notebook.prototype.execute_cell_and_insert_below = function () {
1534 var cell = this.get_selected_cell();
1534 var cell = this.get_selected_cell();
1535 var cell_index = this.find_cell_index(cell);
1535 var cell_index = this.find_cell_index(cell);
1536
1536
1537 cell.execute();
1537 cell.execute();
1538
1538
1539 // If we are at the end always insert a new cell and return
1539 // If we are at the end always insert a new cell and return
1540 if (cell_index === (this.ncells()-1)) {
1540 if (cell_index === (this.ncells()-1)) {
1541 this.command_mode();
1541 this.command_mode();
1542 this.insert_cell_below();
1542 this.insert_cell_below();
1543 this.select(cell_index+1);
1543 this.select(cell_index+1);
1544 this.edit_mode();
1544 this.edit_mode();
1545 this.scroll_to_bottom();
1545 this.scroll_to_bottom();
1546 this.set_dirty(true);
1546 this.set_dirty(true);
1547 return;
1547 return;
1548 }
1548 }
1549
1549
1550 this.command_mode();
1550 this.command_mode();
1551 this.insert_cell_below();
1551 this.insert_cell_below();
1552 this.select(cell_index+1);
1552 this.select(cell_index+1);
1553 this.edit_mode();
1553 this.edit_mode();
1554 this.set_dirty(true);
1554 this.set_dirty(true);
1555 };
1555 };
1556
1556
1557 /**
1557 /**
1558 * Execute or render cell outputs and select the next cell.
1558 * Execute or render cell outputs and select the next cell.
1559 *
1559 *
1560 * @method execute_cell_and_select_below
1560 * @method execute_cell_and_select_below
1561 */
1561 */
1562 Notebook.prototype.execute_cell_and_select_below = function () {
1562 Notebook.prototype.execute_cell_and_select_below = function () {
1563
1563
1564 var cell = this.get_selected_cell();
1564 var cell = this.get_selected_cell();
1565 var cell_index = this.find_cell_index(cell);
1565 var cell_index = this.find_cell_index(cell);
1566
1566
1567 cell.execute();
1567 cell.execute();
1568
1568
1569 // If we are at the end always insert a new cell and return
1569 // If we are at the end always insert a new cell and return
1570 if (cell_index === (this.ncells()-1)) {
1570 if (cell_index === (this.ncells()-1)) {
1571 this.command_mode();
1571 this.command_mode();
1572 this.insert_cell_below();
1572 this.insert_cell_below();
1573 this.select(cell_index+1);
1573 this.select(cell_index+1);
1574 this.edit_mode();
1574 this.edit_mode();
1575 this.scroll_to_bottom();
1575 this.scroll_to_bottom();
1576 this.set_dirty(true);
1576 this.set_dirty(true);
1577 return;
1577 return;
1578 }
1578 }
1579
1579
1580 this.command_mode();
1580 this.command_mode();
1581 this.select(cell_index+1);
1581 this.select(cell_index+1);
1582 this.focus_cell();
1582 this.focus_cell();
1583 this.set_dirty(true);
1583 this.set_dirty(true);
1584 };
1584 };
1585
1585
1586 /**
1586 /**
1587 * Execute all cells below the selected cell.
1587 * Execute all cells below the selected cell.
1588 *
1588 *
1589 * @method execute_cells_below
1589 * @method execute_cells_below
1590 */
1590 */
1591 Notebook.prototype.execute_cells_below = function () {
1591 Notebook.prototype.execute_cells_below = function () {
1592 this.execute_cell_range(this.get_selected_index(), this.ncells());
1592 this.execute_cell_range(this.get_selected_index(), this.ncells());
1593 this.scroll_to_bottom();
1593 this.scroll_to_bottom();
1594 };
1594 };
1595
1595
1596 /**
1596 /**
1597 * Execute all cells above the selected cell.
1597 * Execute all cells above the selected cell.
1598 *
1598 *
1599 * @method execute_cells_above
1599 * @method execute_cells_above
1600 */
1600 */
1601 Notebook.prototype.execute_cells_above = function () {
1601 Notebook.prototype.execute_cells_above = function () {
1602 this.execute_cell_range(0, this.get_selected_index());
1602 this.execute_cell_range(0, this.get_selected_index());
1603 };
1603 };
1604
1604
1605 /**
1605 /**
1606 * Execute all cells.
1606 * Execute all cells.
1607 *
1607 *
1608 * @method execute_all_cells
1608 * @method execute_all_cells
1609 */
1609 */
1610 Notebook.prototype.execute_all_cells = function () {
1610 Notebook.prototype.execute_all_cells = function () {
1611 this.execute_cell_range(0, this.ncells());
1611 this.execute_cell_range(0, this.ncells());
1612 this.scroll_to_bottom();
1612 this.scroll_to_bottom();
1613 };
1613 };
1614
1614
1615 /**
1615 /**
1616 * Execute a contiguous range of cells.
1616 * Execute a contiguous range of cells.
1617 *
1617 *
1618 * @method execute_cell_range
1618 * @method execute_cell_range
1619 * @param {Number} start Index of the first cell to execute (inclusive)
1619 * @param {Number} start Index of the first cell to execute (inclusive)
1620 * @param {Number} end Index of the last cell to execute (exclusive)
1620 * @param {Number} end Index of the last cell to execute (exclusive)
1621 */
1621 */
1622 Notebook.prototype.execute_cell_range = function (start, end) {
1622 Notebook.prototype.execute_cell_range = function (start, end) {
1623 this.command_mode();
1623 this.command_mode();
1624 for (var i=start; i<end; i++) {
1624 for (var i=start; i<end; i++) {
1625 this.select(i);
1625 this.select(i);
1626 this.execute_cell();
1626 this.execute_cell();
1627 }
1627 }
1628 };
1628 };
1629
1629
1630 // Persistance and loading
1630 // Persistance and loading
1631
1631
1632 /**
1632 /**
1633 * Getter method for this notebook's name.
1633 * Getter method for this notebook's name.
1634 *
1634 *
1635 * @method get_notebook_name
1635 * @method get_notebook_name
1636 * @return {String} This notebook's name (excluding file extension)
1636 * @return {String} This notebook's name (excluding file extension)
1637 */
1637 */
1638 Notebook.prototype.get_notebook_name = function () {
1638 Notebook.prototype.get_notebook_name = function () {
1639 var nbname = this.notebook_name.substring(0,this.notebook_name.length-6);
1639 var nbname = this.notebook_name.substring(0,this.notebook_name.length-6);
1640 return nbname;
1640 return nbname;
1641 };
1641 };
1642
1642
1643 /**
1643 /**
1644 * Setter method for this notebook's name.
1644 * Setter method for this notebook's name.
1645 *
1645 *
1646 * @method set_notebook_name
1646 * @method set_notebook_name
1647 * @param {String} name A new name for this notebook
1647 * @param {String} name A new name for this notebook
1648 */
1648 */
1649 Notebook.prototype.set_notebook_name = function (name) {
1649 Notebook.prototype.set_notebook_name = function (name) {
1650 this.notebook_name = name;
1650 this.notebook_name = name;
1651 };
1651 };
1652
1652
1653 /**
1653 /**
1654 * Check that a notebook's name is valid.
1654 * Check that a notebook's name is valid.
1655 *
1655 *
1656 * @method test_notebook_name
1656 * @method test_notebook_name
1657 * @param {String} nbname A name for this notebook
1657 * @param {String} nbname A name for this notebook
1658 * @return {Boolean} True if the name is valid, false if invalid
1658 * @return {Boolean} True if the name is valid, false if invalid
1659 */
1659 */
1660 Notebook.prototype.test_notebook_name = function (nbname) {
1660 Notebook.prototype.test_notebook_name = function (nbname) {
1661 nbname = nbname || '';
1661 nbname = nbname || '';
1662 if (nbname.length>0 && !this.notebook_name_blacklist_re.test(nbname)) {
1662 if (nbname.length>0 && !this.notebook_name_blacklist_re.test(nbname)) {
1663 return true;
1663 return true;
1664 } else {
1664 } else {
1665 return false;
1665 return false;
1666 }
1666 }
1667 };
1667 };
1668
1668
1669 /**
1669 /**
1670 * Load a notebook from JSON (.ipynb).
1670 * Load a notebook from JSON (.ipynb).
1671 *
1671 *
1672 * This currently handles one worksheet: others are deleted.
1672 * This currently handles one worksheet: others are deleted.
1673 *
1673 *
1674 * @method fromJSON
1674 * @method fromJSON
1675 * @param {Object} data JSON representation of a notebook
1675 * @param {Object} data JSON representation of a notebook
1676 */
1676 */
1677 Notebook.prototype.fromJSON = function (data) {
1677 Notebook.prototype.fromJSON = function (data) {
1678 var content = data.content;
1678 var content = data.content;
1679 var ncells = this.ncells();
1679 var ncells = this.ncells();
1680 var i;
1680 var i;
1681 for (i=0; i<ncells; i++) {
1681 for (i=0; i<ncells; i++) {
1682 // Always delete cell 0 as they get renumbered as they are deleted.
1682 // Always delete cell 0 as they get renumbered as they are deleted.
1683 this.delete_cell(0);
1683 this.delete_cell(0);
1684 }
1684 }
1685 // Save the metadata and name.
1685 // Save the metadata and name.
1686 this.metadata = content.metadata;
1686 this.metadata = content.metadata;
1687 this.notebook_name = data.name;
1687 this.notebook_name = data.name;
1688 var trusted = true;
1688 var trusted = true;
1689 // Only handle 1 worksheet for now.
1689 // Only handle 1 worksheet for now.
1690 var worksheet = content.worksheets[0];
1690 var worksheet = content.worksheets[0];
1691 if (worksheet !== undefined) {
1691 if (worksheet !== undefined) {
1692 if (worksheet.metadata) {
1692 if (worksheet.metadata) {
1693 this.worksheet_metadata = worksheet.metadata;
1693 this.worksheet_metadata = worksheet.metadata;
1694 }
1694 }
1695 var new_cells = worksheet.cells;
1695 var new_cells = worksheet.cells;
1696 ncells = new_cells.length;
1696 ncells = new_cells.length;
1697 var cell_data = null;
1697 var cell_data = null;
1698 var new_cell = null;
1698 var new_cell = null;
1699 for (i=0; i<ncells; i++) {
1699 for (i=0; i<ncells; i++) {
1700 cell_data = new_cells[i];
1700 cell_data = new_cells[i];
1701 // VERSIONHACK: plaintext -> raw
1701 // VERSIONHACK: plaintext -> raw
1702 // handle never-released plaintext name for raw cells
1702 // handle never-released plaintext name for raw cells
1703 if (cell_data.cell_type === 'plaintext'){
1703 if (cell_data.cell_type === 'plaintext'){
1704 cell_data.cell_type = 'raw';
1704 cell_data.cell_type = 'raw';
1705 }
1705 }
1706
1706
1707 new_cell = this.insert_cell_at_index(cell_data.cell_type, i);
1707 new_cell = this.insert_cell_at_index(cell_data.cell_type, i);
1708 new_cell.fromJSON(cell_data);
1708 new_cell.fromJSON(cell_data);
1709 if (new_cell.cell_type == 'code' && !new_cell.output_area.trusted) {
1709 if (new_cell.cell_type == 'code' && !new_cell.output_area.trusted) {
1710 trusted = false;
1710 trusted = false;
1711 }
1711 }
1712 }
1712 }
1713 }
1713 }
1714 if (trusted != this.trusted) {
1714 if (trusted != this.trusted) {
1715 this.trusted = trusted;
1715 this.trusted = trusted;
1716 this.events.trigger("trust_changed.Notebook", trusted);
1716 this.events.trigger("trust_changed.Notebook", trusted);
1717 }
1717 }
1718 if (content.worksheets.length > 1) {
1718 if (content.worksheets.length > 1) {
1719 Dialog.modal({
1719 Dialog.modal({
1720 title : "Multiple worksheets",
1720 title : "Multiple worksheets",
1721 body : "This notebook has " + data.worksheets.length + " worksheets, " +
1721 body : "This notebook has " + data.worksheets.length + " worksheets, " +
1722 "but this version of IPython can only handle the first. " +
1722 "but this version of IPython can only handle the first. " +
1723 "If you save this notebook, worksheets after the first will be lost.",
1723 "If you save this notebook, worksheets after the first will be lost.",
1724 buttons : {
1724 buttons : {
1725 OK : {
1725 OK : {
1726 class : "btn-danger"
1726 class : "btn-danger"
1727 }
1727 }
1728 }
1728 }
1729 });
1729 });
1730 }
1730 }
1731 };
1731 };
1732
1732
1733 /**
1733 /**
1734 * Dump this notebook into a JSON-friendly object.
1734 * Dump this notebook into a JSON-friendly object.
1735 *
1735 *
1736 * @method toJSON
1736 * @method toJSON
1737 * @return {Object} A JSON-friendly representation of this notebook.
1737 * @return {Object} A JSON-friendly representation of this notebook.
1738 */
1738 */
1739 Notebook.prototype.toJSON = function () {
1739 Notebook.prototype.toJSON = function () {
1740 var cells = this.get_cells();
1740 var cells = this.get_cells();
1741 var ncells = cells.length;
1741 var ncells = cells.length;
1742 var cell_array = new Array(ncells);
1742 var cell_array = new Array(ncells);
1743 var trusted = true;
1743 var trusted = true;
1744 for (var i=0; i<ncells; i++) {
1744 for (var i=0; i<ncells; i++) {
1745 var cell = cells[i];
1745 var cell = cells[i];
1746 if (cell.cell_type == 'code' && !cell.output_area.trusted) {
1746 if (cell.cell_type == 'code' && !cell.output_area.trusted) {
1747 trusted = false;
1747 trusted = false;
1748 }
1748 }
1749 cell_array[i] = cell.toJSON();
1749 cell_array[i] = cell.toJSON();
1750 }
1750 }
1751 var data = {
1751 var data = {
1752 // Only handle 1 worksheet for now.
1752 // Only handle 1 worksheet for now.
1753 worksheets : [{
1753 worksheets : [{
1754 cells: cell_array,
1754 cells: cell_array,
1755 metadata: this.worksheet_metadata
1755 metadata: this.worksheet_metadata
1756 }],
1756 }],
1757 metadata : this.metadata
1757 metadata : this.metadata
1758 };
1758 };
1759 if (trusted != this.trusted) {
1759 if (trusted != this.trusted) {
1760 this.trusted = trusted;
1760 this.trusted = trusted;
1761 this.events.trigger("trust_changed.Notebook", trusted);
1761 this.events.trigger("trust_changed.Notebook", trusted);
1762 }
1762 }
1763 return data;
1763 return data;
1764 };
1764 };
1765
1765
1766 /**
1766 /**
1767 * Start an autosave timer, for periodically saving the notebook.
1767 * Start an autosave timer, for periodically saving the notebook.
1768 *
1768 *
1769 * @method set_autosave_interval
1769 * @method set_autosave_interval
1770 * @param {Integer} interval the autosave interval in milliseconds
1770 * @param {Integer} interval the autosave interval in milliseconds
1771 */
1771 */
1772 Notebook.prototype.set_autosave_interval = function (interval) {
1772 Notebook.prototype.set_autosave_interval = function (interval) {
1773 var that = this;
1773 var that = this;
1774 // clear previous interval, so we don't get simultaneous timers
1774 // clear previous interval, so we don't get simultaneous timers
1775 if (this.autosave_timer) {
1775 if (this.autosave_timer) {
1776 clearInterval(this.autosave_timer);
1776 clearInterval(this.autosave_timer);
1777 }
1777 }
1778
1778
1779 this.autosave_interval = this.minimum_autosave_interval = interval;
1779 this.autosave_interval = this.minimum_autosave_interval = interval;
1780 if (interval) {
1780 if (interval) {
1781 this.autosave_timer = setInterval(function() {
1781 this.autosave_timer = setInterval(function() {
1782 if (that.dirty) {
1782 if (that.dirty) {
1783 that.save_notebook();
1783 that.save_notebook();
1784 }
1784 }
1785 }, interval);
1785 }, interval);
1786 this.events.trigger("autosave_enabled.Notebook", interval);
1786 this.events.trigger("autosave_enabled.Notebook", interval);
1787 } else {
1787 } else {
1788 this.autosave_timer = null;
1788 this.autosave_timer = null;
1789 this.events.trigger("autosave_disabled.Notebook");
1789 this.events.trigger("autosave_disabled.Notebook");
1790 }
1790 }
1791 };
1791 };
1792
1792
1793 /**
1793 /**
1794 * Save this notebook on the server. This becomes a notebook instance's
1794 * Save this notebook on the server. This becomes a notebook instance's
1795 * .save_notebook method *after* the entire notebook has been loaded.
1795 * .save_notebook method *after* the entire notebook has been loaded.
1796 *
1796 *
1797 * @method save_notebook
1797 * @method save_notebook
1798 */
1798 */
1799 Notebook.prototype.save_notebook = function (extra_settings) {
1799 Notebook.prototype.save_notebook = function (extra_settings) {
1800 // Create a JSON model to be sent to the server.
1800 // Create a JSON model to be sent to the server.
1801 var model = {};
1801 var model = {};
1802 model.name = this.notebook_name;
1802 model.name = this.notebook_name;
1803 model.path = this.notebook_path;
1803 model.path = this.notebook_path;
1804 model.content = this.toJSON();
1804 model.content = this.toJSON();
1805 model.content.nbformat = this.nbformat;
1805 model.content.nbformat = this.nbformat;
1806 model.content.nbformat_minor = this.nbformat_minor;
1806 model.content.nbformat_minor = this.nbformat_minor;
1807 // time the ajax call for autosave tuning purposes.
1807 // time the ajax call for autosave tuning purposes.
1808 var start = new Date().getTime();
1808 var start = new Date().getTime();
1809 // We do the call with settings so we can set cache to false.
1809 // We do the call with settings so we can set cache to false.
1810 var settings = {
1810 var settings = {
1811 processData : false,
1811 processData : false,
1812 cache : false,
1812 cache : false,
1813 type : "PUT",
1813 type : "PUT",
1814 data : JSON.stringify(model),
1814 data : JSON.stringify(model),
1815 headers : {'Content-Type': 'application/json'},
1815 headers : {'Content-Type': 'application/json'},
1816 success : $.proxy(this.save_notebook_success, this, start),
1816 success : $.proxy(this.save_notebook_success, this, start),
1817 error : $.proxy(this.save_notebook_error, this)
1817 error : $.proxy(this.save_notebook_error, this)
1818 };
1818 };
1819 if (extra_settings) {
1819 if (extra_settings) {
1820 for (var key in extra_settings) {
1820 for (var key in extra_settings) {
1821 settings[key] = extra_settings[key];
1821 settings[key] = extra_settings[key];
1822 }
1822 }
1823 }
1823 }
1824 this.events.trigger('notebook_saving.Notebook');
1824 this.events.trigger('notebook_saving.Notebook');
1825 var url = utils.url_join_encode(
1825 var url = utils.url_join_encode(
1826 this.base_url,
1826 this.base_url,
1827 'api/notebooks',
1827 'api/notebooks',
1828 this.notebook_path,
1828 this.notebook_path,
1829 this.notebook_name
1829 this.notebook_name
1830 );
1830 );
1831 $.ajax(url, settings);
1831 $.ajax(url, settings);
1832 };
1832 };
1833
1833
1834 /**
1834 /**
1835 * Success callback for saving a notebook.
1835 * Success callback for saving a notebook.
1836 *
1836 *
1837 * @method save_notebook_success
1837 * @method save_notebook_success
1838 * @param {Integer} start the time when the save request started
1838 * @param {Integer} start the time when the save request started
1839 * @param {Object} data JSON representation of a notebook
1839 * @param {Object} data JSON representation of a notebook
1840 * @param {String} status Description of response status
1840 * @param {String} status Description of response status
1841 * @param {jqXHR} xhr jQuery Ajax object
1841 * @param {jqXHR} xhr jQuery Ajax object
1842 */
1842 */
1843 Notebook.prototype.save_notebook_success = function (start, data, status, xhr) {
1843 Notebook.prototype.save_notebook_success = function (start, data, status, xhr) {
1844 this.set_dirty(false);
1844 this.set_dirty(false);
1845 this.events.trigger('notebook_saved.Notebook');
1845 this.events.trigger('notebook_saved.Notebook');
1846 this._update_autosave_interval(start);
1846 this._update_autosave_interval(start);
1847 if (this._checkpoint_after_save) {
1847 if (this._checkpoint_after_save) {
1848 this.create_checkpoint();
1848 this.create_checkpoint();
1849 this._checkpoint_after_save = false;
1849 this._checkpoint_after_save = false;
1850 }
1850 }
1851 };
1851 };
1852
1852
1853 /**
1853 /**
1854 * update the autosave interval based on how long the last save took
1854 * update the autosave interval based on how long the last save took
1855 *
1855 *
1856 * @method _update_autosave_interval
1856 * @method _update_autosave_interval
1857 * @param {Integer} timestamp when the save request started
1857 * @param {Integer} timestamp when the save request started
1858 */
1858 */
1859 Notebook.prototype._update_autosave_interval = function (start) {
1859 Notebook.prototype._update_autosave_interval = function (start) {
1860 var duration = (new Date().getTime() - start);
1860 var duration = (new Date().getTime() - start);
1861 if (this.autosave_interval) {
1861 if (this.autosave_interval) {
1862 // new save interval: higher of 10x save duration or parameter (default 30 seconds)
1862 // new save interval: higher of 10x save duration or parameter (default 30 seconds)
1863 var interval = Math.max(10 * duration, this.minimum_autosave_interval);
1863 var interval = Math.max(10 * duration, this.minimum_autosave_interval);
1864 // round to 10 seconds, otherwise we will be setting a new interval too often
1864 // round to 10 seconds, otherwise we will be setting a new interval too often
1865 interval = 10000 * Math.round(interval / 10000);
1865 interval = 10000 * Math.round(interval / 10000);
1866 // set new interval, if it's changed
1866 // set new interval, if it's changed
1867 if (interval != this.autosave_interval) {
1867 if (interval != this.autosave_interval) {
1868 this.set_autosave_interval(interval);
1868 this.set_autosave_interval(interval);
1869 }
1869 }
1870 }
1870 }
1871 };
1871 };
1872
1872
1873 /**
1873 /**
1874 * Failure callback for saving a notebook.
1874 * Failure callback for saving a notebook.
1875 *
1875 *
1876 * @method save_notebook_error
1876 * @method save_notebook_error
1877 * @param {jqXHR} xhr jQuery Ajax object
1877 * @param {jqXHR} xhr jQuery Ajax object
1878 * @param {String} status Description of response status
1878 * @param {String} status Description of response status
1879 * @param {String} error HTTP error message
1879 * @param {String} error HTTP error message
1880 */
1880 */
1881 Notebook.prototype.save_notebook_error = function (xhr, status, error) {
1881 Notebook.prototype.save_notebook_error = function (xhr, status, error) {
1882 this.events.trigger('notebook_save_failed.Notebook', [xhr, status, error]);
1882 this.events.trigger('notebook_save_failed.Notebook', [xhr, status, error]);
1883 };
1883 };
1884
1884
1885 /**
1885 /**
1886 * Explicitly trust the output of this notebook.
1886 * Explicitly trust the output of this notebook.
1887 *
1887 *
1888 * @method trust_notebook
1888 * @method trust_notebook
1889 */
1889 */
1890 Notebook.prototype.trust_notebook = function (extra_settings) {
1890 Notebook.prototype.trust_notebook = function (extra_settings) {
1891 var body = $("<div>").append($("<p>")
1891 var body = $("<div>").append($("<p>")
1892 .text("A trusted IPython notebook may execute hidden malicious code ")
1892 .text("A trusted IPython notebook may execute hidden malicious code ")
1893 .append($("<strong>")
1893 .append($("<strong>")
1894 .append(
1894 .append(
1895 $("<em>").text("when you open it")
1895 $("<em>").text("when you open it")
1896 )
1896 )
1897 ).append(".").append(
1897 ).append(".").append(
1898 " Selecting trust will immediately reload this notebook in a trusted state."
1898 " Selecting trust will immediately reload this notebook in a trusted state."
1899 ).append(
1899 ).append(
1900 " For more information, see the "
1900 " For more information, see the "
1901 ).append($("<a>").attr("href", "http://ipython.org/ipython-doc/2/notebook/security.html")
1901 ).append($("<a>").attr("href", "http://ipython.org/ipython-doc/2/notebook/security.html")
1902 .text("IPython security documentation")
1902 .text("IPython security documentation")
1903 ).append(".")
1903 ).append(".")
1904 );
1904 );
1905
1905
1906 var nb = this;
1906 var nb = this;
1907 Dialog.modal({
1907 Dialog.modal({
1908 title: "Trust this notebook?",
1908 title: "Trust this notebook?",
1909 body: body,
1909 body: body,
1910
1910
1911 buttons: {
1911 buttons: {
1912 Cancel : {},
1912 Cancel : {},
1913 Trust : {
1913 Trust : {
1914 class : "btn-danger",
1914 class : "btn-danger",
1915 click : function () {
1915 click : function () {
1916 var cells = nb.get_cells();
1916 var cells = nb.get_cells();
1917 for (var i = 0; i < cells.length; i++) {
1917 for (var i = 0; i < cells.length; i++) {
1918 var cell = cells[i];
1918 var cell = cells[i];
1919 if (cell.cell_type == 'code') {
1919 if (cell.cell_type == 'code') {
1920 cell.output_area.trusted = true;
1920 cell.output_area.trusted = true;
1921 }
1921 }
1922 }
1922 }
1923 this.events.on('notebook_saved.Notebook', function () {
1923 this.events.on('notebook_saved.Notebook', function () {
1924 window.location.reload();
1924 window.location.reload();
1925 });
1925 });
1926 nb.save_notebook();
1926 nb.save_notebook();
1927 }
1927 }
1928 }
1928 }
1929 }
1929 }
1930 });
1930 });
1931 };
1931 };
1932
1932
1933 Notebook.prototype.new_notebook = function(){
1933 Notebook.prototype.new_notebook = function(){
1934 var path = this.notebook_path;
1934 var path = this.notebook_path;
1935 var base_url = this.base_url;
1935 var base_url = this.base_url;
1936 var settings = {
1936 var settings = {
1937 processData : false,
1937 processData : false,
1938 cache : false,
1938 cache : false,
1939 type : "POST",
1939 type : "POST",
1940 dataType : "json",
1940 dataType : "json",
1941 async : false,
1941 async : false,
1942 success : function (data, status, xhr){
1942 success : function (data, status, xhr){
1943 var notebook_name = data.name;
1943 var notebook_name = data.name;
1944 window.open(
1944 window.open(
1945 utils.url_join_encode(
1945 utils.url_join_encode(
1946 base_url,
1946 base_url,
1947 'notebooks',
1947 'notebooks',
1948 path,
1948 path,
1949 notebook_name
1949 notebook_name
1950 ),
1950 ),
1951 '_blank'
1951 '_blank'
1952 );
1952 );
1953 },
1953 },
1954 error : utils.log_ajax_error,
1954 error : utils.log_ajax_error,
1955 };
1955 };
1956 var url = utils.url_join_encode(
1956 var url = utils.url_join_encode(
1957 base_url,
1957 base_url,
1958 'api/notebooks',
1958 'api/notebooks',
1959 path
1959 path
1960 );
1960 );
1961 $.ajax(url,settings);
1961 $.ajax(url,settings);
1962 };
1962 };
1963
1963
1964
1964
1965 Notebook.prototype.copy_notebook = function(){
1965 Notebook.prototype.copy_notebook = function(){
1966 var path = this.notebook_path;
1966 var path = this.notebook_path;
1967 var base_url = this.base_url;
1967 var base_url = this.base_url;
1968 var settings = {
1968 var settings = {
1969 processData : false,
1969 processData : false,
1970 cache : false,
1970 cache : false,
1971 type : "POST",
1971 type : "POST",
1972 dataType : "json",
1972 dataType : "json",
1973 data : JSON.stringify({copy_from : this.notebook_name}),
1973 data : JSON.stringify({copy_from : this.notebook_name}),
1974 async : false,
1974 async : false,
1975 success : function (data, status, xhr) {
1975 success : function (data, status, xhr) {
1976 window.open(utils.url_join_encode(
1976 window.open(utils.url_join_encode(
1977 base_url,
1977 base_url,
1978 'notebooks',
1978 'notebooks',
1979 data.path,
1979 data.path,
1980 data.name
1980 data.name
1981 ), '_blank');
1981 ), '_blank');
1982 },
1982 },
1983 error : utils.log_ajax_error,
1983 error : utils.log_ajax_error,
1984 };
1984 };
1985 var url = utils.url_join_encode(
1985 var url = utils.url_join_encode(
1986 base_url,
1986 base_url,
1987 'api/notebooks',
1987 'api/notebooks',
1988 path
1988 path
1989 );
1989 );
1990 $.ajax(url,settings);
1990 $.ajax(url,settings);
1991 };
1991 };
1992
1992
1993 Notebook.prototype.rename = function (nbname) {
1993 Notebook.prototype.rename = function (nbname) {
1994 var that = this;
1994 var that = this;
1995 if (!nbname.match(/\.ipynb$/)) {
1995 if (!nbname.match(/\.ipynb$/)) {
1996 nbname = nbname + ".ipynb";
1996 nbname = nbname + ".ipynb";
1997 }
1997 }
1998 var data = {name: nbname};
1998 var data = {name: nbname};
1999 var settings = {
1999 var settings = {
2000 processData : false,
2000 processData : false,
2001 cache : false,
2001 cache : false,
2002 type : "PATCH",
2002 type : "PATCH",
2003 data : JSON.stringify(data),
2003 data : JSON.stringify(data),
2004 dataType: "json",
2004 dataType: "json",
2005 headers : {'Content-Type': 'application/json'},
2005 headers : {'Content-Type': 'application/json'},
2006 success : $.proxy(that.rename_success, this),
2006 success : $.proxy(that.rename_success, this),
2007 error : $.proxy(that.rename_error, this)
2007 error : $.proxy(that.rename_error, this)
2008 };
2008 };
2009 this.events.trigger('rename_notebook.Notebook', data);
2009 this.events.trigger('rename_notebook.Notebook', data);
2010 var url = utils.url_join_encode(
2010 var url = utils.url_join_encode(
2011 this.base_url,
2011 this.base_url,
2012 'api/notebooks',
2012 'api/notebooks',
2013 this.notebook_path,
2013 this.notebook_path,
2014 this.notebook_name
2014 this.notebook_name
2015 );
2015 );
2016 $.ajax(url, settings);
2016 $.ajax(url, settings);
2017 };
2017 };
2018
2018
2019 Notebook.prototype.delete = function () {
2019 Notebook.prototype.delete = function () {
2020 var that = this;
2020 var that = this;
2021 var settings = {
2021 var settings = {
2022 processData : false,
2022 processData : false,
2023 cache : false,
2023 cache : false,
2024 type : "DELETE",
2024 type : "DELETE",
2025 dataType: "json",
2025 dataType: "json",
2026 error : utils.log_ajax_error,
2026 error : utils.log_ajax_error,
2027 };
2027 };
2028 var url = utils.url_join_encode(
2028 var url = utils.url_join_encode(
2029 this.base_url,
2029 this.base_url,
2030 'api/notebooks',
2030 'api/notebooks',
2031 this.notebook_path,
2031 this.notebook_path,
2032 this.notebook_name
2032 this.notebook_name
2033 );
2033 );
2034 $.ajax(url, settings);
2034 $.ajax(url, settings);
2035 };
2035 };
2036
2036
2037
2037
2038 Notebook.prototype.rename_success = function (json, status, xhr) {
2038 Notebook.prototype.rename_success = function (json, status, xhr) {
2039 var name = this.notebook_name = json.name;
2039 var name = this.notebook_name = json.name;
2040 var path = json.path;
2040 var path = json.path;
2041 this.session.rename_notebook(name, path);
2041 this.session.rename_notebook(name, path);
2042 this.events.trigger('notebook_renamed.Notebook', json);
2042 this.events.trigger('notebook_renamed.Notebook', json);
2043 };
2043 };
2044
2044
2045 Notebook.prototype.rename_error = function (xhr, status, error) {
2045 Notebook.prototype.rename_error = function (xhr, status, error) {
2046 var that = this;
2046 var that = this;
2047 var dialog = $('<div/>').append(
2047 var dialog = $('<div/>').append(
2048 $("<p/>").addClass("rename-message")
2048 $("<p/>").addClass("rename-message")
2049 .text('This notebook name already exists.')
2049 .text('This notebook name already exists.')
2050 );
2050 );
2051 this.events.trigger('notebook_rename_failed.Notebook', [xhr, status, error]);
2051 this.events.trigger('notebook_rename_failed.Notebook', [xhr, status, error]);
2052 Dialog.modal({
2052 Dialog.modal({
2053 title: "Notebook Rename Error!",
2053 title: "Notebook Rename Error!",
2054 body: dialog,
2054 body: dialog,
2055 buttons : {
2055 buttons : {
2056 "Cancel": {},
2056 "Cancel": {},
2057 "OK": {
2057 "OK": {
2058 class: "btn-primary",
2058 class: "btn-primary",
2059 click: function () {
2059 click: function () {
2060 this.save_widget.rename_notebook();
2060 this.save_widget.rename_notebook();
2061 }}
2061 }}
2062 },
2062 },
2063 open : function (event, ui) {
2063 open : function (event, ui) {
2064 var that = $(this);
2064 var that = $(this);
2065 // Upon ENTER, click the OK button.
2065 // Upon ENTER, click the OK button.
2066 that.find('input[type="text"]').keydown(function (event, ui) {
2066 that.find('input[type="text"]').keydown(function (event, ui) {
2067 if (event.which === this.keyboard.keycodes.enter) {
2067 if (event.which === this.keyboard.keycodes.enter) {
2068 that.find('.btn-primary').first().click();
2068 that.find('.btn-primary').first().click();
2069 }
2069 }
2070 });
2070 });
2071 that.find('input[type="text"]').focus();
2071 that.find('input[type="text"]').focus();
2072 }
2072 }
2073 });
2073 });
2074 };
2074 };
2075
2075
2076 /**
2076 /**
2077 * Request a notebook's data from the server.
2077 * Request a notebook's data from the server.
2078 *
2078 *
2079 * @method load_notebook
2079 * @method load_notebook
2080 * @param {String} notebook_name and path A notebook to load
2080 * @param {String} notebook_name and path A notebook to load
2081 */
2081 */
2082 Notebook.prototype.load_notebook = function (notebook_name, notebook_path) {
2082 Notebook.prototype.load_notebook = function (notebook_name, notebook_path) {
2083 var that = this;
2083 var that = this;
2084 this.notebook_name = notebook_name;
2084 this.notebook_name = notebook_name;
2085 this.notebook_path = notebook_path;
2085 this.notebook_path = notebook_path;
2086 // We do the call with settings so we can set cache to false.
2086 // We do the call with settings so we can set cache to false.
2087 var settings = {
2087 var settings = {
2088 processData : false,
2088 processData : false,
2089 cache : false,
2089 cache : false,
2090 type : "GET",
2090 type : "GET",
2091 dataType : "json",
2091 dataType : "json",
2092 success : $.proxy(this.load_notebook_success,this),
2092 success : $.proxy(this.load_notebook_success,this),
2093 error : $.proxy(this.load_notebook_error,this),
2093 error : $.proxy(this.load_notebook_error,this),
2094 };
2094 };
2095 this.events.trigger('notebook_loading.Notebook');
2095 this.events.trigger('notebook_loading.Notebook');
2096 var url = utils.url_join_encode(
2096 var url = utils.url_join_encode(
2097 this.base_url,
2097 this.base_url,
2098 'api/notebooks',
2098 'api/notebooks',
2099 this.notebook_path,
2099 this.notebook_path,
2100 this.notebook_name
2100 this.notebook_name
2101 );
2101 );
2102 $.ajax(url, settings);
2102 $.ajax(url, settings);
2103 };
2103 };
2104
2104
2105 /**
2105 /**
2106 * Success callback for loading a notebook from the server.
2106 * Success callback for loading a notebook from the server.
2107 *
2107 *
2108 * Load notebook data from the JSON response.
2108 * Load notebook data from the JSON response.
2109 *
2109 *
2110 * @method load_notebook_success
2110 * @method load_notebook_success
2111 * @param {Object} data JSON representation of a notebook
2111 * @param {Object} data JSON representation of a notebook
2112 * @param {String} status Description of response status
2112 * @param {String} status Description of response status
2113 * @param {jqXHR} xhr jQuery Ajax object
2113 * @param {jqXHR} xhr jQuery Ajax object
2114 */
2114 */
2115 Notebook.prototype.load_notebook_success = function (data, status, xhr) {
2115 Notebook.prototype.load_notebook_success = function (data, status, xhr) {
2116 this.fromJSON(data);
2116 this.fromJSON(data);
2117 if (this.ncells() === 0) {
2117 if (this.ncells() === 0) {
2118 this.insert_cell_below('code');
2118 this.insert_cell_below('code');
2119 this.edit_mode(0);
2119 this.edit_mode(0);
2120 } else {
2120 } else {
2121 this.select(0);
2121 this.select(0);
2122 this.handle_command_mode(this.get_cell(0));
2122 this.handle_command_mode(this.get_cell(0));
2123 }
2123 }
2124 this.set_dirty(false);
2124 this.set_dirty(false);
2125 this.scroll_to_top();
2125 this.scroll_to_top();
2126 if (data.orig_nbformat !== undefined && data.nbformat !== data.orig_nbformat) {
2126 if (data.orig_nbformat !== undefined && data.nbformat !== data.orig_nbformat) {
2127 var msg = "This notebook has been converted from an older " +
2127 var msg = "This notebook has been converted from an older " +
2128 "notebook format (v"+data.orig_nbformat+") to the current notebook " +
2128 "notebook format (v"+data.orig_nbformat+") to the current notebook " +
2129 "format (v"+data.nbformat+"). The next time you save this notebook, the " +
2129 "format (v"+data.nbformat+"). The next time you save this notebook, the " +
2130 "newer notebook format will be used and older versions of IPython " +
2130 "newer notebook format will be used and older versions of IPython " +
2131 "may not be able to read it. To keep the older version, close the " +
2131 "may not be able to read it. To keep the older version, close the " +
2132 "notebook without saving it.";
2132 "notebook without saving it.";
2133 Dialog.modal({
2133 Dialog.modal({
2134 title : "Notebook converted",
2134 title : "Notebook converted",
2135 body : msg,
2135 body : msg,
2136 buttons : {
2136 buttons : {
2137 OK : {
2137 OK : {
2138 class : "btn-primary"
2138 class : "btn-primary"
2139 }
2139 }
2140 }
2140 }
2141 });
2141 });
2142 } else if (data.orig_nbformat_minor !== undefined && data.nbformat_minor !== data.orig_nbformat_minor) {
2142 } else if (data.orig_nbformat_minor !== undefined && data.nbformat_minor !== data.orig_nbformat_minor) {
2143 var that = this;
2143 var that = this;
2144 var orig_vs = 'v' + data.nbformat + '.' + data.orig_nbformat_minor;
2144 var orig_vs = 'v' + data.nbformat + '.' + data.orig_nbformat_minor;
2145 var this_vs = 'v' + data.nbformat + '.' + this.nbformat_minor;
2145 var this_vs = 'v' + data.nbformat + '.' + this.nbformat_minor;
2146 var msg = "This notebook is version " + orig_vs + ", but we only fully support up to " +
2146 var msg = "This notebook is version " + orig_vs + ", but we only fully support up to " +
2147 this_vs + ". You can still work with this notebook, but some features " +
2147 this_vs + ". You can still work with this notebook, but some features " +
2148 "introduced in later notebook versions may not be available.";
2148 "introduced in later notebook versions may not be available.";
2149
2149
2150 Dialog.modal({
2150 Dialog.modal({
2151 title : "Newer Notebook",
2151 title : "Newer Notebook",
2152 body : msg,
2152 body : msg,
2153 buttons : {
2153 buttons : {
2154 OK : {
2154 OK : {
2155 class : "btn-danger"
2155 class : "btn-danger"
2156 }
2156 }
2157 }
2157 }
2158 });
2158 });
2159
2159
2160 }
2160 }
2161
2161
2162 // Create the session after the notebook is completely loaded to prevent
2162 // Create the session after the notebook is completely loaded to prevent
2163 // code execution upon loading, which is a security risk.
2163 // code execution upon loading, which is a security risk.
2164 if (this.session === null) {
2164 if (this.session === null) {
2165 this.start_session();
2165 this.start_session();
2166 }
2166 }
2167 // load our checkpoint list
2167 // load our checkpoint list
2168 this.list_checkpoints();
2168 this.list_checkpoints();
2169
2169
2170 // load toolbar state
2170 // load toolbar state
2171 if (this.metadata.celltoolbar) {
2171 if (this.metadata.celltoolbar) {
2172 CellToolbar.global_show();
2172 CellToolbar.global_show();
2173 CellToolbar.activate_preset(this.metadata.celltoolbar);
2173 CellToolbar.activate_preset(this.metadata.celltoolbar);
2174 } else {
2174 } else {
2175 CellToolbar.global_hide();
2175 CellToolbar.global_hide();
2176 }
2176 }
2177
2177
2178 // now that we're fully loaded, it is safe to restore save functionality
2178 // now that we're fully loaded, it is safe to restore save functionality
2179 delete(this.save_notebook);
2179 delete(this.save_notebook);
2180 this.events.trigger('notebook_loaded.Notebook');
2180 this.events.trigger('notebook_loaded.Notebook');
2181 };
2181 };
2182
2182
2183 /**
2183 /**
2184 * Failure callback for loading a notebook from the server.
2184 * Failure callback for loading a notebook from the server.
2185 *
2185 *
2186 * @method load_notebook_error
2186 * @method load_notebook_error
2187 * @param {jqXHR} xhr jQuery Ajax object
2187 * @param {jqXHR} xhr jQuery Ajax object
2188 * @param {String} status Description of response status
2188 * @param {String} status Description of response status
2189 * @param {String} error HTTP error message
2189 * @param {String} error HTTP error message
2190 */
2190 */
2191 Notebook.prototype.load_notebook_error = function (xhr, status, error) {
2191 Notebook.prototype.load_notebook_error = function (xhr, status, error) {
2192 this.events.trigger('notebook_load_failed.Notebook', [xhr, status, error]);
2192 this.events.trigger('notebook_load_failed.Notebook', [xhr, status, error]);
2193 var msg;
2193 var msg;
2194 if (xhr.status === 400) {
2194 if (xhr.status === 400) {
2195 msg = error;
2195 msg = error;
2196 } else if (xhr.status === 500) {
2196 } else if (xhr.status === 500) {
2197 msg = "An unknown error occurred while loading this notebook. " +
2197 msg = "An unknown error occurred while loading this notebook. " +
2198 "This version can load notebook formats " +
2198 "This version can load notebook formats " +
2199 "v" + this.nbformat + " or earlier.";
2199 "v" + this.nbformat + " or earlier.";
2200 }
2200 }
2201 Dialog.modal({
2201 Dialog.modal({
2202 title: "Error loading notebook",
2202 title: "Error loading notebook",
2203 body : msg,
2203 body : msg,
2204 buttons : {
2204 buttons : {
2205 "OK": {}
2205 "OK": {}
2206 }
2206 }
2207 });
2207 });
2208 };
2208 };
2209
2209
2210 /********************* checkpoint-related *********************/
2210 /********************* checkpoint-related *********************/
2211
2211
2212 /**
2212 /**
2213 * Save the notebook then immediately create a checkpoint.
2213 * Save the notebook then immediately create a checkpoint.
2214 *
2214 *
2215 * @method save_checkpoint
2215 * @method save_checkpoint
2216 */
2216 */
2217 Notebook.prototype.save_checkpoint = function () {
2217 Notebook.prototype.save_checkpoint = function () {
2218 this._checkpoint_after_save = true;
2218 this._checkpoint_after_save = true;
2219 this.save_notebook();
2219 this.save_notebook();
2220 };
2220 };
2221
2221
2222 /**
2222 /**
2223 * Add a checkpoint for this notebook.
2223 * Add a checkpoint for this notebook.
2224 * for use as a callback from checkpoint creation.
2224 * for use as a callback from checkpoint creation.
2225 *
2225 *
2226 * @method add_checkpoint
2226 * @method add_checkpoint
2227 */
2227 */
2228 Notebook.prototype.add_checkpoint = function (checkpoint) {
2228 Notebook.prototype.add_checkpoint = function (checkpoint) {
2229 var found = false;
2229 var found = false;
2230 for (var i = 0; i < this.checkpoints.length; i++) {
2230 for (var i = 0; i < this.checkpoints.length; i++) {
2231 var existing = this.checkpoints[i];
2231 var existing = this.checkpoints[i];
2232 if (existing.id == checkpoint.id) {
2232 if (existing.id == checkpoint.id) {
2233 found = true;
2233 found = true;
2234 this.checkpoints[i] = checkpoint;
2234 this.checkpoints[i] = checkpoint;
2235 break;
2235 break;
2236 }
2236 }
2237 }
2237 }
2238 if (!found) {
2238 if (!found) {
2239 this.checkpoints.push(checkpoint);
2239 this.checkpoints.push(checkpoint);
2240 }
2240 }
2241 this.last_checkpoint = this.checkpoints[this.checkpoints.length - 1];
2241 this.last_checkpoint = this.checkpoints[this.checkpoints.length - 1];
2242 };
2242 };
2243
2243
2244 /**
2244 /**
2245 * List checkpoints for this notebook.
2245 * List checkpoints for this notebook.
2246 *
2246 *
2247 * @method list_checkpoints
2247 * @method list_checkpoints
2248 */
2248 */
2249 Notebook.prototype.list_checkpoints = function () {
2249 Notebook.prototype.list_checkpoints = function () {
2250 var url = utils.url_join_encode(
2250 var url = utils.url_join_encode(
2251 this.base_url,
2251 this.base_url,
2252 'api/notebooks',
2252 'api/notebooks',
2253 this.notebook_path,
2253 this.notebook_path,
2254 this.notebook_name,
2254 this.notebook_name,
2255 'checkpoints'
2255 'checkpoints'
2256 );
2256 );
2257 $.get(url).done(
2257 $.get(url).done(
2258 $.proxy(this.list_checkpoints_success, this)
2258 $.proxy(this.list_checkpoints_success, this)
2259 ).fail(
2259 ).fail(
2260 $.proxy(this.list_checkpoints_error, this)
2260 $.proxy(this.list_checkpoints_error, this)
2261 );
2261 );
2262 };
2262 };
2263
2263
2264 /**
2264 /**
2265 * Success callback for listing checkpoints.
2265 * Success callback for listing checkpoints.
2266 *
2266 *
2267 * @method list_checkpoint_success
2267 * @method list_checkpoint_success
2268 * @param {Object} data JSON representation of a checkpoint
2268 * @param {Object} data JSON representation of a checkpoint
2269 * @param {String} status Description of response status
2269 * @param {String} status Description of response status
2270 * @param {jqXHR} xhr jQuery Ajax object
2270 * @param {jqXHR} xhr jQuery Ajax object
2271 */
2271 */
2272 Notebook.prototype.list_checkpoints_success = function (data, status, xhr) {
2272 Notebook.prototype.list_checkpoints_success = function (data, status, xhr) {
2273 data = $.parseJSON(data);
2273 data = $.parseJSON(data);
2274 this.checkpoints = data;
2274 this.checkpoints = data;
2275 if (data.length) {
2275 if (data.length) {
2276 this.last_checkpoint = data[data.length - 1];
2276 this.last_checkpoint = data[data.length - 1];
2277 } else {
2277 } else {
2278 this.last_checkpoint = null;
2278 this.last_checkpoint = null;
2279 }
2279 }
2280 this.events.trigger('checkpoints_listed.Notebook', [data]);
2280 this.events.trigger('checkpoints_listed.Notebook', [data]);
2281 };
2281 };
2282
2282
2283 /**
2283 /**
2284 * Failure callback for listing a checkpoint.
2284 * Failure callback for listing a checkpoint.
2285 *
2285 *
2286 * @method list_checkpoint_error
2286 * @method list_checkpoint_error
2287 * @param {jqXHR} xhr jQuery Ajax object
2287 * @param {jqXHR} xhr jQuery Ajax object
2288 * @param {String} status Description of response status
2288 * @param {String} status Description of response status
2289 * @param {String} error_msg HTTP error message
2289 * @param {String} error_msg HTTP error message
2290 */
2290 */
2291 Notebook.prototype.list_checkpoints_error = function (xhr, status, error_msg) {
2291 Notebook.prototype.list_checkpoints_error = function (xhr, status, error_msg) {
2292 this.events.trigger('list_checkpoints_failed.Notebook');
2292 this.events.trigger('list_checkpoints_failed.Notebook');
2293 };
2293 };
2294
2294
2295 /**
2295 /**
2296 * Create a checkpoint of this notebook on the server from the most recent save.
2296 * Create a checkpoint of this notebook on the server from the most recent save.
2297 *
2297 *
2298 * @method create_checkpoint
2298 * @method create_checkpoint
2299 */
2299 */
2300 Notebook.prototype.create_checkpoint = function () {
2300 Notebook.prototype.create_checkpoint = function () {
2301 var url = utils.url_join_encode(
2301 var url = utils.url_join_encode(
2302 this.base_url,
2302 this.base_url,
2303 'api/notebooks',
2303 'api/notebooks',
2304 this.notebook_path,
2304 this.notebook_path,
2305 this.notebook_name,
2305 this.notebook_name,
2306 'checkpoints'
2306 'checkpoints'
2307 );
2307 );
2308 $.post(url).done(
2308 $.post(url).done(
2309 $.proxy(this.create_checkpoint_success, this)
2309 $.proxy(this.create_checkpoint_success, this)
2310 ).fail(
2310 ).fail(
2311 $.proxy(this.create_checkpoint_error, this)
2311 $.proxy(this.create_checkpoint_error, this)
2312 );
2312 );
2313 };
2313 };
2314
2314
2315 /**
2315 /**
2316 * Success callback for creating a checkpoint.
2316 * Success callback for creating a checkpoint.
2317 *
2317 *
2318 * @method create_checkpoint_success
2318 * @method create_checkpoint_success
2319 * @param {Object} data JSON representation of a checkpoint
2319 * @param {Object} data JSON representation of a checkpoint
2320 * @param {String} status Description of response status
2320 * @param {String} status Description of response status
2321 * @param {jqXHR} xhr jQuery Ajax object
2321 * @param {jqXHR} xhr jQuery Ajax object
2322 */
2322 */
2323 Notebook.prototype.create_checkpoint_success = function (data, status, xhr) {
2323 Notebook.prototype.create_checkpoint_success = function (data, status, xhr) {
2324 data = $.parseJSON(data);
2324 data = $.parseJSON(data);
2325 this.add_checkpoint(data);
2325 this.add_checkpoint(data);
2326 this.events.trigger('checkpoint_created.Notebook', data);
2326 this.events.trigger('checkpoint_created.Notebook', data);
2327 };
2327 };
2328
2328
2329 /**
2329 /**
2330 * Failure callback for creating a checkpoint.
2330 * Failure callback for creating a checkpoint.
2331 *
2331 *
2332 * @method create_checkpoint_error
2332 * @method create_checkpoint_error
2333 * @param {jqXHR} xhr jQuery Ajax object
2333 * @param {jqXHR} xhr jQuery Ajax object
2334 * @param {String} status Description of response status
2334 * @param {String} status Description of response status
2335 * @param {String} error_msg HTTP error message
2335 * @param {String} error_msg HTTP error message
2336 */
2336 */
2337 Notebook.prototype.create_checkpoint_error = function (xhr, status, error_msg) {
2337 Notebook.prototype.create_checkpoint_error = function (xhr, status, error_msg) {
2338 this.events.trigger('checkpoint_failed.Notebook');
2338 this.events.trigger('checkpoint_failed.Notebook');
2339 };
2339 };
2340
2340
2341 Notebook.prototype.restore_checkpoint_dialog = function (checkpoint) {
2341 Notebook.prototype.restore_checkpoint_dialog = function (checkpoint) {
2342 var that = this;
2342 var that = this;
2343 checkpoint = checkpoint || this.last_checkpoint;
2343 checkpoint = checkpoint || this.last_checkpoint;
2344 if ( ! checkpoint ) {
2344 if ( ! checkpoint ) {
2345 console.log("restore dialog, but no checkpoint to restore to!");
2345 console.log("restore dialog, but no checkpoint to restore to!");
2346 return;
2346 return;
2347 }
2347 }
2348 var body = $('<div/>').append(
2348 var body = $('<div/>').append(
2349 $('<p/>').addClass("p-space").text(
2349 $('<p/>').addClass("p-space").text(
2350 "Are you sure you want to revert the notebook to " +
2350 "Are you sure you want to revert the notebook to " +
2351 "the latest checkpoint?"
2351 "the latest checkpoint?"
2352 ).append(
2352 ).append(
2353 $("<strong/>").text(
2353 $("<strong/>").text(
2354 " This cannot be undone."
2354 " This cannot be undone."
2355 )
2355 )
2356 )
2356 )
2357 ).append(
2357 ).append(
2358 $('<p/>').addClass("p-space").text("The checkpoint was last updated at:")
2358 $('<p/>').addClass("p-space").text("The checkpoint was last updated at:")
2359 ).append(
2359 ).append(
2360 $('<p/>').addClass("p-space").text(
2360 $('<p/>').addClass("p-space").text(
2361 Date(checkpoint.last_modified)
2361 Date(checkpoint.last_modified)
2362 ).css("text-align", "center")
2362 ).css("text-align", "center")
2363 );
2363 );
2364
2364
2365 Dialog.modal({
2365 Dialog.modal({
2366 title : "Revert notebook to checkpoint",
2366 title : "Revert notebook to checkpoint",
2367 body : body,
2367 body : body,
2368 buttons : {
2368 buttons : {
2369 Revert : {
2369 Revert : {
2370 class : "btn-danger",
2370 class : "btn-danger",
2371 click : function () {
2371 click : function () {
2372 that.restore_checkpoint(checkpoint.id);
2372 that.restore_checkpoint(checkpoint.id);
2373 }
2373 }
2374 },
2374 },
2375 Cancel : {}
2375 Cancel : {}
2376 }
2376 }
2377 });
2377 });
2378 };
2378 };
2379
2379
2380 /**
2380 /**
2381 * Restore the notebook to a checkpoint state.
2381 * Restore the notebook to a checkpoint state.
2382 *
2382 *
2383 * @method restore_checkpoint
2383 * @method restore_checkpoint
2384 * @param {String} checkpoint ID
2384 * @param {String} checkpoint ID
2385 */
2385 */
2386 Notebook.prototype.restore_checkpoint = function (checkpoint) {
2386 Notebook.prototype.restore_checkpoint = function (checkpoint) {
2387 this.events.trigger('notebook_restoring.Notebook', checkpoint);
2387 this.events.trigger('notebook_restoring.Notebook', checkpoint);
2388 var url = utils.url_join_encode(
2388 var url = utils.url_join_encode(
2389 this.base_url,
2389 this.base_url,
2390 'api/notebooks',
2390 'api/notebooks',
2391 this.notebook_path,
2391 this.notebook_path,
2392 this.notebook_name,
2392 this.notebook_name,
2393 'checkpoints',
2393 'checkpoints',
2394 checkpoint
2394 checkpoint
2395 );
2395 );
2396 $.post(url).done(
2396 $.post(url).done(
2397 $.proxy(this.restore_checkpoint_success, this)
2397 $.proxy(this.restore_checkpoint_success, this)
2398 ).fail(
2398 ).fail(
2399 $.proxy(this.restore_checkpoint_error, this)
2399 $.proxy(this.restore_checkpoint_error, this)
2400 );
2400 );
2401 };
2401 };
2402
2402
2403 /**
2403 /**
2404 * Success callback for restoring a notebook to a checkpoint.
2404 * Success callback for restoring a notebook to a checkpoint.
2405 *
2405 *
2406 * @method restore_checkpoint_success
2406 * @method restore_checkpoint_success
2407 * @param {Object} data (ignored, should be empty)
2407 * @param {Object} data (ignored, should be empty)
2408 * @param {String} status Description of response status
2408 * @param {String} status Description of response status
2409 * @param {jqXHR} xhr jQuery Ajax object
2409 * @param {jqXHR} xhr jQuery Ajax object
2410 */
2410 */
2411 Notebook.prototype.restore_checkpoint_success = function (data, status, xhr) {
2411 Notebook.prototype.restore_checkpoint_success = function (data, status, xhr) {
2412 this.events.trigger('checkpoint_restored.Notebook');
2412 this.events.trigger('checkpoint_restored.Notebook');
2413 this.load_notebook(this.notebook_name, this.notebook_path);
2413 this.load_notebook(this.notebook_name, this.notebook_path);
2414 };
2414 };
2415
2415
2416 /**
2416 /**
2417 * Failure callback for restoring a notebook to a checkpoint.
2417 * Failure callback for restoring a notebook to a checkpoint.
2418 *
2418 *
2419 * @method restore_checkpoint_error
2419 * @method restore_checkpoint_error
2420 * @param {jqXHR} xhr jQuery Ajax object
2420 * @param {jqXHR} xhr jQuery Ajax object
2421 * @param {String} status Description of response status
2421 * @param {String} status Description of response status
2422 * @param {String} error_msg HTTP error message
2422 * @param {String} error_msg HTTP error message
2423 */
2423 */
2424 Notebook.prototype.restore_checkpoint_error = function (xhr, status, error_msg) {
2424 Notebook.prototype.restore_checkpoint_error = function (xhr, status, error_msg) {
2425 this.events.trigger('checkpoint_restore_failed.Notebook');
2425 this.events.trigger('checkpoint_restore_failed.Notebook');
2426 };
2426 };
2427
2427
2428 /**
2428 /**
2429 * Delete a notebook checkpoint.
2429 * Delete a notebook checkpoint.
2430 *
2430 *
2431 * @method delete_checkpoint
2431 * @method delete_checkpoint
2432 * @param {String} checkpoint ID
2432 * @param {String} checkpoint ID
2433 */
2433 */
2434 Notebook.prototype.delete_checkpoint = function (checkpoint) {
2434 Notebook.prototype.delete_checkpoint = function (checkpoint) {
2435 this.events.trigger('notebook_restoring.Notebook', checkpoint);
2435 this.events.trigger('notebook_restoring.Notebook', checkpoint);
2436 var url = utils.url_join_encode(
2436 var url = utils.url_join_encode(
2437 this.base_url,
2437 this.base_url,
2438 'api/notebooks',
2438 'api/notebooks',
2439 this.notebook_path,
2439 this.notebook_path,
2440 this.notebook_name,
2440 this.notebook_name,
2441 'checkpoints',
2441 'checkpoints',
2442 checkpoint
2442 checkpoint
2443 );
2443 );
2444 $.ajax(url, {
2444 $.ajax(url, {
2445 type: 'DELETE',
2445 type: 'DELETE',
2446 success: $.proxy(this.delete_checkpoint_success, this),
2446 success: $.proxy(this.delete_checkpoint_success, this),
2447 error: $.proxy(this.delete_checkpoint_error, this)
2447 error: $.proxy(this.delete_checkpoint_error, this)
2448 });
2448 });
2449 };
2449 };
2450
2450
2451 /**
2451 /**
2452 * Success callback for deleting a notebook checkpoint
2452 * Success callback for deleting a notebook checkpoint
2453 *
2453 *
2454 * @method delete_checkpoint_success
2454 * @method delete_checkpoint_success
2455 * @param {Object} data (ignored, should be empty)
2455 * @param {Object} data (ignored, should be empty)
2456 * @param {String} status Description of response status
2456 * @param {String} status Description of response status
2457 * @param {jqXHR} xhr jQuery Ajax object
2457 * @param {jqXHR} xhr jQuery Ajax object
2458 */
2458 */
2459 Notebook.prototype.delete_checkpoint_success = function (data, status, xhr) {
2459 Notebook.prototype.delete_checkpoint_success = function (data, status, xhr) {
2460 this.events.trigger('checkpoint_deleted.Notebook', data);
2460 this.events.trigger('checkpoint_deleted.Notebook', data);
2461 this.load_notebook(this.notebook_name, this.notebook_path);
2461 this.load_notebook(this.notebook_name, this.notebook_path);
2462 };
2462 };
2463
2463
2464 /**
2464 /**
2465 * Failure callback for deleting a notebook checkpoint.
2465 * Failure callback for deleting a notebook checkpoint.
2466 *
2466 *
2467 * @method delete_checkpoint_error
2467 * @method delete_checkpoint_error
2468 * @param {jqXHR} xhr jQuery Ajax object
2468 * @param {jqXHR} xhr jQuery Ajax object
2469 * @param {String} status Description of response status
2469 * @param {String} status Description of response status
2470 * @param {String} error_msg HTTP error message
2470 * @param {String} error_msg HTTP error message
2471 */
2471 */
2472 Notebook.prototype.delete_checkpoint_error = function (xhr, status, error_msg) {
2472 Notebook.prototype.delete_checkpoint_error = function (xhr, status, error_msg) {
2473 this.events.trigger('checkpoint_delete_failed.Notebook');
2473 this.events.trigger('checkpoint_delete_failed.Notebook');
2474 };
2474 };
2475
2475
2476
2476
2477 // For backwards compatability.
2477 // For backwards compatability.
2478 IPython.Notebook = Notebook;
2478 IPython.Notebook = Notebook;
2479
2479
2480 return Notebook;
2480 return {'Notebook': Notebook};
2481 });
2481 });
@@ -1,233 +1,233 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 define([
4 define([
5 'base/js/namespace',
5 'base/js/namespace',
6 'jquery',
6 'jquery',
7 'base/js/utils',
7 'base/js/utils',
8 'base/js/dialog',
8 'base/js/dialog',
9 'notebook/js/notificationwidget',
9 'notebook/js/notificationwidget',
10 ], function(IPython, $, utils, Dialog, NotificationWidget) {
10 ], function(IPython, $, utils, Dialog, NotificationWidget) {
11 "use strict";
11 "use strict";
12
12
13 var NotificationArea = function (selector, events, save_widget, notebook) {
13 var NotificationArea = function (selector, events, save_widget, notebook) {
14 this.selector = selector;
14 this.selector = selector;
15 this.events = events;
15 this.events = events;
16 this.save_widget = save_widget;
16 this.save_widget = save_widget;
17 this.notebook = notebook;
17 this.notebook = notebook;
18 if (this.selector !== undefined) {
18 if (this.selector !== undefined) {
19 this.element = $(selector);
19 this.element = $(selector);
20 }
20 }
21 this.widget_dict = {};
21 this.widget_dict = {};
22 };
22 };
23
23
24 NotificationArea.prototype.temp_message = function (msg, timeout, css_class) {
24 NotificationArea.prototype.temp_message = function (msg, timeout, css_class) {
25 var uuid = utils.uuid();
25 var uuid = utils.uuid();
26 if( css_class == 'danger') {css_class = 'ui-state-error';}
26 if( css_class == 'danger') {css_class = 'ui-state-error';}
27 if( css_class == 'warning') {css_class = 'ui-state-highlight';}
27 if( css_class == 'warning') {css_class = 'ui-state-highlight';}
28 var tdiv = $('<div>')
28 var tdiv = $('<div>')
29 .attr('id',uuid)
29 .attr('id',uuid)
30 .addClass('notification_widget ui-widget ui-widget-content ui-corner-all')
30 .addClass('notification_widget ui-widget ui-widget-content ui-corner-all')
31 .addClass('border-box-sizing')
31 .addClass('border-box-sizing')
32 .addClass(css_class)
32 .addClass(css_class)
33 .hide()
33 .hide()
34 .text(msg);
34 .text(msg);
35
35
36 $(this.selector).append(tdiv);
36 $(this.selector).append(tdiv);
37 var tmout = Math.max(1500,(timeout||1500));
37 var tmout = Math.max(1500,(timeout||1500));
38 tdiv.fadeIn(100);
38 tdiv.fadeIn(100);
39
39
40 setTimeout(function () {
40 setTimeout(function () {
41 tdiv.fadeOut(100, function () {tdiv.remove();});
41 tdiv.fadeOut(100, function () {tdiv.remove();});
42 }, tmout);
42 }, tmout);
43 };
43 };
44
44
45 NotificationArea.prototype.widget = function(name) {
45 NotificationArea.prototype.widget = function(name) {
46 if(this.widget_dict[name] === undefined) {
46 if(this.widget_dict[name] === undefined) {
47 return this.new_notification_widget(name);
47 return this.new_notification_widget(name);
48 }
48 }
49 return this.get_widget(name);
49 return this.get_widget(name);
50 };
50 };
51
51
52 NotificationArea.prototype.get_widget = function(name) {
52 NotificationArea.prototype.get_widget = function(name) {
53 if(this.widget_dict[name] === undefined) {
53 if(this.widget_dict[name] === undefined) {
54 throw('no widgets with this name');
54 throw('no widgets with this name');
55 }
55 }
56 return this.widget_dict[name];
56 return this.widget_dict[name];
57 };
57 };
58
58
59 NotificationArea.prototype.new_notification_widget = function(name) {
59 NotificationArea.prototype.new_notification_widget = function(name) {
60 if(this.widget_dict[name] !== undefined) {
60 if(this.widget_dict[name] !== undefined) {
61 throw('widget with that name already exists ! ');
61 throw('widget with that name already exists ! ');
62 }
62 }
63 var div = $('<div/>').attr('id','notification_'+name);
63 var div = $('<div/>').attr('id','notification_'+name);
64 $(this.selector).append(div);
64 $(this.selector).append(div);
65 this.widget_dict[name] = new NotificationWidget('#notification_'+name);
65 this.widget_dict[name] = new NotificationWidget('#notification_'+name);
66 return this.widget_dict[name];
66 return this.widget_dict[name];
67 };
67 };
68
68
69 NotificationArea.prototype.init_notification_widgets = function() {
69 NotificationArea.prototype.init_notification_widgets = function() {
70 var that = this;
70 var that = this;
71 var knw = this.new_notification_widget('kernel');
71 var knw = this.new_notification_widget('kernel');
72 var $kernel_ind_icon = $("#kernel_indicator_icon");
72 var $kernel_ind_icon = $("#kernel_indicator_icon");
73 var $modal_ind_icon = $("#modal_indicator_icon");
73 var $modal_ind_icon = $("#modal_indicator_icon");
74
74
75 // Command/Edit mode
75 // Command/Edit mode
76 this.events.on('edit_mode.Notebook',function () {
76 this.events.on('edit_mode.Notebook',function () {
77 that.save_widget.update_document_title();
77 that.save_widget.update_document_title();
78 $modal_ind_icon.attr('class','edit_mode_icon').attr('title','Edit Mode');
78 $modal_ind_icon.attr('class','edit_mode_icon').attr('title','Edit Mode');
79 });
79 });
80
80
81 this.events.on('command_mode.Notebook',function () {
81 this.events.on('command_mode.Notebook',function () {
82 that.save_widget.update_document_title();
82 that.save_widget.update_document_title();
83 $modal_ind_icon.attr('class','command_mode_icon').attr('title','Command Mode');
83 $modal_ind_icon.attr('class','command_mode_icon').attr('title','Command Mode');
84 });
84 });
85
85
86 // Implicitly start off in Command mode, switching to Edit mode will trigger event
86 // Implicitly start off in Command mode, switching to Edit mode will trigger event
87 $modal_ind_icon.attr('class','command-mode_icon').attr('title','Command Mode');
87 $modal_ind_icon.attr('class','command-mode_icon').attr('title','Command Mode');
88
88
89 // Kernel events
89 // Kernel events
90 this.events.on('status_idle.Kernel',function () {
90 this.events.on('status_idle.Kernel',function () {
91 that.save_widget.update_document_title();
91 that.save_widget.update_document_title();
92 $kernel_ind_icon.attr('class','kernel_idle_icon').attr('title','Kernel Idle');
92 $kernel_ind_icon.attr('class','kernel_idle_icon').attr('title','Kernel Idle');
93 });
93 });
94
94
95 this.events.on('status_busy.Kernel',function () {
95 this.events.on('status_busy.Kernel',function () {
96 window.document.title='(Busy) '+window.document.title;
96 window.document.title='(Busy) '+window.document.title;
97 $kernel_ind_icon.attr('class','kernel_busy_icon').attr('title','Kernel Busy');
97 $kernel_ind_icon.attr('class','kernel_busy_icon').attr('title','Kernel Busy');
98 });
98 });
99
99
100 this.events.on('status_restarting.Kernel',function () {
100 this.events.on('status_restarting.Kernel',function () {
101 that.save_widget.update_document_title();
101 that.save_widget.update_document_title();
102 knw.set_message("Restarting kernel", 2000);
102 knw.set_message("Restarting kernel", 2000);
103 });
103 });
104
104
105 this.events.on('status_interrupting.Kernel',function () {
105 this.events.on('status_interrupting.Kernel',function () {
106 knw.set_message("Interrupting kernel", 2000);
106 knw.set_message("Interrupting kernel", 2000);
107 });
107 });
108
108
109 // Start the kernel indicator in the busy state, and send a kernel_info request.
109 // Start the kernel indicator in the busy state, and send a kernel_info request.
110 // When the kernel_info reply arrives, the kernel is idle.
110 // When the kernel_info reply arrives, the kernel is idle.
111 $kernel_ind_icon.attr('class','kernel_busy_icon').attr('title','Kernel Busy');
111 $kernel_ind_icon.attr('class','kernel_busy_icon').attr('title','Kernel Busy');
112
112
113 this.events.on('status_started.Kernel', function (evt, data) {
113 this.events.on('status_started.Kernel', function (evt, data) {
114 data.kernel.kernel_info(function () {
114 data.kernel.kernel_info(function () {
115 that.events.trigger('status_idle.Kernel');
115 that.events.trigger('status_idle.Kernel');
116 });
116 });
117 });
117 });
118
118
119 this.events.on('status_dead.Kernel',function () {
119 this.events.on('status_dead.Kernel',function () {
120 var msg = 'The kernel has died, and the automatic restart has failed.' +
120 var msg = 'The kernel has died, and the automatic restart has failed.' +
121 ' It is possible the kernel cannot be restarted.' +
121 ' It is possible the kernel cannot be restarted.' +
122 ' If you are not able to restart the kernel, you will still be able to save' +
122 ' If you are not able to restart the kernel, you will still be able to save' +
123 ' the notebook, but running code will no longer work until the notebook' +
123 ' the notebook, but running code will no longer work until the notebook' +
124 ' is reopened.';
124 ' is reopened.';
125
125
126 Dialog.modal({
126 Dialog.modal({
127 title: "Dead kernel",
127 title: "Dead kernel",
128 body : msg,
128 body : msg,
129 buttons : {
129 buttons : {
130 "Manual Restart": {
130 "Manual Restart": {
131 class: "btn-danger",
131 class: "btn-danger",
132 click: function () {
132 click: function () {
133 that.events.trigger('status_restarting.Kernel');
133 that.events.trigger('status_restarting.Kernel');
134 that.notebook.start_kernel();
134 that.notebook.start_kernel();
135 }
135 }
136 },
136 },
137 "Don't restart": {}
137 "Don't restart": {}
138 }
138 }
139 });
139 });
140 });
140 });
141
141
142 this.events.on('websocket_closed.Kernel', function (event, data) {
142 this.events.on('websocket_closed.Kernel', function (event, data) {
143 var kernel = data.kernel;
143 var kernel = data.kernel;
144 var ws_url = data.ws_url;
144 var ws_url = data.ws_url;
145 var early = data.early;
145 var early = data.early;
146 var msg;
146 var msg;
147 if (!early) {
147 if (!early) {
148 knw.set_message('Reconnecting WebSockets', 1000);
148 knw.set_message('Reconnecting WebSockets', 1000);
149 setTimeout(function () {
149 setTimeout(function () {
150 kernel.start_channels();
150 kernel.start_channels();
151 }, 5000);
151 }, 5000);
152 return;
152 return;
153 }
153 }
154 console.log('WebSocket connection failed: ', ws_url);
154 console.log('WebSocket connection failed: ', ws_url);
155 msg = "A WebSocket connection could not be established." +
155 msg = "A WebSocket connection could not be established." +
156 " You will NOT be able to run code. Check your" +
156 " You will NOT be able to run code. Check your" +
157 " network connection or notebook server configuration.";
157 " network connection or notebook server configuration.";
158 Dialog.modal({
158 Dialog.modal({
159 title: "WebSocket connection failed",
159 title: "WebSocket connection failed",
160 body: msg,
160 body: msg,
161 buttons : {
161 buttons : {
162 "OK": {},
162 "OK": {},
163 "Reconnect": {
163 "Reconnect": {
164 click: function () {
164 click: function () {
165 knw.set_message('Reconnecting WebSockets', 1000);
165 knw.set_message('Reconnecting WebSockets', 1000);
166 setTimeout(function () {
166 setTimeout(function () {
167 kernel.start_channels();
167 kernel.start_channels();
168 }, 5000);
168 }, 5000);
169 }
169 }
170 }
170 }
171 }
171 }
172 });
172 });
173 });
173 });
174
174
175
175
176 var nnw = this.new_notification_widget('notebook');
176 var nnw = this.new_notification_widget('notebook');
177
177
178 // Notebook events
178 // Notebook events
179 this.events.on('notebook_loading.Notebook', function () {
179 this.events.on('notebook_loading.Notebook', function () {
180 nnw.set_message("Loading notebook",500);
180 nnw.set_message("Loading notebook",500);
181 });
181 });
182 this.events.on('notebook_loaded.Notebook', function () {
182 this.events.on('notebook_loaded.Notebook', function () {
183 nnw.set_message("Notebook loaded",500);
183 nnw.set_message("Notebook loaded",500);
184 });
184 });
185 this.events.on('notebook_saving.Notebook', function () {
185 this.events.on('notebook_saving.Notebook', function () {
186 nnw.set_message("Saving notebook",500);
186 nnw.set_message("Saving notebook",500);
187 });
187 });
188 this.events.on('notebook_saved.Notebook', function () {
188 this.events.on('notebook_saved.Notebook', function () {
189 nnw.set_message("Notebook saved",2000);
189 nnw.set_message("Notebook saved",2000);
190 });
190 });
191 this.events.on('notebook_save_failed.Notebook', function (evt, xhr, status, data) {
191 this.events.on('notebook_save_failed.Notebook', function (evt, xhr, status, data) {
192 nnw.set_message(data || "Notebook save failed");
192 nnw.set_message(data || "Notebook save failed");
193 });
193 });
194
194
195 // Checkpoint events
195 // Checkpoint events
196 this.events.on('checkpoint_created.Notebook', function (evt, data) {
196 this.events.on('checkpoint_created.Notebook', function (evt, data) {
197 var msg = "Checkpoint created";
197 var msg = "Checkpoint created";
198 if (data.last_modified) {
198 if (data.last_modified) {
199 var d = new Date(data.last_modified);
199 var d = new Date(data.last_modified);
200 msg = msg + ": " + d.format("HH:MM:ss");
200 msg = msg + ": " + d.format("HH:MM:ss");
201 }
201 }
202 nnw.set_message(msg, 2000);
202 nnw.set_message(msg, 2000);
203 });
203 });
204 this.events.on('checkpoint_failed.Notebook', function () {
204 this.events.on('checkpoint_failed.Notebook', function () {
205 nnw.set_message("Checkpoint failed");
205 nnw.set_message("Checkpoint failed");
206 });
206 });
207 this.events.on('checkpoint_deleted.Notebook', function () {
207 this.events.on('checkpoint_deleted.Notebook', function () {
208 nnw.set_message("Checkpoint deleted", 500);
208 nnw.set_message("Checkpoint deleted", 500);
209 });
209 });
210 this.events.on('checkpoint_delete_failed.Notebook', function () {
210 this.events.on('checkpoint_delete_failed.Notebook', function () {
211 nnw.set_message("Checkpoint delete failed");
211 nnw.set_message("Checkpoint delete failed");
212 });
212 });
213 this.events.on('checkpoint_restoring.Notebook', function () {
213 this.events.on('checkpoint_restoring.Notebook', function () {
214 nnw.set_message("Restoring to checkpoint...", 500);
214 nnw.set_message("Restoring to checkpoint...", 500);
215 });
215 });
216 this.events.on('checkpoint_restore_failed.Notebook', function () {
216 this.events.on('checkpoint_restore_failed.Notebook', function () {
217 nnw.set_message("Checkpoint restore failed");
217 nnw.set_message("Checkpoint restore failed");
218 });
218 });
219
219
220 // Autosave events
220 // Autosave events
221 this.events.on('autosave_disabled.Notebook', function () {
221 this.events.on('autosave_disabled.Notebook', function () {
222 nnw.set_message("Autosave disabled", 2000);
222 nnw.set_message("Autosave disabled", 2000);
223 });
223 });
224 this.events.on('autosave_enabled.Notebook', function (evt, interval) {
224 this.events.on('autosave_enabled.Notebook', function (evt, interval) {
225 nnw.set_message("Saving every " + interval / 1000 + "s", 1000);
225 nnw.set_message("Saving every " + interval / 1000 + "s", 1000);
226 });
226 });
227
227
228 };
228 };
229
229
230 IPython.NotificationArea = NotificationArea;
230 IPython.NotificationArea = NotificationArea;
231
231
232 return NotificationArea;
232 return {'NotificationArea': NotificationArea};
233 });
233 });
@@ -1,77 +1,77 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 define([
4 define([
5 'base/js/namespace',
5 'base/js/namespace',
6 'jquery',
6 'jquery',
7 ], function(IPython, $) {
7 ], function(IPython, $) {
8 "use strict";
8 "use strict";
9
9
10 var NotificationWidget = function (selector) {
10 var NotificationWidget = function (selector) {
11 this.selector = selector;
11 this.selector = selector;
12 this.timeout = null;
12 this.timeout = null;
13 this.busy = false;
13 this.busy = false;
14 if (this.selector !== undefined) {
14 if (this.selector !== undefined) {
15 this.element = $(selector);
15 this.element = $(selector);
16 this.style();
16 this.style();
17 }
17 }
18 this.element.button();
18 this.element.button();
19 this.element.hide();
19 this.element.hide();
20 var that = this;
20 var that = this;
21
21
22 this.inner = $('<span/>');
22 this.inner = $('<span/>');
23 this.element.append(this.inner);
23 this.element.append(this.inner);
24
24
25 };
25 };
26
26
27 NotificationWidget.prototype.style = function () {
27 NotificationWidget.prototype.style = function () {
28 this.element.addClass('notification_widget pull-right');
28 this.element.addClass('notification_widget pull-right');
29 this.element.addClass('border-box-sizing');
29 this.element.addClass('border-box-sizing');
30 };
30 };
31
31
32 // msg : message to display
32 // msg : message to display
33 // timeout : time in ms before diseapearing
33 // timeout : time in ms before diseapearing
34 //
34 //
35 // if timeout <= 0
35 // if timeout <= 0
36 // click_callback : function called if user click on notification
36 // click_callback : function called if user click on notification
37 // could return false to prevent the notification to be dismissed
37 // could return false to prevent the notification to be dismissed
38 NotificationWidget.prototype.set_message = function (msg, timeout, click_callback, opts) {
38 NotificationWidget.prototype.set_message = function (msg, timeout, click_callback, opts) {
39 opts = opts || {};
39 opts = opts || {};
40 var callback = click_callback || function() {return false;};
40 var callback = click_callback || function() {return false;};
41 var that = this;
41 var that = this;
42 this.inner.attr('class', opts.icon);
42 this.inner.attr('class', opts.icon);
43 this.inner.attr('title', opts.title);
43 this.inner.attr('title', opts.title);
44 this.inner.text(msg);
44 this.inner.text(msg);
45 this.element.fadeIn(100);
45 this.element.fadeIn(100);
46 if (this.timeout !== null) {
46 if (this.timeout !== null) {
47 clearTimeout(this.timeout);
47 clearTimeout(this.timeout);
48 this.timeout = null;
48 this.timeout = null;
49 }
49 }
50 if (timeout !== undefined && timeout >=0) {
50 if (timeout !== undefined && timeout >=0) {
51 this.timeout = setTimeout(function () {
51 this.timeout = setTimeout(function () {
52 that.element.fadeOut(100, function () {that.inner.text('');});
52 that.element.fadeOut(100, function () {that.inner.text('');});
53 that.timeout = null;
53 that.timeout = null;
54 }, timeout);
54 }, timeout);
55 } else {
55 } else {
56 this.element.click(function() {
56 this.element.click(function() {
57 if( callback() !== false ) {
57 if( callback() !== false ) {
58 that.element.fadeOut(100, function () {that.inner.text('');});
58 that.element.fadeOut(100, function () {that.inner.text('');});
59 that.element.unbind('click');
59 that.element.unbind('click');
60 }
60 }
61 if (that.timeout !== undefined) {
61 if (that.timeout !== undefined) {
62 that.timeout = undefined;
62 that.timeout = undefined;
63 clearTimeout(that.timeout);
63 clearTimeout(that.timeout);
64 }
64 }
65 });
65 });
66 }
66 }
67 };
67 };
68
68
69 NotificationWidget.prototype.get_message = function () {
69 NotificationWidget.prototype.get_message = function () {
70 return this.inner.html();
70 return this.inner.html();
71 };
71 };
72
72
73 // For backwards compatability.
73 // For backwards compatability.
74 IPython.NotificationWidget = NotificationWidget;
74 IPython.NotificationWidget = NotificationWidget;
75
75
76 return NotificationWidget;
76 return {'NotificationWidget': NotificationWidget};
77 });
77 });
@@ -1,992 +1,992 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 define([
4 define([
5 'base/js/namespace',
5 'base/js/namespace',
6 'jquery',
6 'jquery',
7 'base/js/utils',
7 'base/js/utils',
8 'base/js/security',
8 'base/js/security',
9 'base/js/keyboard',
9 'base/js/keyboard',
10 'notebook/js/mathjaxutils',
10 'notebook/js/mathjaxutils',
11 ], function(IPython, $, utils, security, keyboard, mathjaxutils) {
11 ], function(IPython, $, utils, security, keyboard, mathjaxutils) {
12 "use strict";
12 "use strict";
13
13
14 /**
14 /**
15 * @class OutputArea
15 * @class OutputArea
16 *
16 *
17 * @constructor
17 * @constructor
18 */
18 */
19
19
20 var OutputArea = function (selector, prompt_area, events, keyboard_manager) {
20 var OutputArea = function (selector, prompt_area, events, keyboard_manager) {
21 this.selector = selector;
21 this.selector = selector;
22 this.events = events;
22 this.events = events;
23 this.keyboard_manager = keyboard_manager;
23 this.keyboard_manager = keyboard_manager;
24 this.wrapper = $(selector);
24 this.wrapper = $(selector);
25 this.outputs = [];
25 this.outputs = [];
26 this.collapsed = false;
26 this.collapsed = false;
27 this.scrolled = false;
27 this.scrolled = false;
28 this.trusted = true;
28 this.trusted = true;
29 this.clear_queued = null;
29 this.clear_queued = null;
30 if (prompt_area === undefined) {
30 if (prompt_area === undefined) {
31 this.prompt_area = true;
31 this.prompt_area = true;
32 } else {
32 } else {
33 this.prompt_area = prompt_area;
33 this.prompt_area = prompt_area;
34 }
34 }
35 this.create_elements();
35 this.create_elements();
36 this.style();
36 this.style();
37 this.bind_events();
37 this.bind_events();
38 };
38 };
39
39
40
40
41 /**
41 /**
42 * Class prototypes
42 * Class prototypes
43 **/
43 **/
44
44
45 OutputArea.prototype.create_elements = function () {
45 OutputArea.prototype.create_elements = function () {
46 this.element = $("<div/>");
46 this.element = $("<div/>");
47 this.collapse_button = $("<div/>");
47 this.collapse_button = $("<div/>");
48 this.prompt_overlay = $("<div/>");
48 this.prompt_overlay = $("<div/>");
49 this.wrapper.append(this.prompt_overlay);
49 this.wrapper.append(this.prompt_overlay);
50 this.wrapper.append(this.element);
50 this.wrapper.append(this.element);
51 this.wrapper.append(this.collapse_button);
51 this.wrapper.append(this.collapse_button);
52 };
52 };
53
53
54
54
55 OutputArea.prototype.style = function () {
55 OutputArea.prototype.style = function () {
56 this.collapse_button.hide();
56 this.collapse_button.hide();
57 this.prompt_overlay.hide();
57 this.prompt_overlay.hide();
58
58
59 this.wrapper.addClass('output_wrapper');
59 this.wrapper.addClass('output_wrapper');
60 this.element.addClass('output');
60 this.element.addClass('output');
61
61
62 this.collapse_button.addClass("btn btn-default output_collapsed");
62 this.collapse_button.addClass("btn btn-default output_collapsed");
63 this.collapse_button.attr('title', 'click to expand output');
63 this.collapse_button.attr('title', 'click to expand output');
64 this.collapse_button.text('. . .');
64 this.collapse_button.text('. . .');
65
65
66 this.prompt_overlay.addClass('out_prompt_overlay prompt');
66 this.prompt_overlay.addClass('out_prompt_overlay prompt');
67 this.prompt_overlay.attr('title', 'click to expand output; double click to hide output');
67 this.prompt_overlay.attr('title', 'click to expand output; double click to hide output');
68
68
69 this.collapse();
69 this.collapse();
70 };
70 };
71
71
72 /**
72 /**
73 * Should the OutputArea scroll?
73 * Should the OutputArea scroll?
74 * Returns whether the height (in lines) exceeds a threshold.
74 * Returns whether the height (in lines) exceeds a threshold.
75 *
75 *
76 * @private
76 * @private
77 * @method _should_scroll
77 * @method _should_scroll
78 * @param [lines=100]{Integer}
78 * @param [lines=100]{Integer}
79 * @return {Bool}
79 * @return {Bool}
80 *
80 *
81 */
81 */
82 OutputArea.prototype._should_scroll = function (lines) {
82 OutputArea.prototype._should_scroll = function (lines) {
83 if (lines <=0 ){ return }
83 if (lines <=0 ){ return }
84 if (!lines) {
84 if (!lines) {
85 lines = 100;
85 lines = 100;
86 }
86 }
87 // line-height from http://stackoverflow.com/questions/1185151
87 // line-height from http://stackoverflow.com/questions/1185151
88 var fontSize = this.element.css('font-size');
88 var fontSize = this.element.css('font-size');
89 var lineHeight = Math.floor(parseInt(fontSize.replace('px','')) * 1.5);
89 var lineHeight = Math.floor(parseInt(fontSize.replace('px','')) * 1.5);
90
90
91 return (this.element.height() > lines * lineHeight);
91 return (this.element.height() > lines * lineHeight);
92 };
92 };
93
93
94
94
95 OutputArea.prototype.bind_events = function () {
95 OutputArea.prototype.bind_events = function () {
96 var that = this;
96 var that = this;
97 this.prompt_overlay.dblclick(function () { that.toggle_output(); });
97 this.prompt_overlay.dblclick(function () { that.toggle_output(); });
98 this.prompt_overlay.click(function () { that.toggle_scroll(); });
98 this.prompt_overlay.click(function () { that.toggle_scroll(); });
99
99
100 this.element.resize(function () {
100 this.element.resize(function () {
101 // FIXME: Firefox on Linux misbehaves, so automatic scrolling is disabled
101 // FIXME: Firefox on Linux misbehaves, so automatic scrolling is disabled
102 if ( utils.browser[0] === "Firefox" ) {
102 if ( utils.browser[0] === "Firefox" ) {
103 return;
103 return;
104 }
104 }
105 // maybe scroll output,
105 // maybe scroll output,
106 // if it's grown large enough and hasn't already been scrolled.
106 // if it's grown large enough and hasn't already been scrolled.
107 if ( !that.scrolled && that._should_scroll(OutputArea.auto_scroll_threshold)) {
107 if ( !that.scrolled && that._should_scroll(OutputArea.auto_scroll_threshold)) {
108 that.scroll_area();
108 that.scroll_area();
109 }
109 }
110 });
110 });
111 this.collapse_button.click(function () {
111 this.collapse_button.click(function () {
112 that.expand();
112 that.expand();
113 });
113 });
114 };
114 };
115
115
116
116
117 OutputArea.prototype.collapse = function () {
117 OutputArea.prototype.collapse = function () {
118 if (!this.collapsed) {
118 if (!this.collapsed) {
119 this.element.hide();
119 this.element.hide();
120 this.prompt_overlay.hide();
120 this.prompt_overlay.hide();
121 if (this.element.html()){
121 if (this.element.html()){
122 this.collapse_button.show();
122 this.collapse_button.show();
123 }
123 }
124 this.collapsed = true;
124 this.collapsed = true;
125 }
125 }
126 };
126 };
127
127
128
128
129 OutputArea.prototype.expand = function () {
129 OutputArea.prototype.expand = function () {
130 if (this.collapsed) {
130 if (this.collapsed) {
131 this.collapse_button.hide();
131 this.collapse_button.hide();
132 this.element.show();
132 this.element.show();
133 this.prompt_overlay.show();
133 this.prompt_overlay.show();
134 this.collapsed = false;
134 this.collapsed = false;
135 }
135 }
136 };
136 };
137
137
138
138
139 OutputArea.prototype.toggle_output = function () {
139 OutputArea.prototype.toggle_output = function () {
140 if (this.collapsed) {
140 if (this.collapsed) {
141 this.expand();
141 this.expand();
142 } else {
142 } else {
143 this.collapse();
143 this.collapse();
144 }
144 }
145 };
145 };
146
146
147
147
148 OutputArea.prototype.scroll_area = function () {
148 OutputArea.prototype.scroll_area = function () {
149 this.element.addClass('output_scroll');
149 this.element.addClass('output_scroll');
150 this.prompt_overlay.attr('title', 'click to unscroll output; double click to hide');
150 this.prompt_overlay.attr('title', 'click to unscroll output; double click to hide');
151 this.scrolled = true;
151 this.scrolled = true;
152 };
152 };
153
153
154
154
155 OutputArea.prototype.unscroll_area = function () {
155 OutputArea.prototype.unscroll_area = function () {
156 this.element.removeClass('output_scroll');
156 this.element.removeClass('output_scroll');
157 this.prompt_overlay.attr('title', 'click to scroll output; double click to hide');
157 this.prompt_overlay.attr('title', 'click to scroll output; double click to hide');
158 this.scrolled = false;
158 this.scrolled = false;
159 };
159 };
160
160
161 /**
161 /**
162 *
162 *
163 * Scroll OutputArea if height supperior than a threshold (in lines).
163 * Scroll OutputArea if height supperior than a threshold (in lines).
164 *
164 *
165 * Threshold is a maximum number of lines. If unspecified, defaults to
165 * Threshold is a maximum number of lines. If unspecified, defaults to
166 * OutputArea.minimum_scroll_threshold.
166 * OutputArea.minimum_scroll_threshold.
167 *
167 *
168 * Negative threshold will prevent the OutputArea from ever scrolling.
168 * Negative threshold will prevent the OutputArea from ever scrolling.
169 *
169 *
170 * @method scroll_if_long
170 * @method scroll_if_long
171 *
171 *
172 * @param [lines=20]{Number} Default to 20 if not set,
172 * @param [lines=20]{Number} Default to 20 if not set,
173 * behavior undefined for value of `0`.
173 * behavior undefined for value of `0`.
174 *
174 *
175 **/
175 **/
176 OutputArea.prototype.scroll_if_long = function (lines) {
176 OutputArea.prototype.scroll_if_long = function (lines) {
177 var n = lines | OutputArea.minimum_scroll_threshold;
177 var n = lines | OutputArea.minimum_scroll_threshold;
178 if(n <= 0){
178 if(n <= 0){
179 return
179 return
180 }
180 }
181
181
182 if (this._should_scroll(n)) {
182 if (this._should_scroll(n)) {
183 // only allow scrolling long-enough output
183 // only allow scrolling long-enough output
184 this.scroll_area();
184 this.scroll_area();
185 }
185 }
186 };
186 };
187
187
188
188
189 OutputArea.prototype.toggle_scroll = function () {
189 OutputArea.prototype.toggle_scroll = function () {
190 if (this.scrolled) {
190 if (this.scrolled) {
191 this.unscroll_area();
191 this.unscroll_area();
192 } else {
192 } else {
193 // only allow scrolling long-enough output
193 // only allow scrolling long-enough output
194 this.scroll_if_long();
194 this.scroll_if_long();
195 }
195 }
196 };
196 };
197
197
198
198
199 // typeset with MathJax if MathJax is available
199 // typeset with MathJax if MathJax is available
200 OutputArea.prototype.typeset = function () {
200 OutputArea.prototype.typeset = function () {
201 if (window.MathJax){
201 if (window.MathJax){
202 MathJax.Hub.Queue(["Typeset",MathJax.Hub]);
202 MathJax.Hub.Queue(["Typeset",MathJax.Hub]);
203 }
203 }
204 };
204 };
205
205
206
206
207 OutputArea.prototype.handle_output = function (msg) {
207 OutputArea.prototype.handle_output = function (msg) {
208 var json = {};
208 var json = {};
209 var msg_type = json.output_type = msg.header.msg_type;
209 var msg_type = json.output_type = msg.header.msg_type;
210 var content = msg.content;
210 var content = msg.content;
211 if (msg_type === "stream") {
211 if (msg_type === "stream") {
212 json.text = content.data;
212 json.text = content.data;
213 json.stream = content.name;
213 json.stream = content.name;
214 } else if (msg_type === "display_data") {
214 } else if (msg_type === "display_data") {
215 json = content.data;
215 json = content.data;
216 json.output_type = msg_type;
216 json.output_type = msg_type;
217 json.metadata = content.metadata;
217 json.metadata = content.metadata;
218 } else if (msg_type === "execute_result") {
218 } else if (msg_type === "execute_result") {
219 json = content.data;
219 json = content.data;
220 json.output_type = msg_type;
220 json.output_type = msg_type;
221 json.metadata = content.metadata;
221 json.metadata = content.metadata;
222 json.prompt_number = content.execution_count;
222 json.prompt_number = content.execution_count;
223 } else if (msg_type === "error") {
223 } else if (msg_type === "error") {
224 json.ename = content.ename;
224 json.ename = content.ename;
225 json.evalue = content.evalue;
225 json.evalue = content.evalue;
226 json.traceback = content.traceback;
226 json.traceback = content.traceback;
227 } else {
227 } else {
228 console.log("unhandled output message", msg);
228 console.log("unhandled output message", msg);
229 return;
229 return;
230 }
230 }
231 this.append_output(json);
231 this.append_output(json);
232 };
232 };
233
233
234
234
235 OutputArea.prototype.rename_keys = function (data, key_map) {
235 OutputArea.prototype.rename_keys = function (data, key_map) {
236 var remapped = {};
236 var remapped = {};
237 for (var key in data) {
237 for (var key in data) {
238 var new_key = key_map[key] || key;
238 var new_key = key_map[key] || key;
239 remapped[new_key] = data[key];
239 remapped[new_key] = data[key];
240 }
240 }
241 return remapped;
241 return remapped;
242 };
242 };
243
243
244
244
245 OutputArea.output_types = [
245 OutputArea.output_types = [
246 'application/javascript',
246 'application/javascript',
247 'text/html',
247 'text/html',
248 'text/markdown',
248 'text/markdown',
249 'text/latex',
249 'text/latex',
250 'image/svg+xml',
250 'image/svg+xml',
251 'image/png',
251 'image/png',
252 'image/jpeg',
252 'image/jpeg',
253 'application/pdf',
253 'application/pdf',
254 'text/plain'
254 'text/plain'
255 ];
255 ];
256
256
257 OutputArea.prototype.validate_output = function (json) {
257 OutputArea.prototype.validate_output = function (json) {
258 // scrub invalid outputs
258 // scrub invalid outputs
259 // TODO: right now everything is a string, but JSON really shouldn't be.
259 // TODO: right now everything is a string, but JSON really shouldn't be.
260 // nbformat 4 will fix that.
260 // nbformat 4 will fix that.
261 $.map(OutputArea.output_types, function(key){
261 $.map(OutputArea.output_types, function(key){
262 if (json[key] !== undefined && typeof json[key] !== 'string') {
262 if (json[key] !== undefined && typeof json[key] !== 'string') {
263 console.log("Invalid type for " + key, json[key]);
263 console.log("Invalid type for " + key, json[key]);
264 delete json[key];
264 delete json[key];
265 }
265 }
266 });
266 });
267 return json;
267 return json;
268 };
268 };
269
269
270 OutputArea.prototype.append_output = function (json) {
270 OutputArea.prototype.append_output = function (json) {
271 this.expand();
271 this.expand();
272
272
273 // validate output data types
273 // validate output data types
274 json = this.validate_output(json);
274 json = this.validate_output(json);
275
275
276 // Clear the output if clear is queued.
276 // Clear the output if clear is queued.
277 var needs_height_reset = false;
277 var needs_height_reset = false;
278 if (this.clear_queued) {
278 if (this.clear_queued) {
279 this.clear_output(false);
279 this.clear_output(false);
280 needs_height_reset = true;
280 needs_height_reset = true;
281 }
281 }
282
282
283 if (json.output_type === 'execute_result') {
283 if (json.output_type === 'execute_result') {
284 this.append_execute_result(json);
284 this.append_execute_result(json);
285 } else if (json.output_type === 'error') {
285 } else if (json.output_type === 'error') {
286 this.append_error(json);
286 this.append_error(json);
287 } else if (json.output_type === 'stream') {
287 } else if (json.output_type === 'stream') {
288 this.append_stream(json);
288 this.append_stream(json);
289 }
289 }
290
290
291 // We must release the animation fixed height in a callback since Gecko
291 // We must release the animation fixed height in a callback since Gecko
292 // (FireFox) doesn't render the image immediately as the data is
292 // (FireFox) doesn't render the image immediately as the data is
293 // available.
293 // available.
294 var that = this;
294 var that = this;
295 var handle_appended = function ($el) {
295 var handle_appended = function ($el) {
296 // Only reset the height to automatic if the height is currently
296 // Only reset the height to automatic if the height is currently
297 // fixed (done by wait=True flag on clear_output).
297 // fixed (done by wait=True flag on clear_output).
298 if (needs_height_reset) {
298 if (needs_height_reset) {
299 that.element.height('');
299 that.element.height('');
300 }
300 }
301 that.element.trigger('resize');
301 that.element.trigger('resize');
302 };
302 };
303 if (json.output_type === 'display_data') {
303 if (json.output_type === 'display_data') {
304 this.append_display_data(json, handle_appended);
304 this.append_display_data(json, handle_appended);
305 } else {
305 } else {
306 handle_appended();
306 handle_appended();
307 }
307 }
308
308
309 this.outputs.push(json);
309 this.outputs.push(json);
310 };
310 };
311
311
312
312
313 OutputArea.prototype.create_output_area = function () {
313 OutputArea.prototype.create_output_area = function () {
314 var oa = $("<div/>").addClass("output_area");
314 var oa = $("<div/>").addClass("output_area");
315 if (this.prompt_area) {
315 if (this.prompt_area) {
316 oa.append($('<div/>').addClass('prompt'));
316 oa.append($('<div/>').addClass('prompt'));
317 }
317 }
318 return oa;
318 return oa;
319 };
319 };
320
320
321
321
322 function _get_metadata_key(metadata, key, mime) {
322 function _get_metadata_key(metadata, key, mime) {
323 var mime_md = metadata[mime];
323 var mime_md = metadata[mime];
324 // mime-specific higher priority
324 // mime-specific higher priority
325 if (mime_md && mime_md[key] !== undefined) {
325 if (mime_md && mime_md[key] !== undefined) {
326 return mime_md[key];
326 return mime_md[key];
327 }
327 }
328 // fallback on global
328 // fallback on global
329 return metadata[key];
329 return metadata[key];
330 }
330 }
331
331
332 OutputArea.prototype.create_output_subarea = function(md, classes, mime) {
332 OutputArea.prototype.create_output_subarea = function(md, classes, mime) {
333 var subarea = $('<div/>').addClass('output_subarea').addClass(classes);
333 var subarea = $('<div/>').addClass('output_subarea').addClass(classes);
334 if (_get_metadata_key(md, 'isolated', mime)) {
334 if (_get_metadata_key(md, 'isolated', mime)) {
335 // Create an iframe to isolate the subarea from the rest of the
335 // Create an iframe to isolate the subarea from the rest of the
336 // document
336 // document
337 var iframe = $('<iframe/>').addClass('box-flex1');
337 var iframe = $('<iframe/>').addClass('box-flex1');
338 iframe.css({'height':1, 'width':'100%', 'display':'block'});
338 iframe.css({'height':1, 'width':'100%', 'display':'block'});
339 iframe.attr('frameborder', 0);
339 iframe.attr('frameborder', 0);
340 iframe.attr('scrolling', 'auto');
340 iframe.attr('scrolling', 'auto');
341
341
342 // Once the iframe is loaded, the subarea is dynamically inserted
342 // Once the iframe is loaded, the subarea is dynamically inserted
343 iframe.on('load', function() {
343 iframe.on('load', function() {
344 // Workaround needed by Firefox, to properly render svg inside
344 // Workaround needed by Firefox, to properly render svg inside
345 // iframes, see http://stackoverflow.com/questions/10177190/
345 // iframes, see http://stackoverflow.com/questions/10177190/
346 // svg-dynamically-added-to-iframe-does-not-render-correctly
346 // svg-dynamically-added-to-iframe-does-not-render-correctly
347 this.contentDocument.open();
347 this.contentDocument.open();
348
348
349 // Insert the subarea into the iframe
349 // Insert the subarea into the iframe
350 // We must directly write the html. When using Jquery's append
350 // We must directly write the html. When using Jquery's append
351 // method, javascript is evaluated in the parent document and
351 // method, javascript is evaluated in the parent document and
352 // not in the iframe document. At this point, subarea doesn't
352 // not in the iframe document. At this point, subarea doesn't
353 // contain any user content.
353 // contain any user content.
354 this.contentDocument.write(subarea.html());
354 this.contentDocument.write(subarea.html());
355
355
356 this.contentDocument.close();
356 this.contentDocument.close();
357
357
358 var body = this.contentDocument.body;
358 var body = this.contentDocument.body;
359 // Adjust the iframe height automatically
359 // Adjust the iframe height automatically
360 iframe.height(body.scrollHeight + 'px');
360 iframe.height(body.scrollHeight + 'px');
361 });
361 });
362
362
363 // Elements should be appended to the inner subarea and not to the
363 // Elements should be appended to the inner subarea and not to the
364 // iframe
364 // iframe
365 iframe.append = function(that) {
365 iframe.append = function(that) {
366 subarea.append(that);
366 subarea.append(that);
367 };
367 };
368
368
369 return iframe;
369 return iframe;
370 } else {
370 } else {
371 return subarea;
371 return subarea;
372 }
372 }
373 }
373 }
374
374
375
375
376 OutputArea.prototype._append_javascript_error = function (err, element) {
376 OutputArea.prototype._append_javascript_error = function (err, element) {
377 // display a message when a javascript error occurs in display output
377 // display a message when a javascript error occurs in display output
378 var msg = "Javascript error adding output!"
378 var msg = "Javascript error adding output!"
379 if ( element === undefined ) return;
379 if ( element === undefined ) return;
380 element
380 element
381 .append($('<div/>').text(msg).addClass('js-error'))
381 .append($('<div/>').text(msg).addClass('js-error'))
382 .append($('<div/>').text(err.toString()).addClass('js-error'))
382 .append($('<div/>').text(err.toString()).addClass('js-error'))
383 .append($('<div/>').text('See your browser Javascript console for more details.').addClass('js-error'));
383 .append($('<div/>').text('See your browser Javascript console for more details.').addClass('js-error'));
384 };
384 };
385
385
386 OutputArea.prototype._safe_append = function (toinsert) {
386 OutputArea.prototype._safe_append = function (toinsert) {
387 // safely append an item to the document
387 // safely append an item to the document
388 // this is an object created by user code,
388 // this is an object created by user code,
389 // and may have errors, which should not be raised
389 // and may have errors, which should not be raised
390 // under any circumstances.
390 // under any circumstances.
391 try {
391 try {
392 this.element.append(toinsert);
392 this.element.append(toinsert);
393 } catch(err) {
393 } catch(err) {
394 console.log(err);
394 console.log(err);
395 // Create an actual output_area and output_subarea, which creates
395 // Create an actual output_area and output_subarea, which creates
396 // the prompt area and the proper indentation.
396 // the prompt area and the proper indentation.
397 var toinsert = this.create_output_area();
397 var toinsert = this.create_output_area();
398 var subarea = $('<div/>').addClass('output_subarea');
398 var subarea = $('<div/>').addClass('output_subarea');
399 toinsert.append(subarea);
399 toinsert.append(subarea);
400 this._append_javascript_error(err, subarea);
400 this._append_javascript_error(err, subarea);
401 this.element.append(toinsert);
401 this.element.append(toinsert);
402 }
402 }
403 };
403 };
404
404
405
405
406 OutputArea.prototype.append_execute_result = function (json) {
406 OutputArea.prototype.append_execute_result = function (json) {
407 var n = json.prompt_number || ' ';
407 var n = json.prompt_number || ' ';
408 var toinsert = this.create_output_area();
408 var toinsert = this.create_output_area();
409 if (this.prompt_area) {
409 if (this.prompt_area) {
410 toinsert.find('div.prompt').addClass('output_prompt').text('Out[' + n + ']:');
410 toinsert.find('div.prompt').addClass('output_prompt').text('Out[' + n + ']:');
411 }
411 }
412 var inserted = this.append_mime_type(json, toinsert);
412 var inserted = this.append_mime_type(json, toinsert);
413 if (inserted) {
413 if (inserted) {
414 inserted.addClass('output_result');
414 inserted.addClass('output_result');
415 }
415 }
416 this._safe_append(toinsert);
416 this._safe_append(toinsert);
417 // If we just output latex, typeset it.
417 // If we just output latex, typeset it.
418 if ((json['text/latex'] !== undefined) ||
418 if ((json['text/latex'] !== undefined) ||
419 (json['text/html'] !== undefined) ||
419 (json['text/html'] !== undefined) ||
420 (json['text/markdown'] !== undefined)) {
420 (json['text/markdown'] !== undefined)) {
421 this.typeset();
421 this.typeset();
422 }
422 }
423 };
423 };
424
424
425
425
426 OutputArea.prototype.append_error = function (json) {
426 OutputArea.prototype.append_error = function (json) {
427 var tb = json.traceback;
427 var tb = json.traceback;
428 if (tb !== undefined && tb.length > 0) {
428 if (tb !== undefined && tb.length > 0) {
429 var s = '';
429 var s = '';
430 var len = tb.length;
430 var len = tb.length;
431 for (var i=0; i<len; i++) {
431 for (var i=0; i<len; i++) {
432 s = s + tb[i] + '\n';
432 s = s + tb[i] + '\n';
433 }
433 }
434 s = s + '\n';
434 s = s + '\n';
435 var toinsert = this.create_output_area();
435 var toinsert = this.create_output_area();
436 var append_text = OutputArea.append_map['text/plain'];
436 var append_text = OutputArea.append_map['text/plain'];
437 if (append_text) {
437 if (append_text) {
438 append_text.apply(this, [s, {}, toinsert]).addClass('output_error');
438 append_text.apply(this, [s, {}, toinsert]).addClass('output_error');
439 }
439 }
440 this._safe_append(toinsert);
440 this._safe_append(toinsert);
441 }
441 }
442 };
442 };
443
443
444
444
445 OutputArea.prototype.append_stream = function (json) {
445 OutputArea.prototype.append_stream = function (json) {
446 // temporary fix: if stream undefined (json file written prior to this patch),
446 // temporary fix: if stream undefined (json file written prior to this patch),
447 // default to most likely stdout:
447 // default to most likely stdout:
448 if (json.stream === undefined){
448 if (json.stream === undefined){
449 json.stream = 'stdout';
449 json.stream = 'stdout';
450 }
450 }
451 var text = json.text;
451 var text = json.text;
452 var subclass = "output_"+json.stream;
452 var subclass = "output_"+json.stream;
453 if (this.outputs.length > 0){
453 if (this.outputs.length > 0){
454 // have at least one output to consider
454 // have at least one output to consider
455 var last = this.outputs[this.outputs.length-1];
455 var last = this.outputs[this.outputs.length-1];
456 if (last.output_type == 'stream' && json.stream == last.stream){
456 if (last.output_type == 'stream' && json.stream == last.stream){
457 // latest output was in the same stream,
457 // latest output was in the same stream,
458 // so append directly into its pre tag
458 // so append directly into its pre tag
459 // escape ANSI & HTML specials:
459 // escape ANSI & HTML specials:
460 var pre = this.element.find('div.'+subclass).last().find('pre');
460 var pre = this.element.find('div.'+subclass).last().find('pre');
461 var html = utils.fixCarriageReturn(
461 var html = utils.fixCarriageReturn(
462 pre.html() + utils.fixConsole(text));
462 pre.html() + utils.fixConsole(text));
463 // The only user content injected with this HTML call is
463 // The only user content injected with this HTML call is
464 // escaped by the fixConsole() method.
464 // escaped by the fixConsole() method.
465 pre.html(html);
465 pre.html(html);
466 return;
466 return;
467 }
467 }
468 }
468 }
469
469
470 if (!text.replace("\r", "")) {
470 if (!text.replace("\r", "")) {
471 // text is nothing (empty string, \r, etc.)
471 // text is nothing (empty string, \r, etc.)
472 // so don't append any elements, which might add undesirable space
472 // so don't append any elements, which might add undesirable space
473 return;
473 return;
474 }
474 }
475
475
476 // If we got here, attach a new div
476 // If we got here, attach a new div
477 var toinsert = this.create_output_area();
477 var toinsert = this.create_output_area();
478 var append_text = OutputArea.append_map['text/plain'];
478 var append_text = OutputArea.append_map['text/plain'];
479 if (append_text) {
479 if (append_text) {
480 append_text.apply(this, [text, {}, toinsert]).addClass("output_stream " + subclass);
480 append_text.apply(this, [text, {}, toinsert]).addClass("output_stream " + subclass);
481 }
481 }
482 this._safe_append(toinsert);
482 this._safe_append(toinsert);
483 };
483 };
484
484
485
485
486 OutputArea.prototype.append_display_data = function (json, handle_inserted) {
486 OutputArea.prototype.append_display_data = function (json, handle_inserted) {
487 var toinsert = this.create_output_area();
487 var toinsert = this.create_output_area();
488 if (this.append_mime_type(json, toinsert, handle_inserted)) {
488 if (this.append_mime_type(json, toinsert, handle_inserted)) {
489 this._safe_append(toinsert);
489 this._safe_append(toinsert);
490 // If we just output latex, typeset it.
490 // If we just output latex, typeset it.
491 if ((json['text/latex'] !== undefined) ||
491 if ((json['text/latex'] !== undefined) ||
492 (json['text/html'] !== undefined) ||
492 (json['text/html'] !== undefined) ||
493 (json['text/markdown'] !== undefined)) {
493 (json['text/markdown'] !== undefined)) {
494 this.typeset();
494 this.typeset();
495 }
495 }
496 }
496 }
497 };
497 };
498
498
499
499
500 OutputArea.safe_outputs = {
500 OutputArea.safe_outputs = {
501 'text/plain' : true,
501 'text/plain' : true,
502 'text/latex' : true,
502 'text/latex' : true,
503 'image/png' : true,
503 'image/png' : true,
504 'image/jpeg' : true
504 'image/jpeg' : true
505 };
505 };
506
506
507 OutputArea.prototype.append_mime_type = function (json, element, handle_inserted) {
507 OutputArea.prototype.append_mime_type = function (json, element, handle_inserted) {
508 for (var i=0; i < OutputArea.display_order.length; i++) {
508 for (var i=0; i < OutputArea.display_order.length; i++) {
509 var type = OutputArea.display_order[i];
509 var type = OutputArea.display_order[i];
510 var append = OutputArea.append_map[type];
510 var append = OutputArea.append_map[type];
511 if ((json[type] !== undefined) && append) {
511 if ((json[type] !== undefined) && append) {
512 var value = json[type];
512 var value = json[type];
513 if (!this.trusted && !OutputArea.safe_outputs[type]) {
513 if (!this.trusted && !OutputArea.safe_outputs[type]) {
514 // not trusted, sanitize HTML
514 // not trusted, sanitize HTML
515 if (type==='text/html' || type==='text/svg') {
515 if (type==='text/html' || type==='text/svg') {
516 value = security.sanitize_html(value);
516 value = security.sanitize_html(value);
517 } else {
517 } else {
518 // don't display if we don't know how to sanitize it
518 // don't display if we don't know how to sanitize it
519 console.log("Ignoring untrusted " + type + " output.");
519 console.log("Ignoring untrusted " + type + " output.");
520 continue;
520 continue;
521 }
521 }
522 }
522 }
523 var md = json.metadata || {};
523 var md = json.metadata || {};
524 var toinsert = append.apply(this, [value, md, element, handle_inserted]);
524 var toinsert = append.apply(this, [value, md, element, handle_inserted]);
525 // Since only the png and jpeg mime types call the inserted
525 // Since only the png and jpeg mime types call the inserted
526 // callback, if the mime type is something other we must call the
526 // callback, if the mime type is something other we must call the
527 // inserted callback only when the element is actually inserted
527 // inserted callback only when the element is actually inserted
528 // into the DOM. Use a timeout of 0 to do this.
528 // into the DOM. Use a timeout of 0 to do this.
529 if (['image/png', 'image/jpeg'].indexOf(type) < 0 && handle_inserted !== undefined) {
529 if (['image/png', 'image/jpeg'].indexOf(type) < 0 && handle_inserted !== undefined) {
530 setTimeout(handle_inserted, 0);
530 setTimeout(handle_inserted, 0);
531 }
531 }
532 this.events.trigger('output_appended.OutputArea', [type, value, md, toinsert]);
532 this.events.trigger('output_appended.OutputArea', [type, value, md, toinsert]);
533 return toinsert;
533 return toinsert;
534 }
534 }
535 }
535 }
536 return null;
536 return null;
537 };
537 };
538
538
539
539
540 var append_html = function (html, md, element) {
540 var append_html = function (html, md, element) {
541 var type = 'text/html';
541 var type = 'text/html';
542 var toinsert = this.create_output_subarea(md, "output_html rendered_html", type);
542 var toinsert = this.create_output_subarea(md, "output_html rendered_html", type);
543 this.keyboard_manager.register_events(toinsert);
543 this.keyboard_manager.register_events(toinsert);
544 toinsert.append(html);
544 toinsert.append(html);
545 element.append(toinsert);
545 element.append(toinsert);
546 return toinsert;
546 return toinsert;
547 };
547 };
548
548
549
549
550 var append_markdown = function(markdown, md, element) {
550 var append_markdown = function(markdown, md, element) {
551 var type = 'text/markdown';
551 var type = 'text/markdown';
552 var toinsert = this.create_output_subarea(md, "output_markdown", type);
552 var toinsert = this.create_output_subarea(md, "output_markdown", type);
553 var text_and_math = mathjaxutils.remove_math(markdown);
553 var text_and_math = mathjaxutils.remove_math(markdown);
554 var text = text_and_math[0];
554 var text = text_and_math[0];
555 var math = text_and_math[1];
555 var math = text_and_math[1];
556 var html = marked.parser(marked.lexer(text));
556 var html = marked.parser(marked.lexer(text));
557 html = mathjaxutils.replace_math(html, math);
557 html = mathjaxutils.replace_math(html, math);
558 toinsert.append(html);
558 toinsert.append(html);
559 element.append(toinsert);
559 element.append(toinsert);
560 return toinsert;
560 return toinsert;
561 };
561 };
562
562
563
563
564 var append_javascript = function (js, md, element) {
564 var append_javascript = function (js, md, element) {
565 // We just eval the JS code, element appears in the local scope.
565 // We just eval the JS code, element appears in the local scope.
566 var type = 'application/javascript';
566 var type = 'application/javascript';
567 var toinsert = this.create_output_subarea(md, "output_javascript", type);
567 var toinsert = this.create_output_subarea(md, "output_javascript", type);
568 this.keyboard_manager.register_events(toinsert);
568 this.keyboard_manager.register_events(toinsert);
569 element.append(toinsert);
569 element.append(toinsert);
570
570
571 // Fix for ipython/issues/5293, make sure `element` is the area which
571 // Fix for ipython/issues/5293, make sure `element` is the area which
572 // output can be inserted into at the time of JS execution.
572 // output can be inserted into at the time of JS execution.
573 element = toinsert;
573 element = toinsert;
574 try {
574 try {
575 eval(js);
575 eval(js);
576 } catch(err) {
576 } catch(err) {
577 console.log(err);
577 console.log(err);
578 this._append_javascript_error(err, toinsert);
578 this._append_javascript_error(err, toinsert);
579 }
579 }
580 return toinsert;
580 return toinsert;
581 };
581 };
582
582
583
583
584 var append_text = function (data, md, element) {
584 var append_text = function (data, md, element) {
585 var type = 'text/plain';
585 var type = 'text/plain';
586 var toinsert = this.create_output_subarea(md, "output_text", type);
586 var toinsert = this.create_output_subarea(md, "output_text", type);
587 // escape ANSI & HTML specials in plaintext:
587 // escape ANSI & HTML specials in plaintext:
588 data = utils.fixConsole(data);
588 data = utils.fixConsole(data);
589 data = utils.fixCarriageReturn(data);
589 data = utils.fixCarriageReturn(data);
590 data = utils.autoLinkUrls(data);
590 data = utils.autoLinkUrls(data);
591 // The only user content injected with this HTML call is
591 // The only user content injected with this HTML call is
592 // escaped by the fixConsole() method.
592 // escaped by the fixConsole() method.
593 toinsert.append($("<pre/>").html(data));
593 toinsert.append($("<pre/>").html(data));
594 element.append(toinsert);
594 element.append(toinsert);
595 return toinsert;
595 return toinsert;
596 };
596 };
597
597
598
598
599 var append_svg = function (svg_html, md, element) {
599 var append_svg = function (svg_html, md, element) {
600 var type = 'image/svg+xml';
600 var type = 'image/svg+xml';
601 var toinsert = this.create_output_subarea(md, "output_svg", type);
601 var toinsert = this.create_output_subarea(md, "output_svg", type);
602
602
603 // Get the svg element from within the HTML.
603 // Get the svg element from within the HTML.
604 var svg = $('<div />').html(svg_html).find('svg');
604 var svg = $('<div />').html(svg_html).find('svg');
605 var svg_area = $('<div />');
605 var svg_area = $('<div />');
606 var width = svg.attr('width');
606 var width = svg.attr('width');
607 var height = svg.attr('height');
607 var height = svg.attr('height');
608 svg
608 svg
609 .width('100%')
609 .width('100%')
610 .height('100%');
610 .height('100%');
611 svg_area
611 svg_area
612 .width(width)
612 .width(width)
613 .height(height);
613 .height(height);
614
614
615 // The jQuery resize handlers don't seem to work on the svg element.
615 // The jQuery resize handlers don't seem to work on the svg element.
616 // When the svg renders completely, measure it's size and set the parent
616 // When the svg renders completely, measure it's size and set the parent
617 // div to that size. Then set the svg to 100% the size of the parent
617 // div to that size. Then set the svg to 100% the size of the parent
618 // div and make the parent div resizable.
618 // div and make the parent div resizable.
619 this._dblclick_to_reset_size(svg_area, true, false);
619 this._dblclick_to_reset_size(svg_area, true, false);
620
620
621 svg_area.append(svg);
621 svg_area.append(svg);
622 toinsert.append(svg_area);
622 toinsert.append(svg_area);
623 element.append(toinsert);
623 element.append(toinsert);
624
624
625 return toinsert;
625 return toinsert;
626 };
626 };
627
627
628 OutputArea.prototype._dblclick_to_reset_size = function (img, immediately, resize_parent) {
628 OutputArea.prototype._dblclick_to_reset_size = function (img, immediately, resize_parent) {
629 // Add a resize handler to an element
629 // Add a resize handler to an element
630 //
630 //
631 // img: jQuery element
631 // img: jQuery element
632 // immediately: bool=False
632 // immediately: bool=False
633 // Wait for the element to load before creating the handle.
633 // Wait for the element to load before creating the handle.
634 // resize_parent: bool=True
634 // resize_parent: bool=True
635 // Should the parent of the element be resized when the element is
635 // Should the parent of the element be resized when the element is
636 // reset (by double click).
636 // reset (by double click).
637 var callback = function (){
637 var callback = function (){
638 var h0 = img.height();
638 var h0 = img.height();
639 var w0 = img.width();
639 var w0 = img.width();
640 if (!(h0 && w0)) {
640 if (!(h0 && w0)) {
641 // zero size, don't make it resizable
641 // zero size, don't make it resizable
642 return;
642 return;
643 }
643 }
644 img.resizable({
644 img.resizable({
645 aspectRatio: true,
645 aspectRatio: true,
646 autoHide: true
646 autoHide: true
647 });
647 });
648 img.dblclick(function () {
648 img.dblclick(function () {
649 // resize wrapper & image together for some reason:
649 // resize wrapper & image together for some reason:
650 img.height(h0);
650 img.height(h0);
651 img.width(w0);
651 img.width(w0);
652 if (resize_parent === undefined || resize_parent) {
652 if (resize_parent === undefined || resize_parent) {
653 img.parent().height(h0);
653 img.parent().height(h0);
654 img.parent().width(w0);
654 img.parent().width(w0);
655 }
655 }
656 });
656 });
657 };
657 };
658
658
659 if (immediately) {
659 if (immediately) {
660 callback();
660 callback();
661 } else {
661 } else {
662 img.on("load", callback);
662 img.on("load", callback);
663 }
663 }
664 };
664 };
665
665
666 var set_width_height = function (img, md, mime) {
666 var set_width_height = function (img, md, mime) {
667 // set width and height of an img element from metadata
667 // set width and height of an img element from metadata
668 var height = _get_metadata_key(md, 'height', mime);
668 var height = _get_metadata_key(md, 'height', mime);
669 if (height !== undefined) img.attr('height', height);
669 if (height !== undefined) img.attr('height', height);
670 var width = _get_metadata_key(md, 'width', mime);
670 var width = _get_metadata_key(md, 'width', mime);
671 if (width !== undefined) img.attr('width', width);
671 if (width !== undefined) img.attr('width', width);
672 };
672 };
673
673
674 var append_png = function (png, md, element, handle_inserted) {
674 var append_png = function (png, md, element, handle_inserted) {
675 var type = 'image/png';
675 var type = 'image/png';
676 var toinsert = this.create_output_subarea(md, "output_png", type);
676 var toinsert = this.create_output_subarea(md, "output_png", type);
677 var img = $("<img/>");
677 var img = $("<img/>");
678 if (handle_inserted !== undefined) {
678 if (handle_inserted !== undefined) {
679 img.on('load', function(){
679 img.on('load', function(){
680 handle_inserted(img);
680 handle_inserted(img);
681 });
681 });
682 }
682 }
683 img[0].src = 'data:image/png;base64,'+ png;
683 img[0].src = 'data:image/png;base64,'+ png;
684 set_width_height(img, md, 'image/png');
684 set_width_height(img, md, 'image/png');
685 this._dblclick_to_reset_size(img);
685 this._dblclick_to_reset_size(img);
686 toinsert.append(img);
686 toinsert.append(img);
687 element.append(toinsert);
687 element.append(toinsert);
688 return toinsert;
688 return toinsert;
689 };
689 };
690
690
691
691
692 var append_jpeg = function (jpeg, md, element, handle_inserted) {
692 var append_jpeg = function (jpeg, md, element, handle_inserted) {
693 var type = 'image/jpeg';
693 var type = 'image/jpeg';
694 var toinsert = this.create_output_subarea(md, "output_jpeg", type);
694 var toinsert = this.create_output_subarea(md, "output_jpeg", type);
695 var img = $("<img/>");
695 var img = $("<img/>");
696 if (handle_inserted !== undefined) {
696 if (handle_inserted !== undefined) {
697 img.on('load', function(){
697 img.on('load', function(){
698 handle_inserted(img);
698 handle_inserted(img);
699 });
699 });
700 }
700 }
701 img[0].src = 'data:image/jpeg;base64,'+ jpeg;
701 img[0].src = 'data:image/jpeg;base64,'+ jpeg;
702 set_width_height(img, md, 'image/jpeg');
702 set_width_height(img, md, 'image/jpeg');
703 this._dblclick_to_reset_size(img);
703 this._dblclick_to_reset_size(img);
704 toinsert.append(img);
704 toinsert.append(img);
705 element.append(toinsert);
705 element.append(toinsert);
706 return toinsert;
706 return toinsert;
707 };
707 };
708
708
709
709
710 var append_pdf = function (pdf, md, element) {
710 var append_pdf = function (pdf, md, element) {
711 var type = 'application/pdf';
711 var type = 'application/pdf';
712 var toinsert = this.create_output_subarea(md, "output_pdf", type);
712 var toinsert = this.create_output_subarea(md, "output_pdf", type);
713 var a = $('<a/>').attr('href', 'data:application/pdf;base64,'+pdf);
713 var a = $('<a/>').attr('href', 'data:application/pdf;base64,'+pdf);
714 a.attr('target', '_blank');
714 a.attr('target', '_blank');
715 a.text('View PDF')
715 a.text('View PDF')
716 toinsert.append(a);
716 toinsert.append(a);
717 element.append(toinsert);
717 element.append(toinsert);
718 return toinsert;
718 return toinsert;
719 }
719 }
720
720
721 var append_latex = function (latex, md, element) {
721 var append_latex = function (latex, md, element) {
722 // This method cannot do the typesetting because the latex first has to
722 // This method cannot do the typesetting because the latex first has to
723 // be on the page.
723 // be on the page.
724 var type = 'text/latex';
724 var type = 'text/latex';
725 var toinsert = this.create_output_subarea(md, "output_latex", type);
725 var toinsert = this.create_output_subarea(md, "output_latex", type);
726 toinsert.append(latex);
726 toinsert.append(latex);
727 element.append(toinsert);
727 element.append(toinsert);
728 return toinsert;
728 return toinsert;
729 };
729 };
730
730
731
731
732 OutputArea.prototype.append_raw_input = function (msg) {
732 OutputArea.prototype.append_raw_input = function (msg) {
733 var that = this;
733 var that = this;
734 this.expand();
734 this.expand();
735 var content = msg.content;
735 var content = msg.content;
736 var area = this.create_output_area();
736 var area = this.create_output_area();
737
737
738 // disable any other raw_inputs, if they are left around
738 // disable any other raw_inputs, if they are left around
739 $("div.output_subarea.raw_input_container").remove();
739 $("div.output_subarea.raw_input_container").remove();
740
740
741 var input_type = content.password ? 'password' : 'text';
741 var input_type = content.password ? 'password' : 'text';
742
742
743 area.append(
743 area.append(
744 $("<div/>")
744 $("<div/>")
745 .addClass("box-flex1 output_subarea raw_input_container")
745 .addClass("box-flex1 output_subarea raw_input_container")
746 .append(
746 .append(
747 $("<span/>")
747 $("<span/>")
748 .addClass("raw_input_prompt")
748 .addClass("raw_input_prompt")
749 .text(content.prompt)
749 .text(content.prompt)
750 )
750 )
751 .append(
751 .append(
752 $("<input/>")
752 $("<input/>")
753 .addClass("raw_input")
753 .addClass("raw_input")
754 .attr('type', input_type)
754 .attr('type', input_type)
755 .attr("size", 47)
755 .attr("size", 47)
756 .keydown(function (event, ui) {
756 .keydown(function (event, ui) {
757 // make sure we submit on enter,
757 // make sure we submit on enter,
758 // and don't re-execute the *cell* on shift-enter
758 // and don't re-execute the *cell* on shift-enter
759 if (event.which === keyboard.keycodes.enter) {
759 if (event.which === keyboard.keycodes.enter) {
760 that._submit_raw_input();
760 that._submit_raw_input();
761 return false;
761 return false;
762 }
762 }
763 })
763 })
764 )
764 )
765 );
765 );
766
766
767 this.element.append(area);
767 this.element.append(area);
768 var raw_input = area.find('input.raw_input');
768 var raw_input = area.find('input.raw_input');
769 // Register events that enable/disable the keyboard manager while raw
769 // Register events that enable/disable the keyboard manager while raw
770 // input is focused.
770 // input is focused.
771 this.keyboard_manager.register_events(raw_input);
771 this.keyboard_manager.register_events(raw_input);
772 // Note, the following line used to read raw_input.focus().focus().
772 // Note, the following line used to read raw_input.focus().focus().
773 // This seemed to be needed otherwise only the cell would be focused.
773 // This seemed to be needed otherwise only the cell would be focused.
774 // But with the modal UI, this seems to work fine with one call to focus().
774 // But with the modal UI, this seems to work fine with one call to focus().
775 raw_input.focus();
775 raw_input.focus();
776 }
776 }
777
777
778 OutputArea.prototype._submit_raw_input = function (evt) {
778 OutputArea.prototype._submit_raw_input = function (evt) {
779 var container = this.element.find("div.raw_input_container");
779 var container = this.element.find("div.raw_input_container");
780 var theprompt = container.find("span.raw_input_prompt");
780 var theprompt = container.find("span.raw_input_prompt");
781 var theinput = container.find("input.raw_input");
781 var theinput = container.find("input.raw_input");
782 var value = theinput.val();
782 var value = theinput.val();
783 var echo = value;
783 var echo = value;
784 // don't echo if it's a password
784 // don't echo if it's a password
785 if (theinput.attr('type') == 'password') {
785 if (theinput.attr('type') == 'password') {
786 echo = '········';
786 echo = '········';
787 }
787 }
788 var content = {
788 var content = {
789 output_type : 'stream',
789 output_type : 'stream',
790 stream : 'stdout',
790 stream : 'stdout',
791 text : theprompt.text() + echo + '\n'
791 text : theprompt.text() + echo + '\n'
792 }
792 }
793 // remove form container
793 // remove form container
794 container.parent().remove();
794 container.parent().remove();
795 // replace with plaintext version in stdout
795 // replace with plaintext version in stdout
796 this.append_output(content, false);
796 this.append_output(content, false);
797 this.events.trigger('send_input_reply.Kernel', value);
797 this.events.trigger('send_input_reply.Kernel', value);
798 }
798 }
799
799
800
800
801 OutputArea.prototype.handle_clear_output = function (msg) {
801 OutputArea.prototype.handle_clear_output = function (msg) {
802 // msg spec v4 had stdout, stderr, display keys
802 // msg spec v4 had stdout, stderr, display keys
803 // v4.1 replaced these with just wait
803 // v4.1 replaced these with just wait
804 // The default behavior is the same (stdout=stderr=display=True, wait=False),
804 // The default behavior is the same (stdout=stderr=display=True, wait=False),
805 // so v4 messages will still be properly handled,
805 // so v4 messages will still be properly handled,
806 // except for the rarely used clearing less than all output.
806 // except for the rarely used clearing less than all output.
807 this.clear_output(msg.content.wait || false);
807 this.clear_output(msg.content.wait || false);
808 };
808 };
809
809
810
810
811 OutputArea.prototype.clear_output = function(wait) {
811 OutputArea.prototype.clear_output = function(wait) {
812 if (wait) {
812 if (wait) {
813
813
814 // If a clear is queued, clear before adding another to the queue.
814 // If a clear is queued, clear before adding another to the queue.
815 if (this.clear_queued) {
815 if (this.clear_queued) {
816 this.clear_output(false);
816 this.clear_output(false);
817 };
817 };
818
818
819 this.clear_queued = true;
819 this.clear_queued = true;
820 } else {
820 } else {
821
821
822 // Fix the output div's height if the clear_output is waiting for
822 // Fix the output div's height if the clear_output is waiting for
823 // new output (it is being used in an animation).
823 // new output (it is being used in an animation).
824 if (this.clear_queued) {
824 if (this.clear_queued) {
825 var height = this.element.height();
825 var height = this.element.height();
826 this.element.height(height);
826 this.element.height(height);
827 this.clear_queued = false;
827 this.clear_queued = false;
828 }
828 }
829
829
830 // Clear all
830 // Clear all
831 // Remove load event handlers from img tags because we don't want
831 // Remove load event handlers from img tags because we don't want
832 // them to fire if the image is never added to the page.
832 // them to fire if the image is never added to the page.
833 this.element.find('img').off('load');
833 this.element.find('img').off('load');
834 this.element.html("");
834 this.element.html("");
835 this.outputs = [];
835 this.outputs = [];
836 this.trusted = true;
836 this.trusted = true;
837 this.unscroll_area();
837 this.unscroll_area();
838 return;
838 return;
839 };
839 };
840 };
840 };
841
841
842
842
843 // JSON serialization
843 // JSON serialization
844
844
845 OutputArea.prototype.fromJSON = function (outputs) {
845 OutputArea.prototype.fromJSON = function (outputs) {
846 var len = outputs.length;
846 var len = outputs.length;
847 var data;
847 var data;
848
848
849 for (var i=0; i<len; i++) {
849 for (var i=0; i<len; i++) {
850 data = outputs[i];
850 data = outputs[i];
851 var msg_type = data.output_type;
851 var msg_type = data.output_type;
852 if (msg_type == "pyout") {
852 if (msg_type == "pyout") {
853 // pyout message has been renamed to execute_result,
853 // pyout message has been renamed to execute_result,
854 // but the nbformat has not been updated,
854 // but the nbformat has not been updated,
855 // so transform back to pyout for json.
855 // so transform back to pyout for json.
856 msg_type = data.output_type = "execute_result";
856 msg_type = data.output_type = "execute_result";
857 } else if (msg_type == "pyerr") {
857 } else if (msg_type == "pyerr") {
858 // pyerr message has been renamed to error,
858 // pyerr message has been renamed to error,
859 // but the nbformat has not been updated,
859 // but the nbformat has not been updated,
860 // so transform back to pyerr for json.
860 // so transform back to pyerr for json.
861 msg_type = data.output_type = "error";
861 msg_type = data.output_type = "error";
862 }
862 }
863 if (msg_type === "display_data" || msg_type === "execute_result") {
863 if (msg_type === "display_data" || msg_type === "execute_result") {
864 // convert short keys to mime keys
864 // convert short keys to mime keys
865 // TODO: remove mapping of short keys when we update to nbformat 4
865 // TODO: remove mapping of short keys when we update to nbformat 4
866 data = this.rename_keys(data, OutputArea.mime_map_r);
866 data = this.rename_keys(data, OutputArea.mime_map_r);
867 data.metadata = this.rename_keys(data.metadata, OutputArea.mime_map_r);
867 data.metadata = this.rename_keys(data.metadata, OutputArea.mime_map_r);
868 // msg spec JSON is an object, nbformat v3 JSON is a JSON string
868 // msg spec JSON is an object, nbformat v3 JSON is a JSON string
869 if (data["application/json"] !== undefined && typeof data["application/json"] === 'string') {
869 if (data["application/json"] !== undefined && typeof data["application/json"] === 'string') {
870 data["application/json"] = JSON.parse(data["application/json"]);
870 data["application/json"] = JSON.parse(data["application/json"]);
871 }
871 }
872 }
872 }
873
873
874 this.append_output(data);
874 this.append_output(data);
875 }
875 }
876 };
876 };
877
877
878
878
879 OutputArea.prototype.toJSON = function () {
879 OutputArea.prototype.toJSON = function () {
880 var outputs = [];
880 var outputs = [];
881 var len = this.outputs.length;
881 var len = this.outputs.length;
882 var data;
882 var data;
883 for (var i=0; i<len; i++) {
883 for (var i=0; i<len; i++) {
884 data = this.outputs[i];
884 data = this.outputs[i];
885 var msg_type = data.output_type;
885 var msg_type = data.output_type;
886 if (msg_type === "display_data" || msg_type === "execute_result") {
886 if (msg_type === "display_data" || msg_type === "execute_result") {
887 // convert mime keys to short keys
887 // convert mime keys to short keys
888 data = this.rename_keys(data, OutputArea.mime_map);
888 data = this.rename_keys(data, OutputArea.mime_map);
889 data.metadata = this.rename_keys(data.metadata, OutputArea.mime_map);
889 data.metadata = this.rename_keys(data.metadata, OutputArea.mime_map);
890 // msg spec JSON is an object, nbformat v3 JSON is a JSON string
890 // msg spec JSON is an object, nbformat v3 JSON is a JSON string
891 if (data.json !== undefined && typeof data.json !== 'string') {
891 if (data.json !== undefined && typeof data.json !== 'string') {
892 data.json = JSON.stringify(data.json);
892 data.json = JSON.stringify(data.json);
893 }
893 }
894 }
894 }
895 if (msg_type == "execute_result") {
895 if (msg_type == "execute_result") {
896 // pyout message has been renamed to execute_result,
896 // pyout message has been renamed to execute_result,
897 // but the nbformat has not been updated,
897 // but the nbformat has not been updated,
898 // so transform back to pyout for json.
898 // so transform back to pyout for json.
899 data.output_type = "pyout";
899 data.output_type = "pyout";
900 } else if (msg_type == "error") {
900 } else if (msg_type == "error") {
901 // pyerr message has been renamed to error,
901 // pyerr message has been renamed to error,
902 // but the nbformat has not been updated,
902 // but the nbformat has not been updated,
903 // so transform back to pyerr for json.
903 // so transform back to pyerr for json.
904 data.output_type = "pyerr";
904 data.output_type = "pyerr";
905 }
905 }
906 outputs[i] = data;
906 outputs[i] = data;
907 }
907 }
908 return outputs;
908 return outputs;
909 };
909 };
910
910
911 /**
911 /**
912 * Class properties
912 * Class properties
913 **/
913 **/
914
914
915 /**
915 /**
916 * Threshold to trigger autoscroll when the OutputArea is resized,
916 * Threshold to trigger autoscroll when the OutputArea is resized,
917 * typically when new outputs are added.
917 * typically when new outputs are added.
918 *
918 *
919 * Behavior is undefined if autoscroll is lower than minimum_scroll_threshold,
919 * Behavior is undefined if autoscroll is lower than minimum_scroll_threshold,
920 * unless it is < 0, in which case autoscroll will never be triggered
920 * unless it is < 0, in which case autoscroll will never be triggered
921 *
921 *
922 * @property auto_scroll_threshold
922 * @property auto_scroll_threshold
923 * @type Number
923 * @type Number
924 * @default 100
924 * @default 100
925 *
925 *
926 **/
926 **/
927 OutputArea.auto_scroll_threshold = 100;
927 OutputArea.auto_scroll_threshold = 100;
928
928
929 /**
929 /**
930 * Lower limit (in lines) for OutputArea to be made scrollable. OutputAreas
930 * Lower limit (in lines) for OutputArea to be made scrollable. OutputAreas
931 * shorter than this are never scrolled.
931 * shorter than this are never scrolled.
932 *
932 *
933 * @property minimum_scroll_threshold
933 * @property minimum_scroll_threshold
934 * @type Number
934 * @type Number
935 * @default 20
935 * @default 20
936 *
936 *
937 **/
937 **/
938 OutputArea.minimum_scroll_threshold = 20;
938 OutputArea.minimum_scroll_threshold = 20;
939
939
940
940
941
941
942 OutputArea.mime_map = {
942 OutputArea.mime_map = {
943 "text/plain" : "text",
943 "text/plain" : "text",
944 "text/html" : "html",
944 "text/html" : "html",
945 "image/svg+xml" : "svg",
945 "image/svg+xml" : "svg",
946 "image/png" : "png",
946 "image/png" : "png",
947 "image/jpeg" : "jpeg",
947 "image/jpeg" : "jpeg",
948 "text/latex" : "latex",
948 "text/latex" : "latex",
949 "application/json" : "json",
949 "application/json" : "json",
950 "application/javascript" : "javascript",
950 "application/javascript" : "javascript",
951 };
951 };
952
952
953 OutputArea.mime_map_r = {
953 OutputArea.mime_map_r = {
954 "text" : "text/plain",
954 "text" : "text/plain",
955 "html" : "text/html",
955 "html" : "text/html",
956 "svg" : "image/svg+xml",
956 "svg" : "image/svg+xml",
957 "png" : "image/png",
957 "png" : "image/png",
958 "jpeg" : "image/jpeg",
958 "jpeg" : "image/jpeg",
959 "latex" : "text/latex",
959 "latex" : "text/latex",
960 "json" : "application/json",
960 "json" : "application/json",
961 "javascript" : "application/javascript",
961 "javascript" : "application/javascript",
962 };
962 };
963
963
964 OutputArea.display_order = [
964 OutputArea.display_order = [
965 'application/javascript',
965 'application/javascript',
966 'text/html',
966 'text/html',
967 'text/markdown',
967 'text/markdown',
968 'text/latex',
968 'text/latex',
969 'image/svg+xml',
969 'image/svg+xml',
970 'image/png',
970 'image/png',
971 'image/jpeg',
971 'image/jpeg',
972 'application/pdf',
972 'application/pdf',
973 'text/plain'
973 'text/plain'
974 ];
974 ];
975
975
976 OutputArea.append_map = {
976 OutputArea.append_map = {
977 "text/plain" : append_text,
977 "text/plain" : append_text,
978 "text/html" : append_html,
978 "text/html" : append_html,
979 "text/markdown": append_markdown,
979 "text/markdown": append_markdown,
980 "image/svg+xml" : append_svg,
980 "image/svg+xml" : append_svg,
981 "image/png" : append_png,
981 "image/png" : append_png,
982 "image/jpeg" : append_jpeg,
982 "image/jpeg" : append_jpeg,
983 "text/latex" : append_latex,
983 "text/latex" : append_latex,
984 "application/javascript" : append_javascript,
984 "application/javascript" : append_javascript,
985 "application/pdf" : append_pdf
985 "application/pdf" : append_pdf
986 };
986 };
987
987
988 // For backwards compatability.
988 // For backwards compatability.
989 IPython.OutputArea = OutputArea;
989 IPython.OutputArea = OutputArea;
990
990
991 return OutputArea;
991 return {'OutputArea': OutputArea};
992 });
992 });
@@ -1,178 +1,178 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 define([
4 define([
5 'base/js/namespace',
5 'base/js/namespace',
6 'jquery',
6 'jquery',
7 'base/js/utils',
7 'base/js/utils',
8 ], function(IPython, $, utils) {
8 ], function(IPython, $, utils) {
9 "use strict";
9 "use strict";
10
10
11 var Pager = function (pager_selector, pager_splitter_selector, layout_manager, events) {
11 var Pager = function (pager_selector, pager_splitter_selector, layout_manager, events) {
12 this.events = events;
12 this.events = events;
13 this.pager_element = $(pager_selector);
13 this.pager_element = $(pager_selector);
14 this.pager_button_area = $('#pager_button_area');
14 this.pager_button_area = $('#pager_button_area');
15 var that = this;
15 var that = this;
16 this.percentage_height = 0.40;
16 this.percentage_height = 0.40;
17 layout_manager.pager = this;
17 layout_manager.pager = this;
18 this.pager_splitter_element = $(pager_splitter_selector)
18 this.pager_splitter_element = $(pager_splitter_selector)
19 .draggable({
19 .draggable({
20 containment: 'window',
20 containment: 'window',
21 axis:'y',
21 axis:'y',
22 helper: null ,
22 helper: null ,
23 drag: function(event, ui) {
23 drag: function(event, ui) {
24 // recalculate the amount of space the pager should take
24 // recalculate the amount of space the pager should take
25 var pheight = ($(document.body).height()-event.clientY-4);
25 var pheight = ($(document.body).height()-event.clientY-4);
26 var downprct = pheight/layout_manager.app_height();
26 var downprct = pheight/layout_manager.app_height();
27 downprct = Math.min(0.9, downprct);
27 downprct = Math.min(0.9, downprct);
28 if (downprct < 0.1) {
28 if (downprct < 0.1) {
29 that.percentage_height = 0.1;
29 that.percentage_height = 0.1;
30 that.collapse({'duration':0});
30 that.collapse({'duration':0});
31 } else if (downprct > 0.2) {
31 } else if (downprct > 0.2) {
32 that.percentage_height = downprct;
32 that.percentage_height = downprct;
33 that.expand({'duration':0});
33 that.expand({'duration':0});
34 }
34 }
35 layout_manager.do_resize();
35 layout_manager.do_resize();
36 }
36 }
37 });
37 });
38 this.expanded = false;
38 this.expanded = false;
39 this.style();
39 this.style();
40 this.create_button_area();
40 this.create_button_area();
41 this.bind_events();
41 this.bind_events();
42 };
42 };
43
43
44 Pager.prototype.create_button_area = function(){
44 Pager.prototype.create_button_area = function(){
45 var that = this;
45 var that = this;
46 this.pager_button_area.append(
46 this.pager_button_area.append(
47 $('<a>').attr('role', "button")
47 $('<a>').attr('role', "button")
48 .attr('title',"Open the pager in an external window")
48 .attr('title',"Open the pager in an external window")
49 .addClass('ui-button')
49 .addClass('ui-button')
50 .click(function(){that.detach();})
50 .click(function(){that.detach();})
51 .attr('style','position: absolute; right: 20px;')
51 .attr('style','position: absolute; right: 20px;')
52 .append(
52 .append(
53 $('<span>').addClass("ui-icon ui-icon-extlink")
53 $('<span>').addClass("ui-icon ui-icon-extlink")
54 )
54 )
55 );
55 );
56 this.pager_button_area.append(
56 this.pager_button_area.append(
57 $('<a>').attr('role', "button")
57 $('<a>').attr('role', "button")
58 .attr('title',"Close the pager")
58 .attr('title',"Close the pager")
59 .addClass('ui-button')
59 .addClass('ui-button')
60 .click(function(){that.collapse();})
60 .click(function(){that.collapse();})
61 .attr('style','position: absolute; right: 5px;')
61 .attr('style','position: absolute; right: 5px;')
62 .append(
62 .append(
63 $('<span>').addClass("ui-icon ui-icon-close")
63 $('<span>').addClass("ui-icon ui-icon-close")
64 )
64 )
65 );
65 );
66 };
66 };
67
67
68 Pager.prototype.style = function () {
68 Pager.prototype.style = function () {
69 this.pager_splitter_element.addClass('border-box-sizing ui-widget ui-state-default');
69 this.pager_splitter_element.addClass('border-box-sizing ui-widget ui-state-default');
70 this.pager_element.addClass('border-box-sizing');
70 this.pager_element.addClass('border-box-sizing');
71 this.pager_element.find(".container").addClass('border-box-sizing');
71 this.pager_element.find(".container").addClass('border-box-sizing');
72 this.pager_splitter_element.attr('title', 'Click to Show/Hide pager area, drag to Resize');
72 this.pager_splitter_element.attr('title', 'Click to Show/Hide pager area, drag to Resize');
73 };
73 };
74
74
75
75
76 Pager.prototype.bind_events = function () {
76 Pager.prototype.bind_events = function () {
77 var that = this;
77 var that = this;
78
78
79 this.pager_element.bind('collapse_pager', function (event, extrap) {
79 this.pager_element.bind('collapse_pager', function (event, extrap) {
80 var time = 'fast';
80 var time = 'fast';
81 if (extrap && extrap.duration) {
81 if (extrap && extrap.duration) {
82 time = extrap.duration;
82 time = extrap.duration;
83 }
83 }
84 that.pager_element.hide(time);
84 that.pager_element.hide(time);
85 });
85 });
86
86
87 this.pager_element.bind('expand_pager', function (event, extrap) {
87 this.pager_element.bind('expand_pager', function (event, extrap) {
88 var time = 'fast';
88 var time = 'fast';
89 if (extrap && extrap.duration) {
89 if (extrap && extrap.duration) {
90 time = extrap.duration;
90 time = extrap.duration;
91 }
91 }
92 that.pager_element.show(time);
92 that.pager_element.show(time);
93 });
93 });
94
94
95 this.pager_splitter_element.hover(
95 this.pager_splitter_element.hover(
96 function () {
96 function () {
97 that.pager_splitter_element.addClass('ui-state-hover');
97 that.pager_splitter_element.addClass('ui-state-hover');
98 },
98 },
99 function () {
99 function () {
100 that.pager_splitter_element.removeClass('ui-state-hover');
100 that.pager_splitter_element.removeClass('ui-state-hover');
101 }
101 }
102 );
102 );
103
103
104 this.pager_splitter_element.click(function () {
104 this.pager_splitter_element.click(function () {
105 that.toggle();
105 that.toggle();
106 });
106 });
107
107
108 this.events.on('open_with_text.Pager', function (event, payload) {
108 this.events.on('open_with_text.Pager', function (event, payload) {
109 // FIXME: support other mime types
109 // FIXME: support other mime types
110 if (payload.data['text/plain'] && payload.data['text/plain'] !== "") {
110 if (payload.data['text/plain'] && payload.data['text/plain'] !== "") {
111 that.clear();
111 that.clear();
112 that.expand();
112 that.expand();
113 that.append_text(payload.data['text/plain']);
113 that.append_text(payload.data['text/plain']);
114 }
114 }
115 });
115 });
116 };
116 };
117
117
118
118
119 Pager.prototype.collapse = function (extrap) {
119 Pager.prototype.collapse = function (extrap) {
120 if (this.expanded === true) {
120 if (this.expanded === true) {
121 this.expanded = false;
121 this.expanded = false;
122 this.pager_element.add($('div#notebook')).trigger('collapse_pager', extrap);
122 this.pager_element.add($('div#notebook')).trigger('collapse_pager', extrap);
123 }
123 }
124 };
124 };
125
125
126
126
127 Pager.prototype.expand = function (extrap) {
127 Pager.prototype.expand = function (extrap) {
128 if (this.expanded !== true) {
128 if (this.expanded !== true) {
129 this.expanded = true;
129 this.expanded = true;
130 this.pager_element.add($('div#notebook')).trigger('expand_pager', extrap);
130 this.pager_element.add($('div#notebook')).trigger('expand_pager', extrap);
131 }
131 }
132 };
132 };
133
133
134
134
135 Pager.prototype.toggle = function () {
135 Pager.prototype.toggle = function () {
136 if (this.expanded === true) {
136 if (this.expanded === true) {
137 this.collapse();
137 this.collapse();
138 } else {
138 } else {
139 this.expand();
139 this.expand();
140 }
140 }
141 };
141 };
142
142
143
143
144 Pager.prototype.clear = function (text) {
144 Pager.prototype.clear = function (text) {
145 this.pager_element.find(".container").empty();
145 this.pager_element.find(".container").empty();
146 };
146 };
147
147
148 Pager.prototype.detach = function(){
148 Pager.prototype.detach = function(){
149 var w = window.open("","_blank");
149 var w = window.open("","_blank");
150 $(w.document.head)
150 $(w.document.head)
151 .append(
151 .append(
152 $('<link>')
152 $('<link>')
153 .attr('rel',"stylesheet")
153 .attr('rel',"stylesheet")
154 .attr('href',"/static/css/notebook.css")
154 .attr('href',"/static/css/notebook.css")
155 .attr('type',"text/css")
155 .attr('type',"text/css")
156 )
156 )
157 .append(
157 .append(
158 $('<title>').text("IPython Pager")
158 $('<title>').text("IPython Pager")
159 );
159 );
160 var pager_body = $(w.document.body);
160 var pager_body = $(w.document.body);
161 pager_body.css('overflow','scroll');
161 pager_body.css('overflow','scroll');
162
162
163 pager_body.append(this.pager_element.clone().children());
163 pager_body.append(this.pager_element.clone().children());
164 w.document.close();
164 w.document.close();
165 this.collapse();
165 this.collapse();
166 };
166 };
167
167
168 Pager.prototype.append_text = function (text) {
168 Pager.prototype.append_text = function (text) {
169 // The only user content injected with this HTML call is escaped by
169 // The only user content injected with this HTML call is escaped by
170 // the fixConsole() method.
170 // the fixConsole() method.
171 this.pager_element.find(".container").append($('<pre/>').html(utils.fixCarriageReturn(utils.fixConsole(text))));
171 this.pager_element.find(".container").append($('<pre/>').html(utils.fixCarriageReturn(utils.fixConsole(text))));
172 };
172 };
173
173
174 // Backwards compatability.
174 // Backwards compatability.
175 IPython.Pager = Pager;
175 IPython.Pager = Pager;
176
176
177 return Pager;
177 return {'Pager': Pager};
178 });
178 });
@@ -1,173 +1,173 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 define([
4 define([
5 'base/js/namespace',
5 'base/js/namespace',
6 'jquery',
6 'jquery',
7 'base/js/utils',
7 'base/js/utils',
8 'base/js/dialog',
8 'base/js/dialog',
9 ], function(IPython, $, utils, Dialog) {
9 ], function(IPython, $, utils, Dialog) {
10 "use strict";
10 "use strict";
11 var platform = utils.platform;
11 var platform = utils.platform;
12
12
13 var QuickHelp = function (selector, keyboard_manager, events) {
13 var QuickHelp = function (selector, keyboard_manager, events) {
14 this.keyboard_manager = keyboard_manager;
14 this.keyboard_manager = keyboard_manager;
15 keyboard_manager.quick_help = this;
15 keyboard_manager.quick_help = this;
16 this.events = events;
16 this.events = events;
17 };
17 };
18
18
19 var cmd_ctrl = 'Ctrl-';
19 var cmd_ctrl = 'Ctrl-';
20 var platform_specific;
20 var platform_specific;
21
21
22 if (platform === 'MacOS') {
22 if (platform === 'MacOS') {
23 // Mac OS X specific
23 // Mac OS X specific
24 cmd_ctrl = 'Cmd-';
24 cmd_ctrl = 'Cmd-';
25 platform_specific = [
25 platform_specific = [
26 { shortcut: "Cmd-Up", help:"go to cell start" },
26 { shortcut: "Cmd-Up", help:"go to cell start" },
27 { shortcut: "Cmd-Down", help:"go to cell end" },
27 { shortcut: "Cmd-Down", help:"go to cell end" },
28 { shortcut: "Opt-Left", help:"go one word left" },
28 { shortcut: "Opt-Left", help:"go one word left" },
29 { shortcut: "Opt-Right", help:"go one word right" },
29 { shortcut: "Opt-Right", help:"go one word right" },
30 { shortcut: "Opt-Backspace", help:"del word before" },
30 { shortcut: "Opt-Backspace", help:"del word before" },
31 { shortcut: "Opt-Delete", help:"del word after" },
31 { shortcut: "Opt-Delete", help:"del word after" },
32 ];
32 ];
33 } else {
33 } else {
34 // PC specific
34 // PC specific
35 platform_specific = [
35 platform_specific = [
36 { shortcut: "Ctrl-Home", help:"go to cell start" },
36 { shortcut: "Ctrl-Home", help:"go to cell start" },
37 { shortcut: "Ctrl-Up", help:"go to cell start" },
37 { shortcut: "Ctrl-Up", help:"go to cell start" },
38 { shortcut: "Ctrl-End", help:"go to cell end" },
38 { shortcut: "Ctrl-End", help:"go to cell end" },
39 { shortcut: "Ctrl-Down", help:"go to cell end" },
39 { shortcut: "Ctrl-Down", help:"go to cell end" },
40 { shortcut: "Ctrl-Left", help:"go one word left" },
40 { shortcut: "Ctrl-Left", help:"go one word left" },
41 { shortcut: "Ctrl-Right", help:"go one word right" },
41 { shortcut: "Ctrl-Right", help:"go one word right" },
42 { shortcut: "Ctrl-Backspace", help:"del word before" },
42 { shortcut: "Ctrl-Backspace", help:"del word before" },
43 { shortcut: "Ctrl-Delete", help:"del word after" },
43 { shortcut: "Ctrl-Delete", help:"del word after" },
44 ];
44 ];
45 }
45 }
46
46
47 var cm_shortcuts = [
47 var cm_shortcuts = [
48 { shortcut:"Tab", help:"code completion or indent" },
48 { shortcut:"Tab", help:"code completion or indent" },
49 { shortcut:"Shift-Tab", help:"tooltip" },
49 { shortcut:"Shift-Tab", help:"tooltip" },
50 { shortcut: cmd_ctrl + "]", help:"indent" },
50 { shortcut: cmd_ctrl + "]", help:"indent" },
51 { shortcut: cmd_ctrl + "[", help:"dedent" },
51 { shortcut: cmd_ctrl + "[", help:"dedent" },
52 { shortcut: cmd_ctrl + "a", help:"select all" },
52 { shortcut: cmd_ctrl + "a", help:"select all" },
53 { shortcut: cmd_ctrl + "z", help:"undo" },
53 { shortcut: cmd_ctrl + "z", help:"undo" },
54 { shortcut: cmd_ctrl + "Shift-z", help:"redo" },
54 { shortcut: cmd_ctrl + "Shift-z", help:"redo" },
55 { shortcut: cmd_ctrl + "y", help:"redo" },
55 { shortcut: cmd_ctrl + "y", help:"redo" },
56 ].concat( platform_specific );
56 ].concat( platform_specific );
57
57
58
58
59
59
60
60
61
61
62
62
63 QuickHelp.prototype.show_keyboard_shortcuts = function () {
63 QuickHelp.prototype.show_keyboard_shortcuts = function () {
64 // toggles display of keyboard shortcut dialog
64 // toggles display of keyboard shortcut dialog
65 var that = this;
65 var that = this;
66 if ( this.force_rebuild ) {
66 if ( this.force_rebuild ) {
67 this.shortcut_dialog.remove();
67 this.shortcut_dialog.remove();
68 delete(this.shortcut_dialog);
68 delete(this.shortcut_dialog);
69 this.force_rebuild = false;
69 this.force_rebuild = false;
70 }
70 }
71 if ( this.shortcut_dialog ){
71 if ( this.shortcut_dialog ){
72 // if dialog is already shown, close it
72 // if dialog is already shown, close it
73 $(this.shortcut_dialog).modal("toggle");
73 $(this.shortcut_dialog).modal("toggle");
74 return;
74 return;
75 }
75 }
76 var command_shortcuts = keyboard_manager.command_shortcuts.help();
76 var command_shortcuts = keyboard_manager.command_shortcuts.help();
77 var edit_shortcuts = keyboard_manager.edit_shortcuts.help();
77 var edit_shortcuts = keyboard_manager.edit_shortcuts.help();
78 var help, shortcut;
78 var help, shortcut;
79 var i, half, n;
79 var i, half, n;
80 var element = $('<div/>');
80 var element = $('<div/>');
81
81
82 // The documentation
82 // The documentation
83 var doc = $('<div/>').addClass('alert alert-warning');
83 var doc = $('<div/>').addClass('alert alert-warning');
84 doc.append(
84 doc.append(
85 $('<button/>').addClass('close').attr('data-dismiss','alert').html('&times;')
85 $('<button/>').addClass('close').attr('data-dismiss','alert').html('&times;')
86 ).append(
86 ).append(
87 'The IPython Notebook has two different keyboard input modes. <b>Edit mode</b> '+
87 'The IPython Notebook has two different keyboard input modes. <b>Edit mode</b> '+
88 'allows you to type code/text into a cell and is indicated by a green cell '+
88 'allows you to type code/text into a cell and is indicated by a green cell '+
89 'border. <b>Command mode</b> binds the keyboard to notebook level actions '+
89 'border. <b>Command mode</b> binds the keyboard to notebook level actions '+
90 'and is indicated by a grey cell border.'
90 'and is indicated by a grey cell border.'
91 );
91 );
92 element.append(doc);
92 element.append(doc);
93
93
94 // Command mode
94 // Command mode
95 var cmd_div = this.build_command_help();
95 var cmd_div = this.build_command_help();
96 element.append(cmd_div);
96 element.append(cmd_div);
97
97
98 // Edit mode
98 // Edit mode
99 var edit_div = this.build_edit_help(cm_shortcuts);
99 var edit_div = this.build_edit_help(cm_shortcuts);
100 element.append(edit_div);
100 element.append(edit_div);
101
101
102 this.shortcut_dialog = Dialog.modal({
102 this.shortcut_dialog = Dialog.modal({
103 title : "Keyboard shortcuts",
103 title : "Keyboard shortcuts",
104 body : element,
104 body : element,
105 destroy : false,
105 destroy : false,
106 buttons : {
106 buttons : {
107 Close : {}
107 Close : {}
108 }
108 }
109 });
109 });
110 this.shortcut_dialog.addClass("modal_stretch");
110 this.shortcut_dialog.addClass("modal_stretch");
111
111
112 this.events.on('rebuild.QuickHelp', function() { that.force_rebuild = true;});
112 this.events.on('rebuild.QuickHelp', function() { that.force_rebuild = true;});
113 };
113 };
114
114
115 QuickHelp.prototype.build_command_help = function () {
115 QuickHelp.prototype.build_command_help = function () {
116 var command_shortcuts = keyboard_manager.command_shortcuts.help();
116 var command_shortcuts = keyboard_manager.command_shortcuts.help();
117 return build_div('<h4>Command Mode (press <code>Esc</code> to enable)</h4>', command_shortcuts);
117 return build_div('<h4>Command Mode (press <code>Esc</code> to enable)</h4>', command_shortcuts);
118 };
118 };
119
119
120 var special_case = { pageup: "PageUp", pagedown: "Page Down", 'minus': '-' };
120 var special_case = { pageup: "PageUp", pagedown: "Page Down", 'minus': '-' };
121 var prettify = function (s) {
121 var prettify = function (s) {
122 s = s.replace(/-$/, 'minus'); // catch shortcuts using '-' key
122 s = s.replace(/-$/, 'minus'); // catch shortcuts using '-' key
123 var keys = s.split('-');
123 var keys = s.split('-');
124 var k, i;
124 var k, i;
125 for (i=0; i < keys.length; i++) {
125 for (i=0; i < keys.length; i++) {
126 k = keys[i];
126 k = keys[i];
127 if ( k.length == 1 ) {
127 if ( k.length == 1 ) {
128 keys[i] = "<code><strong>" + k + "</strong></code>";
128 keys[i] = "<code><strong>" + k + "</strong></code>";
129 continue; // leave individual keys lower-cased
129 continue; // leave individual keys lower-cased
130 }
130 }
131 keys[i] = ( special_case[k] ? special_case[k] : k.charAt(0).toUpperCase() + k.slice(1) );
131 keys[i] = ( special_case[k] ? special_case[k] : k.charAt(0).toUpperCase() + k.slice(1) );
132 keys[i] = "<code><strong>" + keys[i] + "</strong></code>";
132 keys[i] = "<code><strong>" + keys[i] + "</strong></code>";
133 }
133 }
134 return keys.join('-');
134 return keys.join('-');
135
135
136
136
137 };
137 };
138
138
139 QuickHelp.prototype.build_edit_help = function (cm_shortcuts) {
139 QuickHelp.prototype.build_edit_help = function (cm_shortcuts) {
140 var edit_shortcuts = keyboard_manager.edit_shortcuts.help();
140 var edit_shortcuts = keyboard_manager.edit_shortcuts.help();
141 jQuery.merge(cm_shortcuts, edit_shortcuts);
141 jQuery.merge(cm_shortcuts, edit_shortcuts);
142 return build_div('<h4>Edit Mode (press <code>Enter</code> to enable)</h4>', cm_shortcuts);
142 return build_div('<h4>Edit Mode (press <code>Enter</code> to enable)</h4>', cm_shortcuts);
143 };
143 };
144
144
145 var build_one = function (s) {
145 var build_one = function (s) {
146 var help = s.help;
146 var help = s.help;
147 var shortcut = prettify(s.shortcut);
147 var shortcut = prettify(s.shortcut);
148 return $('<div>').addClass('quickhelp').
148 return $('<div>').addClass('quickhelp').
149 append($('<span/>').addClass('shortcut_key').append($(shortcut))).
149 append($('<span/>').addClass('shortcut_key').append($(shortcut))).
150 append($('<span/>').addClass('shortcut_descr').text(' : ' + help));
150 append($('<span/>').addClass('shortcut_descr').text(' : ' + help));
151
151
152 };
152 };
153
153
154 var build_div = function (title, shortcuts) {
154 var build_div = function (title, shortcuts) {
155 var i, half, n;
155 var i, half, n;
156 var div = $('<div/>').append($(title));
156 var div = $('<div/>').append($(title));
157 var sub_div = $('<div/>').addClass('hbox');
157 var sub_div = $('<div/>').addClass('hbox');
158 var col1 = $('<div/>').addClass('box-flex1');
158 var col1 = $('<div/>').addClass('box-flex1');
159 var col2 = $('<div/>').addClass('box-flex1');
159 var col2 = $('<div/>').addClass('box-flex1');
160 n = shortcuts.length;
160 n = shortcuts.length;
161 half = ~~(n/2); // Truncate :)
161 half = ~~(n/2); // Truncate :)
162 for (i=0; i<half; i++) { col1.append( build_one(shortcuts[i]) ); }
162 for (i=0; i<half; i++) { col1.append( build_one(shortcuts[i]) ); }
163 for (i=half; i<n; i++) { col2.append( build_one(shortcuts[i]) ); }
163 for (i=half; i<n; i++) { col2.append( build_one(shortcuts[i]) ); }
164 sub_div.append(col1).append(col2);
164 sub_div.append(col1).append(col2);
165 div.append(sub_div);
165 div.append(sub_div);
166 return div;
166 return div;
167 };
167 };
168
168
169 // Backwards compatability.
169 // Backwards compatability.
170 IPython.QuickHelp = QuickHelp;
170 IPython.QuickHelp = QuickHelp;
171
171
172 return QuickHelp;
172 return {'QuickHelp': QuickHelp};
173 });
173 });
@@ -1,172 +1,172 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 define([
4 define([
5 'base/js/namespace',
5 'base/js/namespace',
6 'jquery',
6 'jquery',
7 'base/js/utils',
7 'base/js/utils',
8 'base/js/dialog',
8 'base/js/dialog',
9 'base/js/keyboard',
9 'base/js/keyboard',
10 'dateformat/date.format',
10 'dateformat/date.format',
11 ], function(IPython, $, utils, Dialog, keyboard) {
11 ], function(IPython, $, utils, Dialog, keyboard) {
12 "use strict";
12 "use strict";
13
13
14 var SaveWidget = function (selector, events) {
14 var SaveWidget = function (selector, events) {
15 this.notebook = undefined;
15 this.notebook = undefined;
16 this.selector = selector;
16 this.selector = selector;
17 this.events = events;
17 this.events = events;
18 if (this.selector !== undefined) {
18 if (this.selector !== undefined) {
19 this.element = $(selector);
19 this.element = $(selector);
20 this.style();
20 this.style();
21 this.bind_events();
21 this.bind_events();
22 }
22 }
23 };
23 };
24
24
25 SaveWidget.prototype.style = function () {
25 SaveWidget.prototype.style = function () {
26 };
26 };
27
27
28
28
29 SaveWidget.prototype.bind_events = function () {
29 SaveWidget.prototype.bind_events = function () {
30 var that = this;
30 var that = this;
31 this.element.find('span#notebook_name').click(function () {
31 this.element.find('span#notebook_name').click(function () {
32 that.rename_notebook();
32 that.rename_notebook();
33 });
33 });
34 this.element.find('span#notebook_name').hover(function () {
34 this.element.find('span#notebook_name').hover(function () {
35 $(this).addClass("ui-state-hover");
35 $(this).addClass("ui-state-hover");
36 }, function () {
36 }, function () {
37 $(this).removeClass("ui-state-hover");
37 $(this).removeClass("ui-state-hover");
38 });
38 });
39 this.events.on('notebook_loaded.Notebook', function () {
39 this.events.on('notebook_loaded.Notebook', function () {
40 that.update_notebook_name();
40 that.update_notebook_name();
41 that.update_document_title();
41 that.update_document_title();
42 });
42 });
43 this.events.on('notebook_saved.Notebook', function () {
43 this.events.on('notebook_saved.Notebook', function () {
44 that.update_notebook_name();
44 that.update_notebook_name();
45 that.update_document_title();
45 that.update_document_title();
46 });
46 });
47 this.events.on('notebook_renamed.Notebook', function () {
47 this.events.on('notebook_renamed.Notebook', function () {
48 that.update_notebook_name();
48 that.update_notebook_name();
49 that.update_document_title();
49 that.update_document_title();
50 that.update_address_bar();
50 that.update_address_bar();
51 });
51 });
52 this.events.on('notebook_save_failed.Notebook', function () {
52 this.events.on('notebook_save_failed.Notebook', function () {
53 that.set_save_status('Autosave Failed!');
53 that.set_save_status('Autosave Failed!');
54 });
54 });
55 this.events.on('checkpoints_listed.Notebook', function (event, data) {
55 this.events.on('checkpoints_listed.Notebook', function (event, data) {
56 that.set_last_checkpoint(data[0]);
56 that.set_last_checkpoint(data[0]);
57 });
57 });
58
58
59 this.events.on('checkpoint_created.Notebook', function (event, data) {
59 this.events.on('checkpoint_created.Notebook', function (event, data) {
60 that.set_last_checkpoint(data);
60 that.set_last_checkpoint(data);
61 });
61 });
62 this.events.on('set_dirty.Notebook', function (event, data) {
62 this.events.on('set_dirty.Notebook', function (event, data) {
63 that.set_autosaved(data.value);
63 that.set_autosaved(data.value);
64 });
64 });
65 };
65 };
66
66
67
67
68 SaveWidget.prototype.rename_notebook = function () {
68 SaveWidget.prototype.rename_notebook = function () {
69 var that = this;
69 var that = this;
70 var dialog = $('<div/>').append(
70 var dialog = $('<div/>').append(
71 $("<p/>").addClass("rename-message")
71 $("<p/>").addClass("rename-message")
72 .text('Enter a new notebook name:')
72 .text('Enter a new notebook name:')
73 ).append(
73 ).append(
74 $("<br/>")
74 $("<br/>")
75 ).append(
75 ).append(
76 $('<input/>').attr('type','text').attr('size','25').addClass('form-control')
76 $('<input/>').attr('type','text').attr('size','25').addClass('form-control')
77 .val(that.notebook.get_notebook_name())
77 .val(that.notebook.get_notebook_name())
78 );
78 );
79 Dialog.modal({
79 Dialog.modal({
80 title: "Rename Notebook",
80 title: "Rename Notebook",
81 body: dialog,
81 body: dialog,
82 buttons : {
82 buttons : {
83 "Cancel": {},
83 "Cancel": {},
84 "OK": {
84 "OK": {
85 class: "btn-primary",
85 class: "btn-primary",
86 click: function () {
86 click: function () {
87 var new_name = $(this).find('input').val();
87 var new_name = $(this).find('input').val();
88 if (!that.notebook.test_notebook_name(new_name)) {
88 if (!that.notebook.test_notebook_name(new_name)) {
89 $(this).find('.rename-message').text(
89 $(this).find('.rename-message').text(
90 "Invalid notebook name. Notebook names must "+
90 "Invalid notebook name. Notebook names must "+
91 "have 1 or more characters and can contain any characters " +
91 "have 1 or more characters and can contain any characters " +
92 "except :/\\. Please enter a new notebook name:"
92 "except :/\\. Please enter a new notebook name:"
93 );
93 );
94 return false;
94 return false;
95 } else {
95 } else {
96 that.notebook.rename(new_name);
96 that.notebook.rename(new_name);
97 }
97 }
98 }}
98 }}
99 },
99 },
100 open : function (event, ui) {
100 open : function (event, ui) {
101 var that = $(this);
101 var that = $(this);
102 // Upon ENTER, click the OK button.
102 // Upon ENTER, click the OK button.
103 that.find('input[type="text"]').keydown(function (event, ui) {
103 that.find('input[type="text"]').keydown(function (event, ui) {
104 if (event.which === that.keyboard.keycodes.enter) {
104 if (event.which === that.keyboard.keycodes.enter) {
105 that.find('.btn-primary').first().click();
105 that.find('.btn-primary').first().click();
106 return false;
106 return false;
107 }
107 }
108 });
108 });
109 that.find('input[type="text"]').focus().select();
109 that.find('input[type="text"]').focus().select();
110 }
110 }
111 });
111 });
112 };
112 };
113
113
114
114
115 SaveWidget.prototype.update_notebook_name = function () {
115 SaveWidget.prototype.update_notebook_name = function () {
116 var nbname = this.notebook.get_notebook_name();
116 var nbname = this.notebook.get_notebook_name();
117 this.element.find('span#notebook_name').text(nbname);
117 this.element.find('span#notebook_name').text(nbname);
118 };
118 };
119
119
120
120
121 SaveWidget.prototype.update_document_title = function () {
121 SaveWidget.prototype.update_document_title = function () {
122 var nbname = this.notebook.get_notebook_name();
122 var nbname = this.notebook.get_notebook_name();
123 document.title = nbname;
123 document.title = nbname;
124 };
124 };
125
125
126 SaveWidget.prototype.update_address_bar = function(){
126 SaveWidget.prototype.update_address_bar = function(){
127 var base_url = this.notebook.base_url;
127 var base_url = this.notebook.base_url;
128 var nbname = this.notebook.notebook_name;
128 var nbname = this.notebook.notebook_name;
129 var path = this.notebook.notebook_path;
129 var path = this.notebook.notebook_path;
130 var state = {path : path, name: nbname};
130 var state = {path : path, name: nbname};
131 window.history.replaceState(state, "", utils.url_join_encode(
131 window.history.replaceState(state, "", utils.url_join_encode(
132 base_url,
132 base_url,
133 "notebooks",
133 "notebooks",
134 path,
134 path,
135 nbname)
135 nbname)
136 );
136 );
137 };
137 };
138
138
139
139
140 SaveWidget.prototype.set_save_status = function (msg) {
140 SaveWidget.prototype.set_save_status = function (msg) {
141 this.element.find('span#autosave_status').text(msg);
141 this.element.find('span#autosave_status').text(msg);
142 };
142 };
143
143
144 SaveWidget.prototype.set_checkpoint_status = function (msg) {
144 SaveWidget.prototype.set_checkpoint_status = function (msg) {
145 this.element.find('span#checkpoint_status').text(msg);
145 this.element.find('span#checkpoint_status').text(msg);
146 };
146 };
147
147
148 SaveWidget.prototype.set_last_checkpoint = function (checkpoint) {
148 SaveWidget.prototype.set_last_checkpoint = function (checkpoint) {
149 if (!checkpoint) {
149 if (!checkpoint) {
150 this.set_checkpoint_status("");
150 this.set_checkpoint_status("");
151 return;
151 return;
152 }
152 }
153 var d = new Date(checkpoint.last_modified);
153 var d = new Date(checkpoint.last_modified);
154 this.set_checkpoint_status(
154 this.set_checkpoint_status(
155 "Last Checkpoint: " + d.format('mmm dd HH:MM')
155 "Last Checkpoint: " + d.format('mmm dd HH:MM')
156 );
156 );
157 };
157 };
158
158
159 SaveWidget.prototype.set_autosaved = function (dirty) {
159 SaveWidget.prototype.set_autosaved = function (dirty) {
160 if (dirty) {
160 if (dirty) {
161 this.set_save_status("(unsaved changes)");
161 this.set_save_status("(unsaved changes)");
162 } else {
162 } else {
163 this.set_save_status("(autosaved)");
163 this.set_save_status("(autosaved)");
164 }
164 }
165 };
165 };
166
166
167 // Backwards compatability.
167 // Backwards compatability.
168 IPython.SaveWidget = SaveWidget;
168 IPython.SaveWidget = SaveWidget;
169
169
170 return SaveWidget;
170 return {'SaveWidget': SaveWidget};
171
171
172 });
172 });
@@ -1,101 +1,101 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 define([
4 define([
5 'base/js/namespace',
5 'base/js/namespace',
6 'jquery',
6 'jquery',
7 ], function(IPython, $) {
7 ], function(IPython, $) {
8 "use strict";
8 "use strict";
9
9
10 /**
10 /**
11 * A generic toolbar on which one can add button
11 * A generic toolbar on which one can add button
12 * @class ToolBar
12 * @class ToolBar
13 * @constructor
13 * @constructor
14 * @param {Dom object} selector
14 * @param {Dom object} selector
15 */
15 */
16 var ToolBar = function (selector, layout_manager) {
16 var ToolBar = function (selector, layout_manager) {
17 this.selector = selector;
17 this.selector = selector;
18 this.layout_manager = layout_manager;
18 this.layout_manager = layout_manager;
19 if (this.selector !== undefined) {
19 if (this.selector !== undefined) {
20 this.element = $(selector);
20 this.element = $(selector);
21 this.style();
21 this.style();
22 }
22 }
23 };
23 };
24
24
25 /**
25 /**
26 * add a group of button into the current toolbar.
26 * add a group of button into the current toolbar.
27 *
27 *
28 *
28 *
29 * @example
29 * @example
30 *
30 *
31 * IPython.toolbar.add_buttons_group([
31 * IPython.toolbar.add_buttons_group([
32 * {
32 * {
33 * label:'my button',
33 * label:'my button',
34 * icon:'icon-hdd',
34 * icon:'icon-hdd',
35 * callback:function(){alert('hoho')},
35 * callback:function(){alert('hoho')},
36 * id : 'my_button_id', // this is optional
36 * id : 'my_button_id', // this is optional
37 * },
37 * },
38 * {
38 * {
39 * label:'my second button',
39 * label:'my second button',
40 * icon:'icon-play',
40 * icon:'icon-play',
41 * callback:function(){alert('be carefull I cut')}
41 * callback:function(){alert('be carefull I cut')}
42 * }
42 * }
43 * ],
43 * ],
44 * "my_button_group_id"
44 * "my_button_group_id"
45 * )
45 * )
46 *
46 *
47 * @method add_buttons_group
47 * @method add_buttons_group
48 * @param list {List}
48 * @param list {List}
49 * List of button of the group, with the following paramter for each :
49 * List of button of the group, with the following paramter for each :
50 * @param list.label {string} text to show on button hover
50 * @param list.label {string} text to show on button hover
51 * @param list.icon {string} icon to choose from [Font Awesome](http://fortawesome.github.io/Font-Awesome)
51 * @param list.icon {string} icon to choose from [Font Awesome](http://fortawesome.github.io/Font-Awesome)
52 * @param list.callback {function} function to be called on button click
52 * @param list.callback {function} function to be called on button click
53 * @param [list.id] {String} id to give to the button
53 * @param [list.id] {String} id to give to the button
54 * @param [group_id] {String} optionnal id to give to the group
54 * @param [group_id] {String} optionnal id to give to the group
55 *
55 *
56 */
56 */
57 ToolBar.prototype.add_buttons_group = function (list, group_id) {
57 ToolBar.prototype.add_buttons_group = function (list, group_id) {
58 var btn_group = $('<div/>').addClass("btn-group");
58 var btn_group = $('<div/>').addClass("btn-group");
59 if( group_id !== undefined ) {
59 if( group_id !== undefined ) {
60 btn_group.attr('id',group_id);
60 btn_group.attr('id',group_id);
61 }
61 }
62 var el;
62 var el;
63 for(var i=0; i < list.length; i++) {
63 for(var i=0; i < list.length; i++) {
64 el = list[i];
64 el = list[i];
65 var button = $('<button/>')
65 var button = $('<button/>')
66 .addClass('btn btn-default')
66 .addClass('btn btn-default')
67 .attr("title", el.label)
67 .attr("title", el.label)
68 .append(
68 .append(
69 $("<i/>").addClass(el.icon)
69 $("<i/>").addClass(el.icon)
70 );
70 );
71 var id = el.id;
71 var id = el.id;
72 if( id !== undefined )
72 if( id !== undefined )
73 button.attr('id',id);
73 button.attr('id',id);
74 var fun = el.callback;
74 var fun = el.callback;
75 button.click(fun);
75 button.click(fun);
76 btn_group.append(button);
76 btn_group.append(button);
77 }
77 }
78 $(this.selector).append(btn_group);
78 $(this.selector).append(btn_group);
79 };
79 };
80
80
81 ToolBar.prototype.style = function () {
81 ToolBar.prototype.style = function () {
82 this.element.addClass('border-box-sizing')
82 this.element.addClass('border-box-sizing')
83 .addClass('toolbar');
83 .addClass('toolbar');
84 };
84 };
85
85
86 /**
86 /**
87 * Show and hide toolbar
87 * Show and hide toolbar
88 * @method toggle
88 * @method toggle
89 */
89 */
90 ToolBar.prototype.toggle = function () {
90 ToolBar.prototype.toggle = function () {
91 this.element.toggle();
91 this.element.toggle();
92 if (this.layout_manager !== undefined) {
92 if (this.layout_manager !== undefined) {
93 this.layout_manager.do_resize();
93 this.layout_manager.do_resize();
94 }
94 }
95 };
95 };
96
96
97 // Backwards compatability.
97 // Backwards compatability.
98 IPython.ToolBar = ToolBar;
98 IPython.ToolBar = ToolBar;
99
99
100 return ToolBar;
100 return {'ToolBar': ToolBar};
101 });
101 });
@@ -1,335 +1,335 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 define([
4 define([
5 'base/js/namespace',
5 'base/js/namespace',
6 'jquery',
6 'jquery',
7 'base/js/utils',
7 'base/js/utils',
8 ], function(IPython, $, utils) {
8 ], function(IPython, $, utils) {
9 "use strict";
9 "use strict";
10
10
11 // tooltip constructor
11 // tooltip constructor
12 var Tooltip = function (events) {
12 var Tooltip = function (events) {
13 var that = this;
13 var that = this;
14 this.events = events;
14 this.events = events;
15 this.time_before_tooltip = 1200;
15 this.time_before_tooltip = 1200;
16
16
17 // handle to html
17 // handle to html
18 this.tooltip = $('#tooltip');
18 this.tooltip = $('#tooltip');
19 this._hidden = true;
19 this._hidden = true;
20
20
21 // variable for consecutive call
21 // variable for consecutive call
22 this._old_cell = null;
22 this._old_cell = null;
23 this._old_request = null;
23 this._old_request = null;
24 this._consecutive_counter = 0;
24 this._consecutive_counter = 0;
25
25
26 // 'sticky ?'
26 // 'sticky ?'
27 this._sticky = false;
27 this._sticky = false;
28
28
29 // display tooltip if the docstring is empty?
29 // display tooltip if the docstring is empty?
30 this._hide_if_no_docstring = false;
30 this._hide_if_no_docstring = false;
31
31
32 // contain the button in the upper right corner
32 // contain the button in the upper right corner
33 this.buttons = $('<div/>').addClass('tooltipbuttons');
33 this.buttons = $('<div/>').addClass('tooltipbuttons');
34
34
35 // will contain the docstring
35 // will contain the docstring
36 this.text = $('<div/>').addClass('tooltiptext').addClass('smalltooltip');
36 this.text = $('<div/>').addClass('tooltiptext').addClass('smalltooltip');
37
37
38 // build the buttons menu on the upper right
38 // build the buttons menu on the upper right
39 // expand the tooltip to see more
39 // expand the tooltip to see more
40 var expandlink = $('<a/>').attr('href', "#").addClass("ui-corner-all") //rounded corner
40 var expandlink = $('<a/>').attr('href', "#").addClass("ui-corner-all") //rounded corner
41 .attr('role', "button").attr('id', 'expanbutton').attr('title', 'Grow the tooltip vertically (press shift-tab twice)').click(function () {
41 .attr('role', "button").attr('id', 'expanbutton').attr('title', 'Grow the tooltip vertically (press shift-tab twice)').click(function () {
42 that.expand();
42 that.expand();
43 }).append(
43 }).append(
44 $('<span/>').text('Expand').addClass('ui-icon').addClass('ui-icon-plus'));
44 $('<span/>').text('Expand').addClass('ui-icon').addClass('ui-icon-plus'));
45
45
46 // open in pager
46 // open in pager
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)');
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 var morespan = $('<span/>').text('Open in Pager').addClass('ui-icon').addClass('ui-icon-arrowstop-l-n');
48 var morespan = $('<span/>').text('Open in Pager').addClass('ui-icon').addClass('ui-icon-arrowstop-l-n');
49 morelink.append(morespan);
49 morelink.append(morespan);
50 morelink.click(function () {
50 morelink.click(function () {
51 that.showInPager(that._old_cell);
51 that.showInPager(that._old_cell);
52 });
52 });
53
53
54 // close the tooltip
54 // close the tooltip
55 var closelink = $('<a/>').attr('href', "#").attr('role', "button").addClass('ui-button');
55 var closelink = $('<a/>').attr('href', "#").attr('role', "button").addClass('ui-button');
56 var closespan = $('<span/>').text('Close').addClass('ui-icon').addClass('ui-icon-close');
56 var closespan = $('<span/>').text('Close').addClass('ui-icon').addClass('ui-icon-close');
57 closelink.append(closespan);
57 closelink.append(closespan);
58 closelink.click(function () {
58 closelink.click(function () {
59 that.remove_and_cancel_tooltip(true);
59 that.remove_and_cancel_tooltip(true);
60 });
60 });
61
61
62 this._clocklink = $('<a/>').attr('href', "#");
62 this._clocklink = $('<a/>').attr('href', "#");
63 this._clocklink.attr('role', "button");
63 this._clocklink.attr('role', "button");
64 this._clocklink.addClass('ui-button');
64 this._clocklink.addClass('ui-button');
65 this._clocklink.attr('title', 'Tootip is not dismissed while typing for 10 seconds');
65 this._clocklink.attr('title', 'Tootip is not dismissed while typing for 10 seconds');
66 var clockspan = $('<span/>').text('Close');
66 var clockspan = $('<span/>').text('Close');
67 clockspan.addClass('ui-icon');
67 clockspan.addClass('ui-icon');
68 clockspan.addClass('ui-icon-clock');
68 clockspan.addClass('ui-icon-clock');
69 this._clocklink.append(clockspan);
69 this._clocklink.append(clockspan);
70 this._clocklink.click(function () {
70 this._clocklink.click(function () {
71 that.cancel_stick();
71 that.cancel_stick();
72 });
72 });
73
73
74
74
75
75
76
76
77 //construct the tooltip
77 //construct the tooltip
78 // add in the reverse order you want them to appear
78 // add in the reverse order you want them to appear
79 this.buttons.append(closelink);
79 this.buttons.append(closelink);
80 this.buttons.append(expandlink);
80 this.buttons.append(expandlink);
81 this.buttons.append(morelink);
81 this.buttons.append(morelink);
82 this.buttons.append(this._clocklink);
82 this.buttons.append(this._clocklink);
83 this._clocklink.hide();
83 this._clocklink.hide();
84
84
85
85
86 // we need a phony element to make the small arrow
86 // we need a phony element to make the small arrow
87 // of the tooltip in css
87 // of the tooltip in css
88 // we will move the arrow later
88 // we will move the arrow later
89 this.arrow = $('<div/>').addClass('pretooltiparrow');
89 this.arrow = $('<div/>').addClass('pretooltiparrow');
90 this.tooltip.append(this.buttons);
90 this.tooltip.append(this.buttons);
91 this.tooltip.append(this.arrow);
91 this.tooltip.append(this.arrow);
92 this.tooltip.append(this.text);
92 this.tooltip.append(this.text);
93
93
94 // function that will be called if you press tab 1, 2, 3... times in a row
94 // function that will be called if you press tab 1, 2, 3... times in a row
95 this.tabs_functions = [function (cell, text, cursor) {
95 this.tabs_functions = [function (cell, text, cursor) {
96 that._request_tooltip(cell, text, cursor);
96 that._request_tooltip(cell, text, cursor);
97 }, function () {
97 }, function () {
98 that.expand();
98 that.expand();
99 }, function () {
99 }, function () {
100 that.stick();
100 that.stick();
101 }, function (cell) {
101 }, function (cell) {
102 that.cancel_stick();
102 that.cancel_stick();
103 that.showInPager(cell);
103 that.showInPager(cell);
104 }];
104 }];
105 // call after all the tabs function above have bee call to clean their effects
105 // call after all the tabs function above have bee call to clean their effects
106 // if necessary
106 // if necessary
107 this.reset_tabs_function = function (cell, text) {
107 this.reset_tabs_function = function (cell, text) {
108 this._old_cell = (cell) ? cell : null;
108 this._old_cell = (cell) ? cell : null;
109 this._old_request = (text) ? text : null;
109 this._old_request = (text) ? text : null;
110 this._consecutive_counter = 0;
110 this._consecutive_counter = 0;
111 };
111 };
112 };
112 };
113
113
114 Tooltip.prototype.is_visible = function () {
114 Tooltip.prototype.is_visible = function () {
115 return !this._hidden;
115 return !this._hidden;
116 };
116 };
117
117
118 Tooltip.prototype.showInPager = function (cell) {
118 Tooltip.prototype.showInPager = function (cell) {
119 // reexecute last call in pager by appending ? to show back in pager
119 // reexecute last call in pager by appending ? to show back in pager
120 var that = this;
120 var that = this;
121 var payload = {};
121 var payload = {};
122 payload.text = that._reply.content.data['text/plain'];
122 payload.text = that._reply.content.data['text/plain'];
123
123
124 this.events.trigger('open_with_text.Pager', payload);
124 this.events.trigger('open_with_text.Pager', payload);
125 this.remove_and_cancel_tooltip();
125 this.remove_and_cancel_tooltip();
126 };
126 };
127
127
128 // grow the tooltip verticaly
128 // grow the tooltip verticaly
129 Tooltip.prototype.expand = function () {
129 Tooltip.prototype.expand = function () {
130 this.text.removeClass('smalltooltip');
130 this.text.removeClass('smalltooltip');
131 this.text.addClass('bigtooltip');
131 this.text.addClass('bigtooltip');
132 $('#expanbutton').hide('slow');
132 $('#expanbutton').hide('slow');
133 };
133 };
134
134
135 // deal with all the logic of hiding the tooltip
135 // deal with all the logic of hiding the tooltip
136 // and reset it's status
136 // and reset it's status
137 Tooltip.prototype._hide = function () {
137 Tooltip.prototype._hide = function () {
138 this._hidden = true;
138 this._hidden = true;
139 this.tooltip.fadeOut('fast');
139 this.tooltip.fadeOut('fast');
140 $('#expanbutton').show('slow');
140 $('#expanbutton').show('slow');
141 this.text.removeClass('bigtooltip');
141 this.text.removeClass('bigtooltip');
142 this.text.addClass('smalltooltip');
142 this.text.addClass('smalltooltip');
143 // keep scroll top to be sure to always see the first line
143 // keep scroll top to be sure to always see the first line
144 this.text.scrollTop(0);
144 this.text.scrollTop(0);
145 this.code_mirror = null;
145 this.code_mirror = null;
146 };
146 };
147
147
148 // return true on successfully removing a visible tooltip; otherwise return
148 // return true on successfully removing a visible tooltip; otherwise return
149 // false.
149 // false.
150 Tooltip.prototype.remove_and_cancel_tooltip = function (force) {
150 Tooltip.prototype.remove_and_cancel_tooltip = function (force) {
151 // note that we don't handle closing directly inside the calltip
151 // note that we don't handle closing directly inside the calltip
152 // as in the completer, because it is not focusable, so won't
152 // as in the completer, because it is not focusable, so won't
153 // get the event.
153 // get the event.
154 this.cancel_pending();
154 this.cancel_pending();
155 if (!this._hidden) {
155 if (!this._hidden) {
156 if (force || !this._sticky) {
156 if (force || !this._sticky) {
157 this.cancel_stick();
157 this.cancel_stick();
158 this._hide();
158 this._hide();
159 }
159 }
160 this.reset_tabs_function();
160 this.reset_tabs_function();
161 return true;
161 return true;
162 } else {
162 } else {
163 return false;
163 return false;
164 }
164 }
165 };
165 };
166
166
167 // cancel autocall done after '(' for example.
167 // cancel autocall done after '(' for example.
168 Tooltip.prototype.cancel_pending = function () {
168 Tooltip.prototype.cancel_pending = function () {
169 if (this._tooltip_timeout !== null) {
169 if (this._tooltip_timeout !== null) {
170 clearTimeout(this._tooltip_timeout);
170 clearTimeout(this._tooltip_timeout);
171 this._tooltip_timeout = null;
171 this._tooltip_timeout = null;
172 }
172 }
173 };
173 };
174
174
175 // will trigger tooltip after timeout
175 // will trigger tooltip after timeout
176 Tooltip.prototype.pending = function (cell, hide_if_no_docstring) {
176 Tooltip.prototype.pending = function (cell, hide_if_no_docstring) {
177 var that = this;
177 var that = this;
178 this._tooltip_timeout = setTimeout(function () {
178 this._tooltip_timeout = setTimeout(function () {
179 that.request(cell, hide_if_no_docstring);
179 that.request(cell, hide_if_no_docstring);
180 }, that.time_before_tooltip);
180 }, that.time_before_tooltip);
181 };
181 };
182
182
183 // easy access for julia monkey patching.
183 // easy access for julia monkey patching.
184 Tooltip.last_token_re = /[a-z_][0-9a-z._]*$/gi;
184 Tooltip.last_token_re = /[a-z_][0-9a-z._]*$/gi;
185
185
186 Tooltip.prototype.extract_oir_token = function(line){
186 Tooltip.prototype.extract_oir_token = function(line){
187 // use internally just to make the request to the kernel
187 // use internally just to make the request to the kernel
188 // Feel free to shorten this logic if you are better
188 // Feel free to shorten this logic if you are better
189 // than me in regEx
189 // than me in regEx
190 // basicaly you shoul be able to get xxx.xxx.xxx from
190 // basicaly you shoul be able to get xxx.xxx.xxx from
191 // something(range(10), kwarg=smth) ; xxx.xxx.xxx( firstarg, rand(234,23), kwarg1=2,
191 // something(range(10), kwarg=smth) ; xxx.xxx.xxx( firstarg, rand(234,23), kwarg1=2,
192 // remove everything between matchin bracket (need to iterate)
192 // remove everything between matchin bracket (need to iterate)
193 var matchBracket = /\([^\(\)]+\)/g;
193 var matchBracket = /\([^\(\)]+\)/g;
194 var endBracket = /\([^\(]*$/g;
194 var endBracket = /\([^\(]*$/g;
195 var oldline = line;
195 var oldline = line;
196
196
197 line = line.replace(matchBracket, "");
197 line = line.replace(matchBracket, "");
198 while (oldline != line) {
198 while (oldline != line) {
199 oldline = line;
199 oldline = line;
200 line = line.replace(matchBracket, "");
200 line = line.replace(matchBracket, "");
201 }
201 }
202 // remove everything after last open bracket
202 // remove everything after last open bracket
203 line = line.replace(endBracket, "");
203 line = line.replace(endBracket, "");
204 // reset the regex object
204 // reset the regex object
205 Tooltip.last_token_re.lastIndex = 0;
205 Tooltip.last_token_re.lastIndex = 0;
206 return Tooltip.last_token_re.exec(line);
206 return Tooltip.last_token_re.exec(line);
207 };
207 };
208
208
209 Tooltip.prototype._request_tooltip = function (cell, text, cursor_pos) {
209 Tooltip.prototype._request_tooltip = function (cell, text, cursor_pos) {
210 var callbacks = $.proxy(this._show, this);
210 var callbacks = $.proxy(this._show, this);
211 var msg_id = cell.kernel.inspect(text, cursor_pos, callbacks);
211 var msg_id = cell.kernel.inspect(text, cursor_pos, callbacks);
212 };
212 };
213
213
214 // make an imediate completion request
214 // make an imediate completion request
215 Tooltip.prototype.request = function (cell, hide_if_no_docstring) {
215 Tooltip.prototype.request = function (cell, hide_if_no_docstring) {
216 // request(codecell)
216 // request(codecell)
217 // Deal with extracting the text from the cell and counting
217 // Deal with extracting the text from the cell and counting
218 // call in a row
218 // call in a row
219 this.cancel_pending();
219 this.cancel_pending();
220 var editor = cell.code_mirror;
220 var editor = cell.code_mirror;
221 var cursor = editor.getCursor();
221 var cursor = editor.getCursor();
222 var cursor_pos = utils.to_absolute_cursor_pos(editor, cursor);
222 var cursor_pos = utils.to_absolute_cursor_pos(editor, cursor);
223 var text = cell.get_text();
223 var text = cell.get_text();
224
224
225 this._hide_if_no_docstring = hide_if_no_docstring;
225 this._hide_if_no_docstring = hide_if_no_docstring;
226
226
227 if(editor.somethingSelected()){
227 if(editor.somethingSelected()){
228 text = editor.getSelection();
228 text = editor.getSelection();
229 }
229 }
230
230
231 // need a permanent handel to code_mirror for future auto recall
231 // need a permanent handel to code_mirror for future auto recall
232 this.code_mirror = editor;
232 this.code_mirror = editor;
233
233
234 // now we treat the different number of keypress
234 // now we treat the different number of keypress
235 // first if same cell, same text, increment counter by 1
235 // first if same cell, same text, increment counter by 1
236 if (this._old_cell == cell && this._old_request == text && this._hidden === false) {
236 if (this._old_cell == cell && this._old_request == text && this._hidden === false) {
237 this._consecutive_counter++;
237 this._consecutive_counter++;
238 } else {
238 } else {
239 // else reset
239 // else reset
240 this.cancel_stick();
240 this.cancel_stick();
241 this.reset_tabs_function (cell, text);
241 this.reset_tabs_function (cell, text);
242 }
242 }
243
243
244 this.tabs_functions[this._consecutive_counter](cell, text, cursor_pos);
244 this.tabs_functions[this._consecutive_counter](cell, text, cursor_pos);
245
245
246 // then if we are at the end of list function, reset
246 // then if we are at the end of list function, reset
247 if (this._consecutive_counter == this.tabs_functions.length) {
247 if (this._consecutive_counter == this.tabs_functions.length) {
248 this.reset_tabs_function (cell, text, cursor);
248 this.reset_tabs_function (cell, text, cursor);
249 }
249 }
250
250
251 return;
251 return;
252 };
252 };
253
253
254 // cancel the option of having the tooltip to stick
254 // cancel the option of having the tooltip to stick
255 Tooltip.prototype.cancel_stick = function () {
255 Tooltip.prototype.cancel_stick = function () {
256 clearTimeout(this._stick_timeout);
256 clearTimeout(this._stick_timeout);
257 this._stick_timeout = null;
257 this._stick_timeout = null;
258 this._clocklink.hide('slow');
258 this._clocklink.hide('slow');
259 this._sticky = false;
259 this._sticky = false;
260 };
260 };
261
261
262 // put the tooltip in a sicky state for 10 seconds
262 // put the tooltip in a sicky state for 10 seconds
263 // it won't be removed by remove_and_cancell() unless you called with
263 // it won't be removed by remove_and_cancell() unless you called with
264 // the first parameter set to true.
264 // the first parameter set to true.
265 // remove_and_cancell_tooltip(true)
265 // remove_and_cancell_tooltip(true)
266 Tooltip.prototype.stick = function (time) {
266 Tooltip.prototype.stick = function (time) {
267 time = (time !== undefined) ? time : 10;
267 time = (time !== undefined) ? time : 10;
268 var that = this;
268 var that = this;
269 this._sticky = true;
269 this._sticky = true;
270 this._clocklink.show('slow');
270 this._clocklink.show('slow');
271 this._stick_timeout = setTimeout(function () {
271 this._stick_timeout = setTimeout(function () {
272 that._sticky = false;
272 that._sticky = false;
273 that._clocklink.hide('slow');
273 that._clocklink.hide('slow');
274 }, time * 1000);
274 }, time * 1000);
275 };
275 };
276
276
277 // should be called with the kernel reply to actually show the tooltip
277 // should be called with the kernel reply to actually show the tooltip
278 Tooltip.prototype._show = function (reply) {
278 Tooltip.prototype._show = function (reply) {
279 // move the bubble if it is not hidden
279 // move the bubble if it is not hidden
280 // otherwise fade it
280 // otherwise fade it
281 this._reply = reply;
281 this._reply = reply;
282 var content = reply.content;
282 var content = reply.content;
283 if (!content.found) {
283 if (!content.found) {
284 // object not found, nothing to show
284 // object not found, nothing to show
285 return;
285 return;
286 }
286 }
287 this.name = content.name;
287 this.name = content.name;
288
288
289 // do some math to have the tooltip arrow on more or less on left or right
289 // do some math to have the tooltip arrow on more or less on left or right
290 // width of the editor
290 // width of the editor
291 var w = $(this.code_mirror.getScrollerElement()).width();
291 var w = $(this.code_mirror.getScrollerElement()).width();
292 // ofset of the editor
292 // ofset of the editor
293 var o = $(this.code_mirror.getScrollerElement()).offset();
293 var o = $(this.code_mirror.getScrollerElement()).offset();
294
294
295 // whatever anchor/head order but arrow at mid x selection
295 // whatever anchor/head order but arrow at mid x selection
296 var anchor = this.code_mirror.cursorCoords(false);
296 var anchor = this.code_mirror.cursorCoords(false);
297 var head = this.code_mirror.cursorCoords(true);
297 var head = this.code_mirror.cursorCoords(true);
298 var xinit = (head.left+anchor.left)/2;
298 var xinit = (head.left+anchor.left)/2;
299 var xinter = o.left + (xinit - o.left) / w * (w - 450);
299 var xinter = o.left + (xinit - o.left) / w * (w - 450);
300 var posarrowleft = xinit - xinter;
300 var posarrowleft = xinit - xinter;
301
301
302 if (this._hidden === false) {
302 if (this._hidden === false) {
303 this.tooltip.animate({
303 this.tooltip.animate({
304 'left': xinter - 30 + 'px',
304 'left': xinter - 30 + 'px',
305 'top': (head.bottom + 10) + 'px'
305 'top': (head.bottom + 10) + 'px'
306 });
306 });
307 } else {
307 } else {
308 this.tooltip.css({
308 this.tooltip.css({
309 'left': xinter - 30 + 'px'
309 'left': xinter - 30 + 'px'
310 });
310 });
311 this.tooltip.css({
311 this.tooltip.css({
312 'top': (head.bottom + 10) + 'px'
312 'top': (head.bottom + 10) + 'px'
313 });
313 });
314 }
314 }
315 this.arrow.animate({
315 this.arrow.animate({
316 'left': posarrowleft + 'px'
316 'left': posarrowleft + 'px'
317 });
317 });
318
318
319 this._hidden = false;
319 this._hidden = false;
320 this.tooltip.fadeIn('fast');
320 this.tooltip.fadeIn('fast');
321 this.text.children().remove();
321 this.text.children().remove();
322
322
323 // This should support rich data types, but only text/plain for now
323 // This should support rich data types, but only text/plain for now
324 // Any HTML within the docstring is escaped by the fixConsole() method.
324 // Any HTML within the docstring is escaped by the fixConsole() method.
325 var pre = $('<pre/>').html(utils.fixConsole(content.data['text/plain']));
325 var pre = $('<pre/>').html(utils.fixConsole(content.data['text/plain']));
326 this.text.append(pre);
326 this.text.append(pre);
327 // keep scroll top to be sure to always see the first line
327 // keep scroll top to be sure to always see the first line
328 this.text.scrollTop(0);
328 this.text.scrollTop(0);
329 };
329 };
330
330
331 // Backwards compatability.
331 // Backwards compatability.
332 IPython.Tooltip = Tooltip;
332 IPython.Tooltip = Tooltip;
333
333
334 return Tooltip;
334 return {'Tooltip': Tooltip};
335 });
335 });
@@ -1,170 +1,170 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 define([
4 define([
5 'base/js/namespace',
5 'base/js/namespace',
6 'jquery',
6 'jquery',
7 ], function(IPython, $) {
7 ], function(IPython, $) {
8 "use strict";
8 "use strict";
9
9
10 var tour_style = "<div class='popover tour'>\n" +
10 var tour_style = "<div class='popover tour'>\n" +
11 "<div class='arrow'></div>\n" +
11 "<div class='arrow'></div>\n" +
12 "<div style='position:absolute; top:7px; right:7px'>\n" +
12 "<div style='position:absolute; top:7px; right:7px'>\n" +
13 "<button class='btn btn-default btn-sm icon-remove' data-role='end'></button>\n" +
13 "<button class='btn btn-default btn-sm icon-remove' data-role='end'></button>\n" +
14 "</div><h3 class='popover-title'></h3>\n" +
14 "</div><h3 class='popover-title'></h3>\n" +
15 "<div class='popover-content'></div>\n" +
15 "<div class='popover-content'></div>\n" +
16 "<div class='popover-navigation'>\n" +
16 "<div class='popover-navigation'>\n" +
17 "<button class='btn btn-default icon-step-backward' data-role='prev'></button>\n" +
17 "<button class='btn btn-default icon-step-backward' data-role='prev'></button>\n" +
18 "<button class='btn btn-default icon-step-forward pull-right' data-role='next'></button>\n" +
18 "<button class='btn btn-default icon-step-forward pull-right' data-role='next'></button>\n" +
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" +
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 "</div>\n" +
20 "</div>\n" +
21 "</div>";
21 "</div>";
22
22
23 var NotebookTour = function (notebook, events) {
23 var NotebookTour = function (notebook, events) {
24 var that = this;
24 var that = this;
25 this.step_duration = 0;
25 this.step_duration = 0;
26 this.tour_steps = [
26 this.tour_steps = [
27 {
27 {
28 title: "Welcome to the Notebook Tour",
28 title: "Welcome to the Notebook Tour",
29 placement: 'bottom',
29 placement: 'bottom',
30 orphan: true,
30 orphan: true,
31 content: "You can use the left and right arrow keys to go backwards and forwards.",
31 content: "You can use the left and right arrow keys to go backwards and forwards.",
32 }, {
32 }, {
33 element: "#notebook_name",
33 element: "#notebook_name",
34 title: "Filename",
34 title: "Filename",
35 placement: 'bottom',
35 placement: 'bottom',
36 content: "Click here to change the filename for this notebook."
36 content: "Click here to change the filename for this notebook."
37 }, {
37 }, {
38 element: $("#menus").parent(),
38 element: $("#menus").parent(),
39 placement: 'bottom',
39 placement: 'bottom',
40 backdrop: true,
40 backdrop: true,
41 title: "Notebook Menubar",
41 title: "Notebook Menubar",
42 content: "The menubar has menus for actions on the notebook, its cells, and the kernel it communicates with."
42 content: "The menubar has menus for actions on the notebook, its cells, and the kernel it communicates with."
43 }, {
43 }, {
44 element: "#maintoolbar",
44 element: "#maintoolbar",
45 placement: 'bottom',
45 placement: 'bottom',
46 backdrop: true,
46 backdrop: true,
47 title: "Notebook Toolbar",
47 title: "Notebook Toolbar",
48 content: "The toolbar has buttons for the most common actions. Hover your mouse over each button for more information."
48 content: "The toolbar has buttons for the most common actions. Hover your mouse over each button for more information."
49 }, {
49 }, {
50 element: "#modal_indicator",
50 element: "#modal_indicator",
51 title: "Mode Indicator",
51 title: "Mode Indicator",
52 placement: 'bottom',
52 placement: 'bottom',
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.",
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 onShow: function(tour) { command_icon_hack(); }
54 onShow: function(tour) { command_icon_hack(); }
55 }, {
55 }, {
56 element: "#modal_indicator",
56 element: "#modal_indicator",
57 title: "Command Mode",
57 title: "Command Mode",
58 placement: 'bottom',
58 placement: 'bottom',
59 onShow: function(tour) { that.notebook.command_mode(); command_icon_hack(); },
59 onShow: function(tour) { that.notebook.command_mode(); command_icon_hack(); },
60 onNext: function(tour) { edit_mode(); },
60 onNext: function(tour) { edit_mode(); },
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."
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 element: "#modal_indicator",
63 element: "#modal_indicator",
64 title: "Edit Mode",
64 title: "Edit Mode",
65 placement: 'bottom',
65 placement: 'bottom',
66 onShow: function(tour) { edit_mode(); },
66 onShow: function(tour) { edit_mode(); },
67 content: "Pressing <code>Enter</code> or clicking in the input text area of the cell switches to Edit Mode."
67 content: "Pressing <code>Enter</code> or clicking in the input text area of the cell switches to Edit Mode."
68 }, {
68 }, {
69 element: '.selected',
69 element: '.selected',
70 title: "Edit Mode",
70 title: "Edit Mode",
71 placement: 'bottom',
71 placement: 'bottom',
72 onShow: function(tour) { edit_mode(); },
72 onShow: function(tour) { edit_mode(); },
73 content: "Notice that the border around the currently active cell changed color. Typing will insert text into the currently active cell."
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 element: '.selected',
75 element: '.selected',
76 title: "Back to Command Mode",
76 title: "Back to Command Mode",
77 placement: 'bottom',
77 placement: 'bottom',
78 onShow: function(tour) { that.notebook.command_mode(); },
78 onShow: function(tour) { that.notebook.command_mode(); },
79 onHide: function(tour) { $('#help_menu').parent().children('a').click(); },
79 onHide: function(tour) { $('#help_menu').parent().children('a').click(); },
80 content: "Pressing <code>Esc</code> or clicking outside of the input text area takes you back to Command Mode."
80 content: "Pressing <code>Esc</code> or clicking outside of the input text area takes you back to Command Mode."
81 }, {
81 }, {
82 element: '#keyboard_shortcuts',
82 element: '#keyboard_shortcuts',
83 title: "Keyboard Shortcuts",
83 title: "Keyboard Shortcuts",
84 placement: 'bottom',
84 placement: 'bottom',
85 onHide: function(tour) { $('#help_menu').parent().children('a').click(); },
85 onHide: function(tour) { $('#help_menu').parent().children('a').click(); },
86 content: "You can click here to get a list of all of the keyboard shortcuts."
86 content: "You can click here to get a list of all of the keyboard shortcuts."
87 }, {
87 }, {
88 element: "#kernel_indicator",
88 element: "#kernel_indicator",
89 title: "Kernel Indicator",
89 title: "Kernel Indicator",
90 placement: 'bottom',
90 placement: 'bottom',
91 onShow: function(tour) { events.trigger('status_idle.Kernel');},
91 onShow: function(tour) { events.trigger('status_idle.Kernel');},
92 content: "This is the Kernel indicator. It looks like this when the Kernel is idle.",
92 content: "This is the Kernel indicator. It looks like this when the Kernel is idle.",
93 }, {
93 }, {
94 element: "#kernel_indicator",
94 element: "#kernel_indicator",
95 title: "Kernel Indicator",
95 title: "Kernel Indicator",
96 placement: 'bottom',
96 placement: 'bottom',
97 onShow: function(tour) { events.trigger('status_busy.Kernel'); },
97 onShow: function(tour) { events.trigger('status_busy.Kernel'); },
98 content: "The Kernel indicator looks like this when the Kernel is busy.",
98 content: "The Kernel indicator looks like this when the Kernel is busy.",
99 }, {
99 }, {
100 element: ".icon-stop",
100 element: ".icon-stop",
101 placement: 'bottom',
101 placement: 'bottom',
102 title: "Interrupting the Kernel",
102 title: "Interrupting the Kernel",
103 onHide: function(tour) { events.trigger('status_idle.Kernel'); },
103 onHide: function(tour) { events.trigger('status_idle.Kernel'); },
104 content: "To cancel a computation in progress, you can click here."
104 content: "To cancel a computation in progress, you can click here."
105 }, {
105 }, {
106 element: "#notification_kernel",
106 element: "#notification_kernel",
107 placement: 'bottom',
107 placement: 'bottom',
108 onShow: function(tour) { $('.icon-stop').click(); },
108 onShow: function(tour) { $('.icon-stop').click(); },
109 title: "Notification Area",
109 title: "Notification Area",
110 content: "Messages in response to user actions (Save, Interrupt, etc) appear here."
110 content: "Messages in response to user actions (Save, Interrupt, etc) appear here."
111 }, {
111 }, {
112 title: "Fin.",
112 title: "Fin.",
113 placement: 'bottom',
113 placement: 'bottom',
114 orphan: true,
114 orphan: true,
115 content: "This concludes the IPython Notebook User Interface Tour. Happy hacking!",
115 content: "This concludes the IPython Notebook User Interface Tour. Happy hacking!",
116 }
116 }
117 ];
117 ];
118
118
119 this.tour = new Tour({
119 this.tour = new Tour({
120 //orphan: true,
120 //orphan: true,
121 storage: false, // start tour from beginning every time
121 storage: false, // start tour from beginning every time
122 //element: $("#ipython_notebook"),
122 //element: $("#ipython_notebook"),
123 debug: true,
123 debug: true,
124 reflex: true, // click on element to continue tour
124 reflex: true, // click on element to continue tour
125 //backdrop: true, // show dark behind popover
125 //backdrop: true, // show dark behind popover
126 animation: false,
126 animation: false,
127 duration: this.step_duration,
127 duration: this.step_duration,
128 onStart: function() { console.log('tour started'); },
128 onStart: function() { console.log('tour started'); },
129 // TODO: remove the onPause/onResume logic once pi's patch has been
129 // TODO: remove the onPause/onResume logic once pi's patch has been
130 // merged upstream to make this work via data-resume-class and
130 // merged upstream to make this work via data-resume-class and
131 // data-resume-text attributes.
131 // data-resume-text attributes.
132 onPause: this.toggle_pause_play,
132 onPause: this.toggle_pause_play,
133 onResume: this.toggle_pause_play,
133 onResume: this.toggle_pause_play,
134 steps: this.tour_steps,
134 steps: this.tour_steps,
135 template: tour_style,
135 template: tour_style,
136 orphan: true
136 orphan: true
137 });
137 });
138
138
139 };
139 };
140
140
141 NotebookTour.prototype.start = function () {
141 NotebookTour.prototype.start = function () {
142 console.log("let's start the tour");
142 console.log("let's start the tour");
143 this.tour.init();
143 this.tour.init();
144 this.tour.start();
144 this.tour.start();
145 if (this.tour.ended())
145 if (this.tour.ended())
146 {
146 {
147 this.tour.restart();
147 this.tour.restart();
148 }
148 }
149 };
149 };
150
150
151 NotebookTour.prototype.command_icon_hack = function() {
151 NotebookTour.prototype.command_icon_hack = function() {
152 $('#modal_indicator').css('min-height', 20);
152 $('#modal_indicator').css('min-height', 20);
153 };
153 };
154
154
155 NotebookTour.prototype.toggle_pause_play = function () {
155 NotebookTour.prototype.toggle_pause_play = function () {
156 $('#tour-pause').toggleClass('icon-pause icon-play');
156 $('#tour-pause').toggleClass('icon-pause icon-play');
157 };
157 };
158
158
159 NotebookTour.prototype.edit_mode = function() {
159 NotebookTour.prototype.edit_mode = function() {
160 this.notebook.focus_cell();
160 this.notebook.focus_cell();
161 this.notebook.edit_mode();
161 this.notebook.edit_mode();
162 };
162 };
163
163
164 // For backwards compatability.
164 // For backwards compatability.
165 IPython.NotebookTour = NotebookTour;
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 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 define([
4 define([
5 'base/js/namespace',
5 'base/js/namespace',
6 'jquery',
6 'jquery',
7 'base/js/utils',
7 'base/js/utils',
8 'services/kernels/js/comm',
8 'services/kernels/js/comm',
9 'widgets/js/init',
9 'widgets/js/init',
10 ], function(IPython, $, utils, comm, WidgetManager) {
10 ], function(IPython, $, utils, comm, WidgetManager) {
11 "use strict";
11 "use strict";
12
12
13 // Initialization and connection.
13 // Initialization and connection.
14 /**
14 /**
15 * A Kernel Class to communicate with the Python kernel
15 * A Kernel Class to communicate with the Python kernel
16 * @Class Kernel
16 * @Class Kernel
17 */
17 */
18 var Kernel = function (kernel_service_url, notebook) {
18 var Kernel = function (kernel_service_url, notebook) {
19 this.events = notebook.events;
19 this.events = notebook.events;
20 this.kernel_id = null;
20 this.kernel_id = null;
21 this.shell_channel = null;
21 this.shell_channel = null;
22 this.iopub_channel = null;
22 this.iopub_channel = null;
23 this.stdin_channel = null;
23 this.stdin_channel = null;
24 this.kernel_service_url = kernel_service_url;
24 this.kernel_service_url = kernel_service_url;
25 this.running = false;
25 this.running = false;
26 this.username = "username";
26 this.username = "username";
27 this.session_id = utils.uuid();
27 this.session_id = utils.uuid();
28 this._msg_callbacks = {};
28 this._msg_callbacks = {};
29 this.post = $.post;
29 this.post = $.post;
30
30
31 if (typeof(WebSocket) !== 'undefined') {
31 if (typeof(WebSocket) !== 'undefined') {
32 this.WebSocket = WebSocket;
32 this.WebSocket = WebSocket;
33 } else if (typeof(MozWebSocket) !== 'undefined') {
33 } else if (typeof(MozWebSocket) !== 'undefined') {
34 this.WebSocket = MozWebSocket;
34 this.WebSocket = MozWebSocket;
35 } else {
35 } else {
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.');
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 this.bind_events();
39 this.bind_events();
40 this.init_iopub_handlers();
40 this.init_iopub_handlers();
41 this.comm_manager = new comm.CommManager(this);
41 this.comm_manager = new comm.CommManager(this);
42 this.widget_manager = new WidgetManager(this.comm_manager, notebook);
42 this.widget_manager = new WidgetManager(this.comm_manager, notebook);
43
43
44 this.last_msg_id = null;
44 this.last_msg_id = null;
45 this.last_msg_callbacks = {};
45 this.last_msg_callbacks = {};
46 };
46 };
47
47
48
48
49 Kernel.prototype._get_msg = function (msg_type, content, metadata) {
49 Kernel.prototype._get_msg = function (msg_type, content, metadata) {
50 var msg = {
50 var msg = {
51 header : {
51 header : {
52 msg_id : utils.uuid(),
52 msg_id : utils.uuid(),
53 username : this.username,
53 username : this.username,
54 session : this.session_id,
54 session : this.session_id,
55 msg_type : msg_type,
55 msg_type : msg_type,
56 version : "5.0"
56 version : "5.0"
57 },
57 },
58 metadata : metadata || {},
58 metadata : metadata || {},
59 content : content,
59 content : content,
60 parent_header : {}
60 parent_header : {}
61 };
61 };
62 return msg;
62 return msg;
63 };
63 };
64
64
65 Kernel.prototype.bind_events = function () {
65 Kernel.prototype.bind_events = function () {
66 var that = this;
66 var that = this;
67 this.events.on('send_input_reply.Kernel', function(evt, data) {
67 this.events.on('send_input_reply.Kernel', function(evt, data) {
68 that.send_input_reply(data);
68 that.send_input_reply(data);
69 });
69 });
70 };
70 };
71
71
72 // Initialize the iopub handlers
72 // Initialize the iopub handlers
73
73
74 Kernel.prototype.init_iopub_handlers = function () {
74 Kernel.prototype.init_iopub_handlers = function () {
75 var output_msg_types = ['stream', 'display_data', 'execute_result', 'error'];
75 var output_msg_types = ['stream', 'display_data', 'execute_result', 'error'];
76 this._iopub_handlers = {};
76 this._iopub_handlers = {};
77 this.register_iopub_handler('status', $.proxy(this._handle_status_message, this));
77 this.register_iopub_handler('status', $.proxy(this._handle_status_message, this));
78 this.register_iopub_handler('clear_output', $.proxy(this._handle_clear_output, this));
78 this.register_iopub_handler('clear_output', $.proxy(this._handle_clear_output, this));
79
79
80 for (var i=0; i < output_msg_types.length; i++) {
80 for (var i=0; i < output_msg_types.length; i++) {
81 this.register_iopub_handler(output_msg_types[i], $.proxy(this._handle_output_message, this));
81 this.register_iopub_handler(output_msg_types[i], $.proxy(this._handle_output_message, this));
82 }
82 }
83 };
83 };
84
84
85 /**
85 /**
86 * Start the Python kernel
86 * Start the Python kernel
87 * @method start
87 * @method start
88 */
88 */
89 Kernel.prototype.start = function (params) {
89 Kernel.prototype.start = function (params) {
90 params = params || {};
90 params = params || {};
91 if (!this.running) {
91 if (!this.running) {
92 var qs = $.param(params);
92 var qs = $.param(params);
93 this.post(utils.url_join_encode(this.kernel_service_url) + '?' + qs,
93 this.post(utils.url_join_encode(this.kernel_service_url) + '?' + qs,
94 $.proxy(this._kernel_started, this),
94 $.proxy(this._kernel_started, this),
95 'json'
95 'json'
96 );
96 );
97 }
97 }
98 };
98 };
99
99
100 /**
100 /**
101 * Restart the python kernel.
101 * Restart the python kernel.
102 *
102 *
103 * Emit a 'status_restarting.Kernel' event with
103 * Emit a 'status_restarting.Kernel' event with
104 * the current object as parameter
104 * the current object as parameter
105 *
105 *
106 * @method restart
106 * @method restart
107 */
107 */
108 Kernel.prototype.restart = function () {
108 Kernel.prototype.restart = function () {
109 this.events.trigger('status_restarting.Kernel', {kernel: this});
109 this.events.trigger('status_restarting.Kernel', {kernel: this});
110 if (this.running) {
110 if (this.running) {
111 this.stop_channels();
111 this.stop_channels();
112 this.post(utils.url_join_encode(this.kernel_url, "restart"),
112 this.post(utils.url_join_encode(this.kernel_url, "restart"),
113 $.proxy(this._kernel_started, this),
113 $.proxy(this._kernel_started, this),
114 'json'
114 'json'
115 );
115 );
116 }
116 }
117 };
117 };
118
118
119
119
120 Kernel.prototype._kernel_started = function (json) {
120 Kernel.prototype._kernel_started = function (json) {
121 console.log("Kernel started: ", json.id);
121 console.log("Kernel started: ", json.id);
122 this.running = true;
122 this.running = true;
123 this.kernel_id = json.id;
123 this.kernel_id = json.id;
124 // trailing 's' in https will become wss for secure web sockets
124 // trailing 's' in https will become wss for secure web sockets
125 this.ws_host = location.protocol.replace('http', 'ws') + "//" + location.host;
125 this.ws_host = location.protocol.replace('http', 'ws') + "//" + location.host;
126 this.kernel_url = utils.url_path_join(this.kernel_service_url, this.kernel_id);
126 this.kernel_url = utils.url_path_join(this.kernel_service_url, this.kernel_id);
127 this.start_channels();
127 this.start_channels();
128 };
128 };
129
129
130
130
131 Kernel.prototype._websocket_closed = function(ws_url, early) {
131 Kernel.prototype._websocket_closed = function(ws_url, early) {
132 this.stop_channels();
132 this.stop_channels();
133 this.events.trigger('websocket_closed.Kernel',
133 this.events.trigger('websocket_closed.Kernel',
134 {ws_url: ws_url, kernel: this, early: early}
134 {ws_url: ws_url, kernel: this, early: early}
135 );
135 );
136 };
136 };
137
137
138 /**
138 /**
139 * Start the `shell`and `iopub` channels.
139 * Start the `shell`and `iopub` channels.
140 * Will stop and restart them if they already exist.
140 * Will stop and restart them if they already exist.
141 *
141 *
142 * @method start_channels
142 * @method start_channels
143 */
143 */
144 Kernel.prototype.start_channels = function () {
144 Kernel.prototype.start_channels = function () {
145 var that = this;
145 var that = this;
146 this.stop_channels();
146 this.stop_channels();
147 var ws_host_url = this.ws_host + this.kernel_url;
147 var ws_host_url = this.ws_host + this.kernel_url;
148 console.log("Starting WebSockets:", ws_host_url);
148 console.log("Starting WebSockets:", ws_host_url);
149 this.shell_channel = new this.WebSocket(
149 this.shell_channel = new this.WebSocket(
150 this.ws_host + utils.url_join_encode(this.kernel_url, "shell")
150 this.ws_host + utils.url_join_encode(this.kernel_url, "shell")
151 );
151 );
152 this.stdin_channel = new this.WebSocket(
152 this.stdin_channel = new this.WebSocket(
153 this.ws_host + utils.url_join_encode(this.kernel_url, "stdin")
153 this.ws_host + utils.url_join_encode(this.kernel_url, "stdin")
154 );
154 );
155 this.iopub_channel = new this.WebSocket(
155 this.iopub_channel = new this.WebSocket(
156 this.ws_host + utils.url_join_encode(this.kernel_url, "iopub")
156 this.ws_host + utils.url_join_encode(this.kernel_url, "iopub")
157 );
157 );
158
158
159 var already_called_onclose = false; // only alert once
159 var already_called_onclose = false; // only alert once
160 var ws_closed_early = function(evt){
160 var ws_closed_early = function(evt){
161 if (already_called_onclose){
161 if (already_called_onclose){
162 return;
162 return;
163 }
163 }
164 already_called_onclose = true;
164 already_called_onclose = true;
165 if ( ! evt.wasClean ){
165 if ( ! evt.wasClean ){
166 that._websocket_closed(ws_host_url, true);
166 that._websocket_closed(ws_host_url, true);
167 }
167 }
168 };
168 };
169 var ws_closed_late = function(evt){
169 var ws_closed_late = function(evt){
170 if (already_called_onclose){
170 if (already_called_onclose){
171 return;
171 return;
172 }
172 }
173 already_called_onclose = true;
173 already_called_onclose = true;
174 if ( ! evt.wasClean ){
174 if ( ! evt.wasClean ){
175 that._websocket_closed(ws_host_url, false);
175 that._websocket_closed(ws_host_url, false);
176 }
176 }
177 };
177 };
178 var channels = [this.shell_channel, this.iopub_channel, this.stdin_channel];
178 var channels = [this.shell_channel, this.iopub_channel, this.stdin_channel];
179 for (var i=0; i < channels.length; i++) {
179 for (var i=0; i < channels.length; i++) {
180 channels[i].onopen = $.proxy(this._ws_opened, this);
180 channels[i].onopen = $.proxy(this._ws_opened, this);
181 channels[i].onclose = ws_closed_early;
181 channels[i].onclose = ws_closed_early;
182 }
182 }
183 // switch from early-close to late-close message after 1s
183 // switch from early-close to late-close message after 1s
184 setTimeout(function() {
184 setTimeout(function() {
185 for (var i=0; i < channels.length; i++) {
185 for (var i=0; i < channels.length; i++) {
186 if (channels[i] !== null) {
186 if (channels[i] !== null) {
187 channels[i].onclose = ws_closed_late;
187 channels[i].onclose = ws_closed_late;
188 }
188 }
189 }
189 }
190 }, 1000);
190 }, 1000);
191 this.shell_channel.onmessage = $.proxy(this._handle_shell_reply, this);
191 this.shell_channel.onmessage = $.proxy(this._handle_shell_reply, this);
192 this.iopub_channel.onmessage = $.proxy(this._handle_iopub_message, this);
192 this.iopub_channel.onmessage = $.proxy(this._handle_iopub_message, this);
193 this.stdin_channel.onmessage = $.proxy(this._handle_input_request, this);
193 this.stdin_channel.onmessage = $.proxy(this._handle_input_request, this);
194 };
194 };
195
195
196 /**
196 /**
197 * Handle a websocket entering the open state
197 * Handle a websocket entering the open state
198 * sends session and cookie authentication info as first message.
198 * sends session and cookie authentication info as first message.
199 * Once all sockets are open, signal the Kernel.status_started event.
199 * Once all sockets are open, signal the Kernel.status_started event.
200 * @method _ws_opened
200 * @method _ws_opened
201 */
201 */
202 Kernel.prototype._ws_opened = function (evt) {
202 Kernel.prototype._ws_opened = function (evt) {
203 // send the session id so the Session object Python-side
203 // send the session id so the Session object Python-side
204 // has the same identity
204 // has the same identity
205 evt.target.send(this.session_id + ':' + document.cookie);
205 evt.target.send(this.session_id + ':' + document.cookie);
206
206
207 var channels = [this.shell_channel, this.iopub_channel, this.stdin_channel];
207 var channels = [this.shell_channel, this.iopub_channel, this.stdin_channel];
208 for (var i=0; i < channels.length; i++) {
208 for (var i=0; i < channels.length; i++) {
209 // if any channel is not ready, don't trigger event.
209 // if any channel is not ready, don't trigger event.
210 if ( !channels[i].readyState ) return;
210 if ( !channels[i].readyState ) return;
211 }
211 }
212 // all events ready, trigger started event.
212 // all events ready, trigger started event.
213 this.events.trigger('status_started.Kernel', {kernel: this});
213 this.events.trigger('status_started.Kernel', {kernel: this});
214 };
214 };
215
215
216 /**
216 /**
217 * Stop the websocket channels.
217 * Stop the websocket channels.
218 * @method stop_channels
218 * @method stop_channels
219 */
219 */
220 Kernel.prototype.stop_channels = function () {
220 Kernel.prototype.stop_channels = function () {
221 var channels = [this.shell_channel, this.iopub_channel, this.stdin_channel];
221 var channels = [this.shell_channel, this.iopub_channel, this.stdin_channel];
222 for (var i=0; i < channels.length; i++) {
222 for (var i=0; i < channels.length; i++) {
223 if ( channels[i] !== null ) {
223 if ( channels[i] !== null ) {
224 channels[i].onclose = null;
224 channels[i].onclose = null;
225 channels[i].close();
225 channels[i].close();
226 }
226 }
227 }
227 }
228 this.shell_channel = this.iopub_channel = this.stdin_channel = null;
228 this.shell_channel = this.iopub_channel = this.stdin_channel = null;
229 };
229 };
230
230
231 // Main public methods.
231 // Main public methods.
232
232
233 // send a message on the Kernel's shell channel
233 // send a message on the Kernel's shell channel
234 Kernel.prototype.send_shell_message = function (msg_type, content, callbacks, metadata) {
234 Kernel.prototype.send_shell_message = function (msg_type, content, callbacks, metadata) {
235 var msg = this._get_msg(msg_type, content, metadata);
235 var msg = this._get_msg(msg_type, content, metadata);
236 this.shell_channel.send(JSON.stringify(msg));
236 this.shell_channel.send(JSON.stringify(msg));
237 this.set_callbacks_for_msg(msg.header.msg_id, callbacks);
237 this.set_callbacks_for_msg(msg.header.msg_id, callbacks);
238 return msg.header.msg_id;
238 return msg.header.msg_id;
239 };
239 };
240
240
241 /**
241 /**
242 * Get kernel info
242 * Get kernel info
243 *
243 *
244 * @param callback {function}
244 * @param callback {function}
245 * @method kernel_info
245 * @method kernel_info
246 *
246 *
247 * When calling this method, pass a callback function that expects one argument.
247 * When calling this method, pass a callback function that expects one argument.
248 * The callback will be passed the complete `kernel_info_reply` message documented
248 * The callback will be passed the complete `kernel_info_reply` message documented
249 * [here](http://ipython.org/ipython-doc/dev/development/messaging.html#kernel-info)
249 * [here](http://ipython.org/ipython-doc/dev/development/messaging.html#kernel-info)
250 */
250 */
251 Kernel.prototype.kernel_info = function (callback) {
251 Kernel.prototype.kernel_info = function (callback) {
252 var callbacks;
252 var callbacks;
253 if (callback) {
253 if (callback) {
254 callbacks = { shell : { reply : callback } };
254 callbacks = { shell : { reply : callback } };
255 }
255 }
256 return this.send_shell_message("kernel_info_request", {}, callbacks);
256 return this.send_shell_message("kernel_info_request", {}, callbacks);
257 };
257 };
258
258
259 /**
259 /**
260 * Get info on an object
260 * Get info on an object
261 *
261 *
262 * @param code {string}
262 * @param code {string}
263 * @param cursor_pos {integer}
263 * @param cursor_pos {integer}
264 * @param callback {function}
264 * @param callback {function}
265 * @method inspect
265 * @method inspect
266 *
266 *
267 * When calling this method, pass a callback function that expects one argument.
267 * When calling this method, pass a callback function that expects one argument.
268 * The callback will be passed the complete `inspect_reply` message documented
268 * The callback will be passed the complete `inspect_reply` message documented
269 * [here](http://ipython.org/ipython-doc/dev/development/messaging.html#object-information)
269 * [here](http://ipython.org/ipython-doc/dev/development/messaging.html#object-information)
270 */
270 */
271 Kernel.prototype.inspect = function (code, cursor_pos, callback) {
271 Kernel.prototype.inspect = function (code, cursor_pos, callback) {
272 var callbacks;
272 var callbacks;
273 if (callback) {
273 if (callback) {
274 callbacks = { shell : { reply : callback } };
274 callbacks = { shell : { reply : callback } };
275 }
275 }
276
276
277 var content = {
277 var content = {
278 code : code,
278 code : code,
279 cursor_pos : cursor_pos,
279 cursor_pos : cursor_pos,
280 detail_level : 0,
280 detail_level : 0,
281 };
281 };
282 return this.send_shell_message("inspect_request", content, callbacks);
282 return this.send_shell_message("inspect_request", content, callbacks);
283 };
283 };
284
284
285 /**
285 /**
286 * Execute given code into kernel, and pass result to callback.
286 * Execute given code into kernel, and pass result to callback.
287 *
287 *
288 * @async
288 * @async
289 * @method execute
289 * @method execute
290 * @param {string} code
290 * @param {string} code
291 * @param [callbacks] {Object} With the following keys (all optional)
291 * @param [callbacks] {Object} With the following keys (all optional)
292 * @param callbacks.shell.reply {function}
292 * @param callbacks.shell.reply {function}
293 * @param callbacks.shell.payload.[payload_name] {function}
293 * @param callbacks.shell.payload.[payload_name] {function}
294 * @param callbacks.iopub.output {function}
294 * @param callbacks.iopub.output {function}
295 * @param callbacks.iopub.clear_output {function}
295 * @param callbacks.iopub.clear_output {function}
296 * @param callbacks.input {function}
296 * @param callbacks.input {function}
297 * @param {object} [options]
297 * @param {object} [options]
298 * @param [options.silent=false] {Boolean}
298 * @param [options.silent=false] {Boolean}
299 * @param [options.user_expressions=empty_dict] {Dict}
299 * @param [options.user_expressions=empty_dict] {Dict}
300 * @param [options.allow_stdin=false] {Boolean} true|false
300 * @param [options.allow_stdin=false] {Boolean} true|false
301 *
301 *
302 * @example
302 * @example
303 *
303 *
304 * The options object should contain the options for the execute call. Its default
304 * The options object should contain the options for the execute call. Its default
305 * values are:
305 * values are:
306 *
306 *
307 * options = {
307 * options = {
308 * silent : true,
308 * silent : true,
309 * user_expressions : {},
309 * user_expressions : {},
310 * allow_stdin : false
310 * allow_stdin : false
311 * }
311 * }
312 *
312 *
313 * When calling this method pass a callbacks structure of the form:
313 * When calling this method pass a callbacks structure of the form:
314 *
314 *
315 * callbacks = {
315 * callbacks = {
316 * shell : {
316 * shell : {
317 * reply : execute_reply_callback,
317 * reply : execute_reply_callback,
318 * payload : {
318 * payload : {
319 * set_next_input : set_next_input_callback,
319 * set_next_input : set_next_input_callback,
320 * }
320 * }
321 * },
321 * },
322 * iopub : {
322 * iopub : {
323 * output : output_callback,
323 * output : output_callback,
324 * clear_output : clear_output_callback,
324 * clear_output : clear_output_callback,
325 * },
325 * },
326 * input : raw_input_callback
326 * input : raw_input_callback
327 * }
327 * }
328 *
328 *
329 * Each callback will be passed the entire message as a single arugment.
329 * Each callback will be passed the entire message as a single arugment.
330 * Payload handlers will be passed the corresponding payload and the execute_reply message.
330 * Payload handlers will be passed the corresponding payload and the execute_reply message.
331 */
331 */
332 Kernel.prototype.execute = function (code, callbacks, options) {
332 Kernel.prototype.execute = function (code, callbacks, options) {
333
333
334 var content = {
334 var content = {
335 code : code,
335 code : code,
336 silent : true,
336 silent : true,
337 store_history : false,
337 store_history : false,
338 user_expressions : {},
338 user_expressions : {},
339 allow_stdin : false
339 allow_stdin : false
340 };
340 };
341 callbacks = callbacks || {};
341 callbacks = callbacks || {};
342 if (callbacks.input !== undefined) {
342 if (callbacks.input !== undefined) {
343 content.allow_stdin = true;
343 content.allow_stdin = true;
344 }
344 }
345 $.extend(true, content, options);
345 $.extend(true, content, options);
346 this.events.trigger('execution_request.Kernel', {kernel: this, content:content});
346 this.events.trigger('execution_request.Kernel', {kernel: this, content:content});
347 return this.send_shell_message("execute_request", content, callbacks);
347 return this.send_shell_message("execute_request", content, callbacks);
348 };
348 };
349
349
350 /**
350 /**
351 * When calling this method, pass a function to be called with the `complete_reply` message
351 * When calling this method, pass a function to be called with the `complete_reply` message
352 * as its only argument when it arrives.
352 * as its only argument when it arrives.
353 *
353 *
354 * `complete_reply` is documented
354 * `complete_reply` is documented
355 * [here](http://ipython.org/ipython-doc/dev/development/messaging.html#complete)
355 * [here](http://ipython.org/ipython-doc/dev/development/messaging.html#complete)
356 *
356 *
357 * @method complete
357 * @method complete
358 * @param code {string}
358 * @param code {string}
359 * @param cursor_pos {integer}
359 * @param cursor_pos {integer}
360 * @param callback {function}
360 * @param callback {function}
361 *
361 *
362 */
362 */
363 Kernel.prototype.complete = function (code, cursor_pos, callback) {
363 Kernel.prototype.complete = function (code, cursor_pos, callback) {
364 var callbacks;
364 var callbacks;
365 if (callback) {
365 if (callback) {
366 callbacks = { shell : { reply : callback } };
366 callbacks = { shell : { reply : callback } };
367 }
367 }
368 var content = {
368 var content = {
369 code : code,
369 code : code,
370 cursor_pos : cursor_pos,
370 cursor_pos : cursor_pos,
371 };
371 };
372 return this.send_shell_message("complete_request", content, callbacks);
372 return this.send_shell_message("complete_request", content, callbacks);
373 };
373 };
374
374
375
375
376 Kernel.prototype.interrupt = function () {
376 Kernel.prototype.interrupt = function () {
377 if (this.running) {
377 if (this.running) {
378 this.events.trigger('status_interrupting.Kernel', {kernel: this});
378 this.events.trigger('status_interrupting.Kernel', {kernel: this});
379 this.post(utils.url_join_encode(this.kernel_url, "interrupt"));
379 this.post(utils.url_join_encode(this.kernel_url, "interrupt"));
380 }
380 }
381 };
381 };
382
382
383
383
384 Kernel.prototype.kill = function () {
384 Kernel.prototype.kill = function () {
385 if (this.running) {
385 if (this.running) {
386 this.running = false;
386 this.running = false;
387 var settings = {
387 var settings = {
388 cache : false,
388 cache : false,
389 type : "DELETE",
389 type : "DELETE",
390 error : utils.log_ajax_error,
390 error : utils.log_ajax_error,
391 };
391 };
392 $.ajax(utils.url_join_encode(this.kernel_url), settings);
392 $.ajax(utils.url_join_encode(this.kernel_url), settings);
393 }
393 }
394 };
394 };
395
395
396 Kernel.prototype.send_input_reply = function (input) {
396 Kernel.prototype.send_input_reply = function (input) {
397 var content = {
397 var content = {
398 value : input,
398 value : input,
399 };
399 };
400 this.events.trigger('input_reply.Kernel', {kernel: this, content:content});
400 this.events.trigger('input_reply.Kernel', {kernel: this, content:content});
401 var msg = this._get_msg("input_reply", content);
401 var msg = this._get_msg("input_reply", content);
402 this.stdin_channel.send(JSON.stringify(msg));
402 this.stdin_channel.send(JSON.stringify(msg));
403 return msg.header.msg_id;
403 return msg.header.msg_id;
404 };
404 };
405
405
406
406
407 // Reply handlers
407 // Reply handlers
408
408
409 Kernel.prototype.register_iopub_handler = function (msg_type, callback) {
409 Kernel.prototype.register_iopub_handler = function (msg_type, callback) {
410 this._iopub_handlers[msg_type] = callback;
410 this._iopub_handlers[msg_type] = callback;
411 };
411 };
412
412
413 Kernel.prototype.get_iopub_handler = function (msg_type) {
413 Kernel.prototype.get_iopub_handler = function (msg_type) {
414 // get iopub handler for a specific message type
414 // get iopub handler for a specific message type
415 return this._iopub_handlers[msg_type];
415 return this._iopub_handlers[msg_type];
416 };
416 };
417
417
418
418
419 Kernel.prototype.get_callbacks_for_msg = function (msg_id) {
419 Kernel.prototype.get_callbacks_for_msg = function (msg_id) {
420 // get callbacks for a specific message
420 // get callbacks for a specific message
421 if (msg_id == this.last_msg_id) {
421 if (msg_id == this.last_msg_id) {
422 return this.last_msg_callbacks;
422 return this.last_msg_callbacks;
423 } else {
423 } else {
424 return this._msg_callbacks[msg_id];
424 return this._msg_callbacks[msg_id];
425 }
425 }
426 };
426 };
427
427
428
428
429 Kernel.prototype.clear_callbacks_for_msg = function (msg_id) {
429 Kernel.prototype.clear_callbacks_for_msg = function (msg_id) {
430 if (this._msg_callbacks[msg_id] !== undefined ) {
430 if (this._msg_callbacks[msg_id] !== undefined ) {
431 delete this._msg_callbacks[msg_id];
431 delete this._msg_callbacks[msg_id];
432 }
432 }
433 };
433 };
434
434
435 Kernel.prototype._finish_shell = function (msg_id) {
435 Kernel.prototype._finish_shell = function (msg_id) {
436 var callbacks = this._msg_callbacks[msg_id];
436 var callbacks = this._msg_callbacks[msg_id];
437 if (callbacks !== undefined) {
437 if (callbacks !== undefined) {
438 callbacks.shell_done = true;
438 callbacks.shell_done = true;
439 if (callbacks.iopub_done) {
439 if (callbacks.iopub_done) {
440 this.clear_callbacks_for_msg(msg_id);
440 this.clear_callbacks_for_msg(msg_id);
441 }
441 }
442 }
442 }
443 };
443 };
444
444
445 Kernel.prototype._finish_iopub = function (msg_id) {
445 Kernel.prototype._finish_iopub = function (msg_id) {
446 var callbacks = this._msg_callbacks[msg_id];
446 var callbacks = this._msg_callbacks[msg_id];
447 if (callbacks !== undefined) {
447 if (callbacks !== undefined) {
448 callbacks.iopub_done = true;
448 callbacks.iopub_done = true;
449 if (callbacks.shell_done) {
449 if (callbacks.shell_done) {
450 this.clear_callbacks_for_msg(msg_id);
450 this.clear_callbacks_for_msg(msg_id);
451 }
451 }
452 }
452 }
453 };
453 };
454
454
455 /* Set callbacks for a particular message.
455 /* Set callbacks for a particular message.
456 * Callbacks should be a struct of the following form:
456 * Callbacks should be a struct of the following form:
457 * shell : {
457 * shell : {
458 *
458 *
459 * }
459 * }
460
460
461 */
461 */
462 Kernel.prototype.set_callbacks_for_msg = function (msg_id, callbacks) {
462 Kernel.prototype.set_callbacks_for_msg = function (msg_id, callbacks) {
463 this.last_msg_id = msg_id;
463 this.last_msg_id = msg_id;
464 if (callbacks) {
464 if (callbacks) {
465 // shallow-copy mapping, because we will modify it at the top level
465 // shallow-copy mapping, because we will modify it at the top level
466 var cbcopy = this._msg_callbacks[msg_id] = this.last_msg_callbacks = {};
466 var cbcopy = this._msg_callbacks[msg_id] = this.last_msg_callbacks = {};
467 cbcopy.shell = callbacks.shell;
467 cbcopy.shell = callbacks.shell;
468 cbcopy.iopub = callbacks.iopub;
468 cbcopy.iopub = callbacks.iopub;
469 cbcopy.input = callbacks.input;
469 cbcopy.input = callbacks.input;
470 cbcopy.shell_done = (!callbacks.shell);
470 cbcopy.shell_done = (!callbacks.shell);
471 cbcopy.iopub_done = (!callbacks.iopub);
471 cbcopy.iopub_done = (!callbacks.iopub);
472 } else {
472 } else {
473 this.last_msg_callbacks = {};
473 this.last_msg_callbacks = {};
474 }
474 }
475 };
475 };
476
476
477
477
478 Kernel.prototype._handle_shell_reply = function (e) {
478 Kernel.prototype._handle_shell_reply = function (e) {
479 var reply = $.parseJSON(e.data);
479 var reply = $.parseJSON(e.data);
480 this.events.trigger('shell_reply.Kernel', {kernel: this, reply:reply});
480 this.events.trigger('shell_reply.Kernel', {kernel: this, reply:reply});
481 var content = reply.content;
481 var content = reply.content;
482 var metadata = reply.metadata;
482 var metadata = reply.metadata;
483 var parent_id = reply.parent_header.msg_id;
483 var parent_id = reply.parent_header.msg_id;
484 var callbacks = this.get_callbacks_for_msg(parent_id);
484 var callbacks = this.get_callbacks_for_msg(parent_id);
485 if (!callbacks || !callbacks.shell) {
485 if (!callbacks || !callbacks.shell) {
486 return;
486 return;
487 }
487 }
488 var shell_callbacks = callbacks.shell;
488 var shell_callbacks = callbacks.shell;
489
489
490 // signal that shell callbacks are done
490 // signal that shell callbacks are done
491 this._finish_shell(parent_id);
491 this._finish_shell(parent_id);
492
492
493 if (shell_callbacks.reply !== undefined) {
493 if (shell_callbacks.reply !== undefined) {
494 shell_callbacks.reply(reply);
494 shell_callbacks.reply(reply);
495 }
495 }
496 if (content.payload && shell_callbacks.payload) {
496 if (content.payload && shell_callbacks.payload) {
497 this._handle_payloads(content.payload, shell_callbacks.payload, reply);
497 this._handle_payloads(content.payload, shell_callbacks.payload, reply);
498 }
498 }
499 };
499 };
500
500
501
501
502 Kernel.prototype._handle_payloads = function (payloads, payload_callbacks, msg) {
502 Kernel.prototype._handle_payloads = function (payloads, payload_callbacks, msg) {
503 var l = payloads.length;
503 var l = payloads.length;
504 // Payloads are handled by triggering events because we don't want the Kernel
504 // Payloads are handled by triggering events because we don't want the Kernel
505 // to depend on the Notebook or Pager classes.
505 // to depend on the Notebook or Pager classes.
506 for (var i=0; i<l; i++) {
506 for (var i=0; i<l; i++) {
507 var payload = payloads[i];
507 var payload = payloads[i];
508 var callback = payload_callbacks[payload.source];
508 var callback = payload_callbacks[payload.source];
509 if (callback) {
509 if (callback) {
510 callback(payload, msg);
510 callback(payload, msg);
511 }
511 }
512 }
512 }
513 };
513 };
514
514
515 Kernel.prototype._handle_status_message = function (msg) {
515 Kernel.prototype._handle_status_message = function (msg) {
516 var execution_state = msg.content.execution_state;
516 var execution_state = msg.content.execution_state;
517 var parent_id = msg.parent_header.msg_id;
517 var parent_id = msg.parent_header.msg_id;
518
518
519 // dispatch status msg callbacks, if any
519 // dispatch status msg callbacks, if any
520 var callbacks = this.get_callbacks_for_msg(parent_id);
520 var callbacks = this.get_callbacks_for_msg(parent_id);
521 if (callbacks && callbacks.iopub && callbacks.iopub.status) {
521 if (callbacks && callbacks.iopub && callbacks.iopub.status) {
522 try {
522 try {
523 callbacks.iopub.status(msg);
523 callbacks.iopub.status(msg);
524 } catch (e) {
524 } catch (e) {
525 console.log("Exception in status msg handler", e, e.stack);
525 console.log("Exception in status msg handler", e, e.stack);
526 }
526 }
527 }
527 }
528
528
529 if (execution_state === 'busy') {
529 if (execution_state === 'busy') {
530 this.events.trigger('status_busy.Kernel', {kernel: this});
530 this.events.trigger('status_busy.Kernel', {kernel: this});
531 } else if (execution_state === 'idle') {
531 } else if (execution_state === 'idle') {
532 // signal that iopub callbacks are (probably) done
532 // signal that iopub callbacks are (probably) done
533 // async output may still arrive,
533 // async output may still arrive,
534 // but only for the most recent request
534 // but only for the most recent request
535 this._finish_iopub(parent_id);
535 this._finish_iopub(parent_id);
536
536
537 // trigger status_idle event
537 // trigger status_idle event
538 this.events.trigger('status_idle.Kernel', {kernel: this});
538 this.events.trigger('status_idle.Kernel', {kernel: this});
539 } else if (execution_state === 'restarting') {
539 } else if (execution_state === 'restarting') {
540 // autorestarting is distinct from restarting,
540 // autorestarting is distinct from restarting,
541 // in that it means the kernel died and the server is restarting it.
541 // in that it means the kernel died and the server is restarting it.
542 // status_restarting sets the notification widget,
542 // status_restarting sets the notification widget,
543 // autorestart shows the more prominent dialog.
543 // autorestart shows the more prominent dialog.
544 this.events.trigger('status_autorestarting.Kernel', {kernel: this});
544 this.events.trigger('status_autorestarting.Kernel', {kernel: this});
545 this.events.trigger('status_restarting.Kernel', {kernel: this});
545 this.events.trigger('status_restarting.Kernel', {kernel: this});
546 } else if (execution_state === 'dead') {
546 } else if (execution_state === 'dead') {
547 this.stop_channels();
547 this.stop_channels();
548 this.events.trigger('status_dead.Kernel', {kernel: this});
548 this.events.trigger('status_dead.Kernel', {kernel: this});
549 }
549 }
550 };
550 };
551
551
552
552
553 // handle clear_output message
553 // handle clear_output message
554 Kernel.prototype._handle_clear_output = function (msg) {
554 Kernel.prototype._handle_clear_output = function (msg) {
555 var callbacks = this.get_callbacks_for_msg(msg.parent_header.msg_id);
555 var callbacks = this.get_callbacks_for_msg(msg.parent_header.msg_id);
556 if (!callbacks || !callbacks.iopub) {
556 if (!callbacks || !callbacks.iopub) {
557 return;
557 return;
558 }
558 }
559 var callback = callbacks.iopub.clear_output;
559 var callback = callbacks.iopub.clear_output;
560 if (callback) {
560 if (callback) {
561 callback(msg);
561 callback(msg);
562 }
562 }
563 };
563 };
564
564
565
565
566 // handle an output message (execute_result, display_data, etc.)
566 // handle an output message (execute_result, display_data, etc.)
567 Kernel.prototype._handle_output_message = function (msg) {
567 Kernel.prototype._handle_output_message = function (msg) {
568 var callbacks = this.get_callbacks_for_msg(msg.parent_header.msg_id);
568 var callbacks = this.get_callbacks_for_msg(msg.parent_header.msg_id);
569 if (!callbacks || !callbacks.iopub) {
569 if (!callbacks || !callbacks.iopub) {
570 return;
570 return;
571 }
571 }
572 var callback = callbacks.iopub.output;
572 var callback = callbacks.iopub.output;
573 if (callback) {
573 if (callback) {
574 callback(msg);
574 callback(msg);
575 }
575 }
576 };
576 };
577
577
578 // dispatch IOPub messages to respective handlers.
578 // dispatch IOPub messages to respective handlers.
579 // each message type should have a handler.
579 // each message type should have a handler.
580 Kernel.prototype._handle_iopub_message = function (e) {
580 Kernel.prototype._handle_iopub_message = function (e) {
581 var msg = $.parseJSON(e.data);
581 var msg = $.parseJSON(e.data);
582
582
583 var handler = this.get_iopub_handler(msg.header.msg_type);
583 var handler = this.get_iopub_handler(msg.header.msg_type);
584 if (handler !== undefined) {
584 if (handler !== undefined) {
585 handler(msg);
585 handler(msg);
586 }
586 }
587 };
587 };
588
588
589
589
590 Kernel.prototype._handle_input_request = function (e) {
590 Kernel.prototype._handle_input_request = function (e) {
591 var request = $.parseJSON(e.data);
591 var request = $.parseJSON(e.data);
592 var header = request.header;
592 var header = request.header;
593 var content = request.content;
593 var content = request.content;
594 var metadata = request.metadata;
594 var metadata = request.metadata;
595 var msg_type = header.msg_type;
595 var msg_type = header.msg_type;
596 if (msg_type !== 'input_request') {
596 if (msg_type !== 'input_request') {
597 console.log("Invalid input request!", request);
597 console.log("Invalid input request!", request);
598 return;
598 return;
599 }
599 }
600 var callbacks = this.get_callbacks_for_msg(request.parent_header.msg_id);
600 var callbacks = this.get_callbacks_for_msg(request.parent_header.msg_id);
601 if (callbacks) {
601 if (callbacks) {
602 if (callbacks.input) {
602 if (callbacks.input) {
603 callbacks.input(request);
603 callbacks.input(request);
604 }
604 }
605 }
605 }
606 };
606 };
607
607
608 // Backwards compatability.
608 // Backwards compatability.
609 IPython.Kernel = Kernel;
609 IPython.Kernel = Kernel;
610
610
611 return Kernel;
611 return {'Kernel': Kernel};
612 });
612 });
@@ -1,116 +1,116 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 define([
4 define([
5 'base/js/namespace',
5 'base/js/namespace',
6 'jquery',
6 'jquery',
7 'base/js/utils',
7 'base/js/utils',
8 'services/kernels/js/kernel',
8 'services/kernels/js/kernel',
9 ], function(IPython, $, utils, Kernel) {
9 ], function(IPython, $, utils, Kernel) {
10 "use strict";
10 "use strict";
11
11
12 var Session = function(notebook, options){
12 var Session = function(notebook, options){
13 this.kernel = null;
13 this.kernel = null;
14 this.id = null;
14 this.id = null;
15 this.notebook = notebook;
15 this.notebook = notebook;
16 this.name = notebook.notebook_name;
16 this.name = notebook.notebook_name;
17 this.path = notebook.notebook_path;
17 this.path = notebook.notebook_path;
18 this.base_url = notebook.base_url;
18 this.base_url = notebook.base_url;
19 };
19 };
20
20
21 Session.prototype.start = function(callback) {
21 Session.prototype.start = function(callback) {
22 var that = this;
22 var that = this;
23 var model = {
23 var model = {
24 notebook : {
24 notebook : {
25 name : this.name,
25 name : this.name,
26 path : this.path
26 path : this.path
27 }
27 }
28 };
28 };
29 var settings = {
29 var settings = {
30 processData : false,
30 processData : false,
31 cache : false,
31 cache : false,
32 type : "POST",
32 type : "POST",
33 data: JSON.stringify(model),
33 data: JSON.stringify(model),
34 dataType : "json",
34 dataType : "json",
35 success : function (data, status, xhr) {
35 success : function (data, status, xhr) {
36 that._handle_start_success(data);
36 that._handle_start_success(data);
37 if (callback) {
37 if (callback) {
38 callback(data, status, xhr);
38 callback(data, status, xhr);
39 }
39 }
40 },
40 },
41 error : utils.log_ajax_error,
41 error : utils.log_ajax_error,
42 };
42 };
43 var url = utils.url_join_encode(this.base_url, 'api/sessions');
43 var url = utils.url_join_encode(this.base_url, 'api/sessions');
44 $.ajax(url, settings);
44 $.ajax(url, settings);
45 };
45 };
46
46
47 Session.prototype.rename_notebook = function (name, path) {
47 Session.prototype.rename_notebook = function (name, path) {
48 this.name = name;
48 this.name = name;
49 this.path = path;
49 this.path = path;
50 var model = {
50 var model = {
51 notebook : {
51 notebook : {
52 name : this.name,
52 name : this.name,
53 path : this.path
53 path : this.path
54 }
54 }
55 };
55 };
56 var settings = {
56 var settings = {
57 processData : false,
57 processData : false,
58 cache : false,
58 cache : false,
59 type : "PATCH",
59 type : "PATCH",
60 data: JSON.stringify(model),
60 data: JSON.stringify(model),
61 dataType : "json",
61 dataType : "json",
62 error : utils.log_ajax_error,
62 error : utils.log_ajax_error,
63 };
63 };
64 var url = utils.url_join_encode(this.base_url, 'api/sessions', this.id);
64 var url = utils.url_join_encode(this.base_url, 'api/sessions', this.id);
65 $.ajax(url, settings);
65 $.ajax(url, settings);
66 };
66 };
67
67
68 Session.prototype.delete = function() {
68 Session.prototype.delete = function() {
69 var settings = {
69 var settings = {
70 processData : false,
70 processData : false,
71 cache : false,
71 cache : false,
72 type : "DELETE",
72 type : "DELETE",
73 dataType : "json",
73 dataType : "json",
74 error : utils.log_ajax_error,
74 error : utils.log_ajax_error,
75 };
75 };
76 this.kernel.running = false;
76 this.kernel.running = false;
77 var url = utils.url_join_encode(this.base_url, 'api/sessions', this.id);
77 var url = utils.url_join_encode(this.base_url, 'api/sessions', this.id);
78 $.ajax(url, settings);
78 $.ajax(url, settings);
79 };
79 };
80
80
81 // Kernel related things
81 // Kernel related things
82 /**
82 /**
83 * Create the Kernel object associated with this Session.
83 * Create the Kernel object associated with this Session.
84 *
84 *
85 * @method _handle_start_success
85 * @method _handle_start_success
86 */
86 */
87 Session.prototype._handle_start_success = function (data, status, xhr) {
87 Session.prototype._handle_start_success = function (data, status, xhr) {
88 this.id = data.id;
88 this.id = data.id;
89 var kernel_service_url = utils.url_path_join(this.base_url, "api/kernels");
89 var kernel_service_url = utils.url_path_join(this.base_url, "api/kernels");
90 this.kernel = new Kernel(kernel_service_url, this.notebook);
90 this.kernel = new Kernel(kernel_service_url, this.notebook);
91 this.kernel._kernel_started(data.kernel);
91 this.kernel._kernel_started(data.kernel);
92 };
92 };
93
93
94 /**
94 /**
95 * Prompt the user to restart the IPython kernel.
95 * Prompt the user to restart the IPython kernel.
96 *
96 *
97 * @method restart_kernel
97 * @method restart_kernel
98 */
98 */
99 Session.prototype.restart_kernel = function () {
99 Session.prototype.restart_kernel = function () {
100 this.kernel.restart();
100 this.kernel.restart();
101 };
101 };
102
102
103 Session.prototype.interrupt_kernel = function() {
103 Session.prototype.interrupt_kernel = function() {
104 this.kernel.interrupt();
104 this.kernel.interrupt();
105 };
105 };
106
106
107
107
108 Session.prototype.kill_kernel = function() {
108 Session.prototype.kill_kernel = function() {
109 this.kernel.kill();
109 this.kernel.kill();
110 };
110 };
111
111
112 // For backwards compatability.
112 // For backwards compatability.
113 IPython.Session = Session;
113 IPython.Session = Session;
114
114
115 return Session;
115 return {'Session': Session};
116 });
116 });
@@ -1,190 +1,193 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 define([
4 define([
5 'base/js/namespace',
5 'base/js/namespace',
6 'jquery',
6 'jquery',
7 'base/js/utils',
7 'base/js/utils',
8 ], function(IPython, $, utils) {
8 ], function(IPython, $, utils) {
9 "use strict";
9 "use strict";
10
10
11 var ClusterList = function (selector, options) {
11 var ClusterList = function (selector, options) {
12 this.selector = selector;
12 this.selector = selector;
13 if (this.selector !== undefined) {
13 if (this.selector !== undefined) {
14 this.element = $(selector);
14 this.element = $(selector);
15 this.style();
15 this.style();
16 this.bind_events();
16 this.bind_events();
17 }
17 }
18 options = options || {};
18 options = options || {};
19 this.options = options;
19 this.options = options;
20 this.base_url = options.base_url || utils.get_body_data("baseUrl");
20 this.base_url = options.base_url || utils.get_body_data("baseUrl");
21 this.notebook_path = options.notebook_path || utils.get_body_data("notebookPath");
21 this.notebook_path = options.notebook_path || utils.get_body_data("notebookPath");
22 };
22 };
23
23
24 ClusterList.prototype.style = function () {
24 ClusterList.prototype.style = function () {
25 $('#cluster_list').addClass('list_container');
25 $('#cluster_list').addClass('list_container');
26 $('#cluster_toolbar').addClass('list_toolbar');
26 $('#cluster_toolbar').addClass('list_toolbar');
27 $('#cluster_list_info').addClass('toolbar_info');
27 $('#cluster_list_info').addClass('toolbar_info');
28 $('#cluster_buttons').addClass('toolbar_buttons');
28 $('#cluster_buttons').addClass('toolbar_buttons');
29 };
29 };
30
30
31
31
32 ClusterList.prototype.bind_events = function () {
32 ClusterList.prototype.bind_events = function () {
33 var that = this;
33 var that = this;
34 $('#refresh_cluster_list').click(function () {
34 $('#refresh_cluster_list').click(function () {
35 that.load_list();
35 that.load_list();
36 });
36 });
37 };
37 };
38
38
39
39
40 ClusterList.prototype.load_list = function () {
40 ClusterList.prototype.load_list = function () {
41 var settings = {
41 var settings = {
42 processData : false,
42 processData : false,
43 cache : false,
43 cache : false,
44 type : "GET",
44 type : "GET",
45 dataType : "json",
45 dataType : "json",
46 success : $.proxy(this.load_list_success, this),
46 success : $.proxy(this.load_list_success, this),
47 error : utils.log_ajax_error,
47 error : utils.log_ajax_error,
48 };
48 };
49 var url = utils.url_join_encode(this.base_url, 'clusters');
49 var url = utils.url_join_encode(this.base_url, 'clusters');
50 $.ajax(url, settings);
50 $.ajax(url, settings);
51 };
51 };
52
52
53
53
54 ClusterList.prototype.clear_list = function () {
54 ClusterList.prototype.clear_list = function () {
55 this.element.children('.list_item').remove();
55 this.element.children('.list_item').remove();
56 };
56 };
57
57
58 ClusterList.prototype.load_list_success = function (data, status, xhr) {
58 ClusterList.prototype.load_list_success = function (data, status, xhr) {
59 this.clear_list();
59 this.clear_list();
60 var len = data.length;
60 var len = data.length;
61 for (var i=0; i<len; i++) {
61 for (var i=0; i<len; i++) {
62 var element = $('<div/>');
62 var element = $('<div/>');
63 var item = new ClusterItem(element, this.options);
63 var item = new ClusterItem(element, this.options);
64 item.update_state(data[i]);
64 item.update_state(data[i]);
65 element.data('item', item);
65 element.data('item', item);
66 this.element.append(element);
66 this.element.append(element);
67 }
67 }
68 };
68 };
69
69
70
70
71 var ClusterItem = function (element, options) {
71 var ClusterItem = function (element, options) {
72 this.element = $(element);
72 this.element = $(element);
73 this.base_url = options.base_url || utils.get_body_data("baseUrl");
73 this.base_url = options.base_url || utils.get_body_data("baseUrl");
74 this.notebook_path = options.notebook_path || utils.get_body_data("notebookPath");
74 this.notebook_path = options.notebook_path || utils.get_body_data("notebookPath");
75 this.data = null;
75 this.data = null;
76 this.style();
76 this.style();
77 };
77 };
78
78
79 ClusterItem.prototype.style = function () {
79 ClusterItem.prototype.style = function () {
80 this.element.addClass('list_item').addClass("row");
80 this.element.addClass('list_item').addClass("row");
81 };
81 };
82
82
83 ClusterItem.prototype.update_state = function (data) {
83 ClusterItem.prototype.update_state = function (data) {
84 this.data = data;
84 this.data = data;
85 if (data.status === 'running') {
85 if (data.status === 'running') {
86 this.state_running();
86 this.state_running();
87 } else if (data.status === 'stopped') {
87 } else if (data.status === 'stopped') {
88 this.state_stopped();
88 this.state_stopped();
89 }
89 }
90 };
90 };
91
91
92
92
93 ClusterItem.prototype.state_stopped = function () {
93 ClusterItem.prototype.state_stopped = function () {
94 var that = this;
94 var that = this;
95 var profile_col = $('<div/>').addClass('profile_col col-md-4').text(this.data.profile);
95 var profile_col = $('<div/>').addClass('profile_col col-md-4').text(this.data.profile);
96 var status_col = $('<div/>').addClass('status_col col-md-3').text('stopped');
96 var status_col = $('<div/>').addClass('status_col col-md-3').text('stopped');
97 var engines_col = $('<div/>').addClass('engine_col col-md-3');
97 var engines_col = $('<div/>').addClass('engine_col col-md-3');
98 var input = $('<input/>').attr('type','number')
98 var input = $('<input/>').attr('type','number')
99 .attr('min',1)
99 .attr('min',1)
100 .attr('size',3)
100 .attr('size',3)
101 .addClass('engine_num_input form-control');
101 .addClass('engine_num_input form-control');
102 engines_col.append(input);
102 engines_col.append(input);
103 var start_button = $('<button/>').addClass("btn btn-default btn-xs").text("Start");
103 var start_button = $('<button/>').addClass("btn btn-default btn-xs").text("Start");
104 var action_col = $('<div/>').addClass('action_col col-md-2').append(
104 var action_col = $('<div/>').addClass('action_col col-md-2').append(
105 $("<span/>").addClass("item_buttons btn-group").append(
105 $("<span/>").addClass("item_buttons btn-group").append(
106 start_button
106 start_button
107 )
107 )
108 );
108 );
109 this.element.empty()
109 this.element.empty()
110 .append(profile_col)
110 .append(profile_col)
111 .append(status_col)
111 .append(status_col)
112 .append(engines_col)
112 .append(engines_col)
113 .append(action_col);
113 .append(action_col);
114 start_button.click(function (e) {
114 start_button.click(function (e) {
115 var n = that.element.find('.engine_num_input').val();
115 var n = that.element.find('.engine_num_input').val();
116 if (!/^\d+$/.test(n) && n.length>0) {
116 if (!/^\d+$/.test(n) && n.length>0) {
117 status_col.text('invalid engine #');
117 status_col.text('invalid engine #');
118 } else {
118 } else {
119 var settings = {
119 var settings = {
120 cache : false,
120 cache : false,
121 data : {n:n},
121 data : {n:n},
122 type : "POST",
122 type : "POST",
123 dataType : "json",
123 dataType : "json",
124 success : function (data, status, xhr) {
124 success : function (data, status, xhr) {
125 that.update_state(data);
125 that.update_state(data);
126 },
126 },
127 error : function (xhr, status, error) {
127 error : function (xhr, status, error) {
128 status_col.text("error starting cluster");
128 status_col.text("error starting cluster");
129 utils.log_ajax_error(xhr, status, error);
129 utils.log_ajax_error(xhr, status, error);
130 }
130 }
131 };
131 };
132 status_col.text('starting');
132 status_col.text('starting');
133 var url = utils.url_join_encode(
133 var url = utils.url_join_encode(
134 that.base_url,
134 that.base_url,
135 'clusters',
135 'clusters',
136 that.data.profile,
136 that.data.profile,
137 'start'
137 'start'
138 );
138 );
139 $.ajax(url, settings);
139 $.ajax(url, settings);
140 }
140 }
141 });
141 });
142 };
142 };
143
143
144
144
145 ClusterItem.prototype.state_running = function () {
145 ClusterItem.prototype.state_running = function () {
146 var that = this;
146 var that = this;
147 var profile_col = $('<div/>').addClass('profile_col col-md-4').text(this.data.profile);
147 var profile_col = $('<div/>').addClass('profile_col col-md-4').text(this.data.profile);
148 var status_col = $('<div/>').addClass('status_col col-md-3').text('running');
148 var status_col = $('<div/>').addClass('status_col col-md-3').text('running');
149 var engines_col = $('<div/>').addClass('engines_col col-md-3').text(this.data.n);
149 var engines_col = $('<div/>').addClass('engines_col col-md-3').text(this.data.n);
150 var stop_button = $('<button/>').addClass("btn btn-default btn-xs").text("Stop");
150 var stop_button = $('<button/>').addClass("btn btn-default btn-xs").text("Stop");
151 var action_col = $('<div/>').addClass('action_col col-md-2').append(
151 var action_col = $('<div/>').addClass('action_col col-md-2').append(
152 $("<span/>").addClass("item_buttons btn-group").append(
152 $("<span/>").addClass("item_buttons btn-group").append(
153 stop_button
153 stop_button
154 )
154 )
155 );
155 );
156 this.element.empty()
156 this.element.empty()
157 .append(profile_col)
157 .append(profile_col)
158 .append(status_col)
158 .append(status_col)
159 .append(engines_col)
159 .append(engines_col)
160 .append(action_col);
160 .append(action_col);
161 stop_button.click(function (e) {
161 stop_button.click(function (e) {
162 var settings = {
162 var settings = {
163 cache : false,
163 cache : false,
164 type : "POST",
164 type : "POST",
165 dataType : "json",
165 dataType : "json",
166 success : function (data, status, xhr) {
166 success : function (data, status, xhr) {
167 that.update_state(data);
167 that.update_state(data);
168 },
168 },
169 error : function (xhr, status, error) {
169 error : function (xhr, status, error) {
170 utils.log_ajax_error(xhr, status, error),
170 utils.log_ajax_error(xhr, status, error),
171 status_col.text("error stopping cluster");
171 status_col.text("error stopping cluster");
172 }
172 }
173 };
173 };
174 status_col.text('stopping');
174 status_col.text('stopping');
175 var url = utils.url_join_encode(
175 var url = utils.url_join_encode(
176 that.base_url,
176 that.base_url,
177 'clusters',
177 'clusters',
178 that.data.profile,
178 that.data.profile,
179 'stop'
179 'stop'
180 );
180 );
181 $.ajax(url, settings);
181 $.ajax(url, settings);
182 });
182 });
183 };
183 };
184
184
185 // For backwards compatability.
185 // For backwards compatability.
186 IPython.ClusterList = ClusterList;
186 IPython.ClusterList = ClusterList;
187 IPython.ClusterItem = ClusterItem;
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 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 define([
4 define([
5 'base/js/namespace',
5 'base/js/namespace',
6 'jquery',
6 'jquery',
7 'tree/js/notebooklist',
7 'tree/js/notebooklist',
8 ], function(IPython, $, NotebookList) {
8 ], function(IPython, $, NotebookList) {
9 "use strict";
9 "use strict";
10
10
11 var KernelList = function (selector, options, session_list) {
11 var KernelList = function (selector, options, session_list) {
12 NotebookList.call(this, selector, options, 'running', session_list);
12 NotebookList.call(this, selector, options, 'running', session_list);
13 };
13 };
14
14
15 KernelList.prototype = Object.create(NotebookList.prototype);
15 KernelList.prototype = Object.create(NotebookList.prototype);
16
16
17 KernelList.prototype.sessions_loaded = function (d) {
17 KernelList.prototype.sessions_loaded = function (d) {
18 this.sessions = d;
18 this.sessions = d;
19 this.clear_list();
19 this.clear_list();
20 var item;
20 var item;
21 for (var path in d) {
21 for (var path in d) {
22 item = this.new_notebook_item(-1);
22 item = this.new_notebook_item(-1);
23 this.add_link('', path, item);
23 this.add_link('', path, item);
24 this.add_shutdown_button(item, this.sessions[path]);
24 this.add_shutdown_button(item, this.sessions[path]);
25 }
25 }
26
26
27 $('#running_list_header').toggle($.isEmptyObject(d));
27 $('#running_list_header').toggle($.isEmptyObject(d));
28 };
28 };
29
29
30 // Backwards compatability.
30 // Backwards compatability.
31 IPython.KernelList = KernelList;
31 IPython.KernelList = KernelList;
32
32
33 return KernelList;
33 return {'KernelList': KernelList};
34 });
34 });
@@ -1,438 +1,438 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 define([
4 define([
5 'base/js/namespace',
5 'base/js/namespace',
6 'jquery',
6 'jquery',
7 'base/js/utils',
7 'base/js/utils',
8 'base/js/dialog',
8 'base/js/dialog',
9 ], function(IPython, $, utils, Dialog) {
9 ], function(IPython, $, utils, Dialog) {
10 "use strict";
10 "use strict";
11
11
12 var NotebookList = function (selector, options, element_name, session_list) {
12 var NotebookList = function (selector, options, element_name, session_list) {
13 var that = this;
13 var that = this;
14 this.session_list = session_list;
14 this.session_list = session_list;
15 // allow code re-use by just changing element_name in kernellist.js
15 // allow code re-use by just changing element_name in kernellist.js
16 this.element_name = element_name || 'notebook';
16 this.element_name = element_name || 'notebook';
17 this.selector = selector;
17 this.selector = selector;
18 if (this.selector !== undefined) {
18 if (this.selector !== undefined) {
19 this.element = $(selector);
19 this.element = $(selector);
20 this.style();
20 this.style();
21 this.bind_events();
21 this.bind_events();
22 }
22 }
23 this.notebooks_list = [];
23 this.notebooks_list = [];
24 this.sessions = {};
24 this.sessions = {};
25 this.base_url = options.base_url || utils.get_body_data("baseUrl");
25 this.base_url = options.base_url || utils.get_body_data("baseUrl");
26 this.notebook_path = options.notebook_path || utils.get_body_data("notebookPath");
26 this.notebook_path = options.notebook_path || utils.get_body_data("notebookPath");
27 if (this.session_list && this.session_list.events) {
27 if (this.session_list && this.session_list.events) {
28 this.session_list.events.on('sessions_loaded.Dashboard',
28 this.session_list.events.on('sessions_loaded.Dashboard',
29 function(e, d) { that.sessions_loaded(d); });
29 function(e, d) { that.sessions_loaded(d); });
30 }
30 }
31 };
31 };
32
32
33 NotebookList.prototype.style = function () {
33 NotebookList.prototype.style = function () {
34 var prefix = '#' + this.element_name;
34 var prefix = '#' + this.element_name;
35 $(prefix + '_toolbar').addClass('list_toolbar');
35 $(prefix + '_toolbar').addClass('list_toolbar');
36 $(prefix + '_list_info').addClass('toolbar_info');
36 $(prefix + '_list_info').addClass('toolbar_info');
37 $(prefix + '_buttons').addClass('toolbar_buttons');
37 $(prefix + '_buttons').addClass('toolbar_buttons');
38 $(prefix + '_list_header').addClass('list_header');
38 $(prefix + '_list_header').addClass('list_header');
39 this.element.addClass("list_container");
39 this.element.addClass("list_container");
40 };
40 };
41
41
42
42
43 NotebookList.prototype.bind_events = function () {
43 NotebookList.prototype.bind_events = function () {
44 var that = this;
44 var that = this;
45 $('#refresh_' + this.element_name + '_list').click(function () {
45 $('#refresh_' + this.element_name + '_list').click(function () {
46 that.load_sessions();
46 that.load_sessions();
47 });
47 });
48 this.element.bind('dragover', function () {
48 this.element.bind('dragover', function () {
49 return false;
49 return false;
50 });
50 });
51 this.element.bind('drop', function(event){
51 this.element.bind('drop', function(event){
52 that.handleFilesUpload(event,'drop');
52 that.handleFilesUpload(event,'drop');
53 return false;
53 return false;
54 });
54 });
55 };
55 };
56
56
57 NotebookList.prototype.handleFilesUpload = function(event, dropOrForm) {
57 NotebookList.prototype.handleFilesUpload = function(event, dropOrForm) {
58 var that = this;
58 var that = this;
59 var files;
59 var files;
60 if(dropOrForm =='drop'){
60 if(dropOrForm =='drop'){
61 files = event.originalEvent.dataTransfer.files;
61 files = event.originalEvent.dataTransfer.files;
62 } else
62 } else
63 {
63 {
64 files = event.originalEvent.target.files;
64 files = event.originalEvent.target.files;
65 }
65 }
66 for (var i = 0; i < files.length; i++) {
66 for (var i = 0; i < files.length; i++) {
67 var f = files[i];
67 var f = files[i];
68 var reader = new FileReader();
68 var reader = new FileReader();
69 reader.readAsText(f);
69 reader.readAsText(f);
70 var name_and_ext = utils.splitext(f.name);
70 var name_and_ext = utils.splitext(f.name);
71 var file_ext = name_and_ext[1];
71 var file_ext = name_and_ext[1];
72 if (file_ext === '.ipynb') {
72 if (file_ext === '.ipynb') {
73 var item = that.new_notebook_item(0);
73 var item = that.new_notebook_item(0);
74 item.addClass('new-file');
74 item.addClass('new-file');
75 that.add_name_input(f.name, item);
75 that.add_name_input(f.name, item);
76 // Store the notebook item in the reader so we can use it later
76 // Store the notebook item in the reader so we can use it later
77 // to know which item it belongs to.
77 // to know which item it belongs to.
78 $(reader).data('item', item);
78 $(reader).data('item', item);
79 reader.onload = function (event) {
79 reader.onload = function (event) {
80 var nbitem = $(event.target).data('item');
80 var nbitem = $(event.target).data('item');
81 that.add_notebook_data(event.target.result, nbitem);
81 that.add_notebook_data(event.target.result, nbitem);
82 that.add_upload_button(nbitem);
82 that.add_upload_button(nbitem);
83 };
83 };
84 } else {
84 } else {
85 var dialog = 'Uploaded notebooks must be .ipynb files';
85 var dialog = 'Uploaded notebooks must be .ipynb files';
86 Dialog.modal({
86 Dialog.modal({
87 title : 'Invalid file type',
87 title : 'Invalid file type',
88 body : dialog,
88 body : dialog,
89 buttons : {'OK' : {'class' : 'btn-primary'}}
89 buttons : {'OK' : {'class' : 'btn-primary'}}
90 });
90 });
91 }
91 }
92 }
92 }
93 // Replace the file input form wth a clone of itself. This is required to
93 // Replace the file input form wth a clone of itself. This is required to
94 // reset the form. Otherwise, if you upload a file, delete it and try to
94 // reset the form. Otherwise, if you upload a file, delete it and try to
95 // upload it again, the changed event won't fire.
95 // upload it again, the changed event won't fire.
96 var form = $('input.fileinput');
96 var form = $('input.fileinput');
97 form.replaceWith(form.clone(true));
97 form.replaceWith(form.clone(true));
98 return false;
98 return false;
99 };
99 };
100
100
101 NotebookList.prototype.clear_list = function (remove_uploads) {
101 NotebookList.prototype.clear_list = function (remove_uploads) {
102 // Clears the navigation tree.
102 // Clears the navigation tree.
103 //
103 //
104 // Parameters
104 // Parameters
105 // remove_uploads: bool=False
105 // remove_uploads: bool=False
106 // Should upload prompts also be removed from the tree.
106 // Should upload prompts also be removed from the tree.
107 if (remove_uploads) {
107 if (remove_uploads) {
108 this.element.children('.list_item').remove();
108 this.element.children('.list_item').remove();
109 } else {
109 } else {
110 this.element.children('.list_item:not(.new-file)').remove();
110 this.element.children('.list_item:not(.new-file)').remove();
111 }
111 }
112 };
112 };
113
113
114 NotebookList.prototype.load_sessions = function(){
114 NotebookList.prototype.load_sessions = function(){
115 this.session_list.load_sessions();
115 this.session_list.load_sessions();
116 };
116 };
117
117
118
118
119 NotebookList.prototype.sessions_loaded = function(data){
119 NotebookList.prototype.sessions_loaded = function(data){
120 this.sessions = data;
120 this.sessions = data;
121 this.load_list();
121 this.load_list();
122 };
122 };
123
123
124 NotebookList.prototype.load_list = function () {
124 NotebookList.prototype.load_list = function () {
125 var that = this;
125 var that = this;
126 var settings = {
126 var settings = {
127 processData : false,
127 processData : false,
128 cache : false,
128 cache : false,
129 type : "GET",
129 type : "GET",
130 dataType : "json",
130 dataType : "json",
131 success : $.proxy(this.list_loaded, this),
131 success : $.proxy(this.list_loaded, this),
132 error : $.proxy( function(xhr, status, error){
132 error : $.proxy( function(xhr, status, error){
133 utils.log_ajax_error(xhr, status, error);
133 utils.log_ajax_error(xhr, status, error);
134 that.list_loaded([], null, null, {msg:"Error connecting to server."});
134 that.list_loaded([], null, null, {msg:"Error connecting to server."});
135 },this)
135 },this)
136 };
136 };
137
137
138 var url = utils.url_join_encode(
138 var url = utils.url_join_encode(
139 this.base_url,
139 this.base_url,
140 'api',
140 'api',
141 'notebooks',
141 'notebooks',
142 this.notebook_path
142 this.notebook_path
143 );
143 );
144 $.ajax(url, settings);
144 $.ajax(url, settings);
145 };
145 };
146
146
147
147
148 NotebookList.prototype.list_loaded = function (data, status, xhr, param) {
148 NotebookList.prototype.list_loaded = function (data, status, xhr, param) {
149 var message = 'Notebook list empty.';
149 var message = 'Notebook list empty.';
150 if (param !== undefined && param.msg) {
150 if (param !== undefined && param.msg) {
151 message = param.msg;
151 message = param.msg;
152 }
152 }
153 var item = null;
153 var item = null;
154 var len = data.length;
154 var len = data.length;
155 this.clear_list();
155 this.clear_list();
156 if (len === 0) {
156 if (len === 0) {
157 item = this.new_notebook_item(0);
157 item = this.new_notebook_item(0);
158 var span12 = item.children().first();
158 var span12 = item.children().first();
159 span12.empty();
159 span12.empty();
160 span12.append($('<div style="margin:auto;text-align:center;color:grey"/>').text(message));
160 span12.append($('<div style="margin:auto;text-align:center;color:grey"/>').text(message));
161 }
161 }
162 var path = this.notebook_path;
162 var path = this.notebook_path;
163 var offset = 0;
163 var offset = 0;
164 if (path !== '') {
164 if (path !== '') {
165 item = this.new_notebook_item(0);
165 item = this.new_notebook_item(0);
166 this.add_dir(path, '..', item);
166 this.add_dir(path, '..', item);
167 offset = 1;
167 offset = 1;
168 }
168 }
169 for (var i=0; i<len; i++) {
169 for (var i=0; i<len; i++) {
170 if (data[i].type === 'directory') {
170 if (data[i].type === 'directory') {
171 var name = data[i].name;
171 var name = data[i].name;
172 item = this.new_notebook_item(i+offset);
172 item = this.new_notebook_item(i+offset);
173 this.add_dir(path, name, item);
173 this.add_dir(path, name, item);
174 } else {
174 } else {
175 var name = data[i].name;
175 var name = data[i].name;
176 item = this.new_notebook_item(i+offset);
176 item = this.new_notebook_item(i+offset);
177 this.add_link(path, name, item);
177 this.add_link(path, name, item);
178 name = utils.url_path_join(path, name);
178 name = utils.url_path_join(path, name);
179 if(this.sessions[name] === undefined){
179 if(this.sessions[name] === undefined){
180 this.add_delete_button(item);
180 this.add_delete_button(item);
181 } else {
181 } else {
182 this.add_shutdown_button(item,this.sessions[name]);
182 this.add_shutdown_button(item,this.sessions[name]);
183 }
183 }
184 }
184 }
185 }
185 }
186 };
186 };
187
187
188
188
189 NotebookList.prototype.new_notebook_item = function (index) {
189 NotebookList.prototype.new_notebook_item = function (index) {
190 var item = $('<div/>').addClass("list_item").addClass("row");
190 var item = $('<div/>').addClass("list_item").addClass("row");
191 // item.addClass('list_item ui-widget ui-widget-content ui-helper-clearfix');
191 // item.addClass('list_item ui-widget ui-widget-content ui-helper-clearfix');
192 // item.css('border-top-style','none');
192 // item.css('border-top-style','none');
193 item.append($("<div/>").addClass("col-md-12").append(
193 item.append($("<div/>").addClass("col-md-12").append(
194 $('<i/>').addClass('item_icon')
194 $('<i/>').addClass('item_icon')
195 ).append(
195 ).append(
196 $("<a/>").addClass("item_link").append(
196 $("<a/>").addClass("item_link").append(
197 $("<span/>").addClass("item_name")
197 $("<span/>").addClass("item_name")
198 )
198 )
199 ).append(
199 ).append(
200 $('<div/>').addClass("item_buttons btn-group pull-right")
200 $('<div/>').addClass("item_buttons btn-group pull-right")
201 ));
201 ));
202
202
203 if (index === -1) {
203 if (index === -1) {
204 this.element.append(item);
204 this.element.append(item);
205 } else {
205 } else {
206 this.element.children().eq(index).after(item);
206 this.element.children().eq(index).after(item);
207 }
207 }
208 return item;
208 return item;
209 };
209 };
210
210
211
211
212 NotebookList.prototype.add_dir = function (path, name, item) {
212 NotebookList.prototype.add_dir = function (path, name, item) {
213 item.data('name', name);
213 item.data('name', name);
214 item.data('path', path);
214 item.data('path', path);
215 item.find(".item_name").text(name);
215 item.find(".item_name").text(name);
216 item.find(".item_icon").addClass('folder_icon').addClass('icon-fixed-width');
216 item.find(".item_icon").addClass('folder_icon').addClass('icon-fixed-width');
217 item.find("a.item_link")
217 item.find("a.item_link")
218 .attr('href',
218 .attr('href',
219 utils.url_join_encode(
219 utils.url_join_encode(
220 this.base_url,
220 this.base_url,
221 "tree",
221 "tree",
222 path,
222 path,
223 name
223 name
224 )
224 )
225 );
225 );
226 };
226 };
227
227
228
228
229 NotebookList.prototype.add_link = function (path, nbname, item) {
229 NotebookList.prototype.add_link = function (path, nbname, item) {
230 item.data('nbname', nbname);
230 item.data('nbname', nbname);
231 item.data('path', path);
231 item.data('path', path);
232 item.find(".item_name").text(nbname);
232 item.find(".item_name").text(nbname);
233 item.find(".item_icon").addClass('notebook_icon').addClass('icon-fixed-width');
233 item.find(".item_icon").addClass('notebook_icon').addClass('icon-fixed-width');
234 item.find("a.item_link")
234 item.find("a.item_link")
235 .attr('href',
235 .attr('href',
236 utils.url_join_encode(
236 utils.url_join_encode(
237 this.base_url,
237 this.base_url,
238 "notebooks",
238 "notebooks",
239 path,
239 path,
240 nbname
240 nbname
241 )
241 )
242 ).attr('target','_blank');
242 ).attr('target','_blank');
243 };
243 };
244
244
245
245
246 NotebookList.prototype.add_name_input = function (nbname, item) {
246 NotebookList.prototype.add_name_input = function (nbname, item) {
247 item.data('nbname', nbname);
247 item.data('nbname', nbname);
248 item.find(".item_icon").addClass('notebook_icon').addClass('icon-fixed-width');
248 item.find(".item_icon").addClass('notebook_icon').addClass('icon-fixed-width');
249 item.find(".item_name").empty().append(
249 item.find(".item_name").empty().append(
250 $('<input/>')
250 $('<input/>')
251 .addClass("nbname_input")
251 .addClass("nbname_input")
252 .attr('value', utils.splitext(nbname)[0])
252 .attr('value', utils.splitext(nbname)[0])
253 .attr('size', '30')
253 .attr('size', '30')
254 .attr('type', 'text')
254 .attr('type', 'text')
255 );
255 );
256 };
256 };
257
257
258
258
259 NotebookList.prototype.add_notebook_data = function (data, item) {
259 NotebookList.prototype.add_notebook_data = function (data, item) {
260 item.data('nbdata', data);
260 item.data('nbdata', data);
261 };
261 };
262
262
263
263
264 NotebookList.prototype.add_shutdown_button = function (item, session) {
264 NotebookList.prototype.add_shutdown_button = function (item, session) {
265 var that = this;
265 var that = this;
266 var shutdown_button = $("<button/>").text("Shutdown").addClass("btn btn-xs btn-danger").
266 var shutdown_button = $("<button/>").text("Shutdown").addClass("btn btn-xs btn-danger").
267 click(function (e) {
267 click(function (e) {
268 var settings = {
268 var settings = {
269 processData : false,
269 processData : false,
270 cache : false,
270 cache : false,
271 type : "DELETE",
271 type : "DELETE",
272 dataType : "json",
272 dataType : "json",
273 success : function () {
273 success : function () {
274 that.load_sessions();
274 that.load_sessions();
275 },
275 },
276 error : utils.log_ajax_error,
276 error : utils.log_ajax_error,
277 };
277 };
278 var url = utils.url_join_encode(
278 var url = utils.url_join_encode(
279 that.base_url,
279 that.base_url,
280 'api/sessions',
280 'api/sessions',
281 session
281 session
282 );
282 );
283 $.ajax(url, settings);
283 $.ajax(url, settings);
284 return false;
284 return false;
285 });
285 });
286 // var new_buttons = item.find('a'); // shutdown_button;
286 // var new_buttons = item.find('a'); // shutdown_button;
287 item.find(".item_buttons").text("").append(shutdown_button);
287 item.find(".item_buttons").text("").append(shutdown_button);
288 };
288 };
289
289
290 NotebookList.prototype.add_delete_button = function (item) {
290 NotebookList.prototype.add_delete_button = function (item) {
291 var new_buttons = $('<span/>').addClass("btn-group pull-right");
291 var new_buttons = $('<span/>').addClass("btn-group pull-right");
292 var notebooklist = this;
292 var notebooklist = this;
293 var delete_button = $("<button/>").text("Delete").addClass("btn btn-default btn-xs").
293 var delete_button = $("<button/>").text("Delete").addClass("btn btn-default btn-xs").
294 click(function (e) {
294 click(function (e) {
295 // $(this) is the button that was clicked.
295 // $(this) is the button that was clicked.
296 var that = $(this);
296 var that = $(this);
297 // We use the nbname and notebook_id from the parent notebook_item element's
297 // We use the nbname and notebook_id from the parent notebook_item element's
298 // data because the outer scopes values change as we iterate through the loop.
298 // data because the outer scopes values change as we iterate through the loop.
299 var parent_item = that.parents('div.list_item');
299 var parent_item = that.parents('div.list_item');
300 var nbname = parent_item.data('nbname');
300 var nbname = parent_item.data('nbname');
301 var message = 'Are you sure you want to permanently delete the notebook: ' + nbname + '?';
301 var message = 'Are you sure you want to permanently delete the notebook: ' + nbname + '?';
302 Dialog.modal({
302 Dialog.modal({
303 title : "Delete notebook",
303 title : "Delete notebook",
304 body : message,
304 body : message,
305 buttons : {
305 buttons : {
306 Delete : {
306 Delete : {
307 class: "btn-danger",
307 class: "btn-danger",
308 click: function() {
308 click: function() {
309 var settings = {
309 var settings = {
310 processData : false,
310 processData : false,
311 cache : false,
311 cache : false,
312 type : "DELETE",
312 type : "DELETE",
313 dataType : "json",
313 dataType : "json",
314 success : function (data, status, xhr) {
314 success : function (data, status, xhr) {
315 parent_item.remove();
315 parent_item.remove();
316 },
316 },
317 error : utils.log_ajax_error,
317 error : utils.log_ajax_error,
318 };
318 };
319 var url = utils.url_join_encode(
319 var url = utils.url_join_encode(
320 notebooklist.base_url,
320 notebooklist.base_url,
321 'api/notebooks',
321 'api/notebooks',
322 notebooklist.notebook_path,
322 notebooklist.notebook_path,
323 nbname
323 nbname
324 );
324 );
325 $.ajax(url, settings);
325 $.ajax(url, settings);
326 }
326 }
327 },
327 },
328 Cancel : {}
328 Cancel : {}
329 }
329 }
330 });
330 });
331 return false;
331 return false;
332 });
332 });
333 item.find(".item_buttons").text("").append(delete_button);
333 item.find(".item_buttons").text("").append(delete_button);
334 };
334 };
335
335
336
336
337 NotebookList.prototype.add_upload_button = function (item) {
337 NotebookList.prototype.add_upload_button = function (item) {
338 var that = this;
338 var that = this;
339 var upload_button = $('<button/>').text("Upload")
339 var upload_button = $('<button/>').text("Upload")
340 .addClass('btn btn-primary btn-xs upload_button')
340 .addClass('btn btn-primary btn-xs upload_button')
341 .click(function (e) {
341 .click(function (e) {
342 var nbname = item.find('.item_name > input').val();
342 var nbname = item.find('.item_name > input').val();
343 if (nbname.slice(nbname.length-6, nbname.length) != ".ipynb") {
343 if (nbname.slice(nbname.length-6, nbname.length) != ".ipynb") {
344 nbname = nbname + ".ipynb";
344 nbname = nbname + ".ipynb";
345 }
345 }
346 var path = that.notebook_path;
346 var path = that.notebook_path;
347 var nbdata = item.data('nbdata');
347 var nbdata = item.data('nbdata');
348 var content_type = 'application/json';
348 var content_type = 'application/json';
349 var model = {
349 var model = {
350 content : JSON.parse(nbdata),
350 content : JSON.parse(nbdata),
351 };
351 };
352 var settings = {
352 var settings = {
353 processData : false,
353 processData : false,
354 cache : false,
354 cache : false,
355 type : 'PUT',
355 type : 'PUT',
356 dataType : 'json',
356 dataType : 'json',
357 data : JSON.stringify(model),
357 data : JSON.stringify(model),
358 headers : {'Content-Type': content_type},
358 headers : {'Content-Type': content_type},
359 success : function (data, status, xhr) {
359 success : function (data, status, xhr) {
360 that.add_link(path, nbname, item);
360 that.add_link(path, nbname, item);
361 that.add_delete_button(item);
361 that.add_delete_button(item);
362 },
362 },
363 error : utils.log_ajax_error,
363 error : utils.log_ajax_error,
364 };
364 };
365
365
366 var url = utils.url_join_encode(
366 var url = utils.url_join_encode(
367 that.base_url,
367 that.base_url,
368 'api/notebooks',
368 'api/notebooks',
369 that.notebook_path,
369 that.notebook_path,
370 nbname
370 nbname
371 );
371 );
372 $.ajax(url, settings);
372 $.ajax(url, settings);
373 return false;
373 return false;
374 });
374 });
375 var cancel_button = $('<button/>').text("Cancel")
375 var cancel_button = $('<button/>').text("Cancel")
376 .addClass("btn btn-default btn-xs")
376 .addClass("btn btn-default btn-xs")
377 .click(function (e) {
377 .click(function (e) {
378 console.log('cancel click');
378 console.log('cancel click');
379 item.remove();
379 item.remove();
380 return false;
380 return false;
381 });
381 });
382 item.find(".item_buttons").empty()
382 item.find(".item_buttons").empty()
383 .append(upload_button)
383 .append(upload_button)
384 .append(cancel_button);
384 .append(cancel_button);
385 };
385 };
386
386
387
387
388 NotebookList.prototype.new_notebook = function(){
388 NotebookList.prototype.new_notebook = function(){
389 var path = this.notebook_path;
389 var path = this.notebook_path;
390 var base_url = this.base_url;
390 var base_url = this.base_url;
391 var settings = {
391 var settings = {
392 processData : false,
392 processData : false,
393 cache : false,
393 cache : false,
394 type : "POST",
394 type : "POST",
395 dataType : "json",
395 dataType : "json",
396 async : false,
396 async : false,
397 success : function (data, status, xhr) {
397 success : function (data, status, xhr) {
398 var notebook_name = data.name;
398 var notebook_name = data.name;
399 window.open(
399 window.open(
400 utils.url_join_encode(
400 utils.url_join_encode(
401 base_url,
401 base_url,
402 'notebooks',
402 'notebooks',
403 path,
403 path,
404 notebook_name),
404 notebook_name),
405 '_blank'
405 '_blank'
406 );
406 );
407 },
407 },
408 error : $.proxy(this.new_notebook_failed, this),
408 error : $.proxy(this.new_notebook_failed, this),
409 };
409 };
410 var url = utils.url_join_encode(
410 var url = utils.url_join_encode(
411 base_url,
411 base_url,
412 'api/notebooks',
412 'api/notebooks',
413 path
413 path
414 );
414 );
415 $.ajax(url, settings);
415 $.ajax(url, settings);
416 };
416 };
417
417
418
418
419 NotebookList.prototype.new_notebook_failed = function (xhr, status, error) {
419 NotebookList.prototype.new_notebook_failed = function (xhr, status, error) {
420 utils.log_ajax_error(xhr, status, error);
420 utils.log_ajax_error(xhr, status, error);
421 var msg;
421 var msg;
422 if (xhr.responseJSON && xhr.responseJSON.message) {
422 if (xhr.responseJSON && xhr.responseJSON.message) {
423 msg = xhr.responseJSON.message;
423 msg = xhr.responseJSON.message;
424 } else {
424 } else {
425 msg = xhr.statusText;
425 msg = xhr.statusText;
426 }
426 }
427 Dialog.modal({
427 Dialog.modal({
428 title : 'Creating Notebook Failed',
428 title : 'Creating Notebook Failed',
429 body : "The error was: " + msg,
429 body : "The error was: " + msg,
430 buttons : {'OK' : {'class' : 'btn-primary'}}
430 buttons : {'OK' : {'class' : 'btn-primary'}}
431 });
431 });
432 };
432 };
433
433
434 // Backwards compatability.
434 // Backwards compatability.
435 IPython.NotebookList = NotebookList;
435 IPython.NotebookList = NotebookList;
436
436
437 return NotebookList;
437 return {'NotebookList': NotebookList};
438 });
438 });
@@ -1,49 +1,49 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 define([
4 define([
5 'base/js/namespace',
5 'base/js/namespace',
6 'jquery',
6 'jquery',
7 'base/js/utils',
7 'base/js/utils',
8 ], function(IPython, $, utils) {
8 ], function(IPython, $, utils) {
9 "use strict";
9 "use strict";
10
10
11 var SesssionList = function (options, events) {
11 var SesssionList = function (options, events) {
12 this.events = events;
12 this.events = events;
13 this.sessions = {};
13 this.sessions = {};
14 this.base_url = options.base_url || utils.get_body_data("baseUrl");
14 this.base_url = options.base_url || utils.get_body_data("baseUrl");
15 };
15 };
16
16
17 SesssionList.prototype.load_sessions = function(){
17 SesssionList.prototype.load_sessions = function(){
18 var that = this;
18 var that = this;
19 var settings = {
19 var settings = {
20 processData : false,
20 processData : false,
21 cache : false,
21 cache : false,
22 type : "GET",
22 type : "GET",
23 dataType : "json",
23 dataType : "json",
24 success : $.proxy(that.sessions_loaded, this),
24 success : $.proxy(that.sessions_loaded, this),
25 error : utils.log_ajax_error,
25 error : utils.log_ajax_error,
26 };
26 };
27 var url = utils.url_join_encode(this.base_url, 'api/sessions');
27 var url = utils.url_join_encode(this.base_url, 'api/sessions');
28 $.ajax(url, settings);
28 $.ajax(url, settings);
29 };
29 };
30
30
31 SesssionList.prototype.sessions_loaded = function(data){
31 SesssionList.prototype.sessions_loaded = function(data){
32 this.sessions = {};
32 this.sessions = {};
33 var len = data.length;
33 var len = data.length;
34 var nb_path;
34 var nb_path;
35 for (var i=0; i<len; i++) {
35 for (var i=0; i<len; i++) {
36 nb_path = utils.url_path_join(
36 nb_path = utils.url_path_join(
37 data[i].notebook.path,
37 data[i].notebook.path,
38 data[i].notebook.name
38 data[i].notebook.name
39 );
39 );
40 this.sessions[nb_path] = data[i].id;
40 this.sessions[nb_path] = data[i].id;
41 }
41 }
42 this.events.trigger('sessions_loaded.Dashboard', this.sessions);
42 this.events.trigger('sessions_loaded.Dashboard', this.sessions);
43 };
43 };
44
44
45 // Backwards compatability.
45 // Backwards compatability.
46 IPython.SesssionList = SesssionList;
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