##// END OF EJS Templates
Ran function comment conversion tool
Jonathan Frederic -
Show More

The requested changes are too big and content was truncated. Show full diff

@@ -1,174 +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 'codemirror/lib/codemirror',
7 'codemirror/lib/codemirror',
8 ], function(IPython, $, CodeMirror) {
8 ], function(IPython, $, CodeMirror) {
9 "use strict";
9 "use strict";
10
10
11 var modal = function (options) {
11 var modal = function (options) {
12
12
13 var modal = $("<div/>")
13 var modal = $("<div/>")
14 .addClass("modal")
14 .addClass("modal")
15 .addClass("fade")
15 .addClass("fade")
16 .attr("role", "dialog");
16 .attr("role", "dialog");
17 var dialog = $("<div/>")
17 var dialog = $("<div/>")
18 .addClass("modal-dialog")
18 .addClass("modal-dialog")
19 .appendTo(modal);
19 .appendTo(modal);
20 var dialog_content = $("<div/>")
20 var dialog_content = $("<div/>")
21 .addClass("modal-content")
21 .addClass("modal-content")
22 .appendTo(dialog);
22 .appendTo(dialog);
23 dialog_content.append(
23 dialog_content.append(
24 $("<div/>")
24 $("<div/>")
25 .addClass("modal-header")
25 .addClass("modal-header")
26 .append($("<button>")
26 .append($("<button>")
27 .attr("type", "button")
27 .attr("type", "button")
28 .addClass("close")
28 .addClass("close")
29 .attr("data-dismiss", "modal")
29 .attr("data-dismiss", "modal")
30 .attr("aria-hidden", "true")
30 .attr("aria-hidden", "true")
31 .html("&times;")
31 .html("&times;")
32 ).append(
32 ).append(
33 $("<h4/>")
33 $("<h4/>")
34 .addClass('modal-title')
34 .addClass('modal-title')
35 .text(options.title || "")
35 .text(options.title || "")
36 )
36 )
37 ).append(
37 ).append(
38 $("<div/>").addClass("modal-body").append(
38 $("<div/>").addClass("modal-body").append(
39 options.body || $("<p/>")
39 options.body || $("<p/>")
40 )
40 )
41 );
41 );
42
42
43 var footer = $("<div/>").addClass("modal-footer");
43 var footer = $("<div/>").addClass("modal-footer");
44
44
45 for (var label in options.buttons) {
45 for (var label in options.buttons) {
46 var btn_opts = options.buttons[label];
46 var btn_opts = options.buttons[label];
47 var button = $("<button/>")
47 var button = $("<button/>")
48 .addClass("btn btn-default btn-sm")
48 .addClass("btn btn-default btn-sm")
49 .attr("data-dismiss", "modal")
49 .attr("data-dismiss", "modal")
50 .text(label);
50 .text(label);
51 if (btn_opts.click) {
51 if (btn_opts.click) {
52 button.click($.proxy(btn_opts.click, dialog_content));
52 button.click($.proxy(btn_opts.click, dialog_content));
53 }
53 }
54 if (btn_opts.class) {
54 if (btn_opts.class) {
55 button.addClass(btn_opts.class);
55 button.addClass(btn_opts.class);
56 }
56 }
57 footer.append(button);
57 footer.append(button);
58 }
58 }
59 dialog_content.append(footer);
59 dialog_content.append(footer);
60 // hook up on-open event
60 // hook up on-open event
61 modal.on("shown.bs.modal", function() {
61 modal.on("shown.bs.modal", function() {
62 setTimeout(function() {
62 setTimeout(function() {
63 footer.find("button").last().focus();
63 footer.find("button").last().focus();
64 if (options.open) {
64 if (options.open) {
65 $.proxy(options.open, modal)();
65 $.proxy(options.open, modal)();
66 }
66 }
67 }, 0);
67 }, 0);
68 });
68 });
69
69
70 // destroy modal on hide, unless explicitly asked not to
70 // destroy modal on hide, unless explicitly asked not to
71 if (options.destroy === undefined || options.destroy) {
71 if (options.destroy === undefined || options.destroy) {
72 modal.on("hidden.bs.modal", function () {
72 modal.on("hidden.bs.modal", function () {
73 modal.remove();
73 modal.remove();
74 });
74 });
75 }
75 }
76 modal.on("hidden.bs.modal", function () {
76 modal.on("hidden.bs.modal", function () {
77 if (options.notebook) {
77 if (options.notebook) {
78 var cell = options.notebook.get_selected_cell();
78 var cell = options.notebook.get_selected_cell();
79 if (cell) cell.select();
79 if (cell) cell.select();
80 }
80 }
81 if (options.keyboard_manager) {
81 if (options.keyboard_manager) {
82 options.keyboard_manager.enable();
82 options.keyboard_manager.enable();
83 options.keyboard_manager.command_mode();
83 options.keyboard_manager.command_mode();
84 }
84 }
85 });
85 });
86
86
87 if (options.keyboard_manager) {
87 if (options.keyboard_manager) {
88 options.keyboard_manager.disable();
88 options.keyboard_manager.disable();
89 }
89 }
90
90
91 return modal.modal(options);
91 return modal.modal(options);
92 };
92 };
93
93
94 var kernel_modal = function (options) {
94 var kernel_modal = function (options) {
95 // only one kernel dialog should be open at a time -- but
95 /**
96 // other modal dialogs can still be open
96 * only one kernel dialog should be open at a time -- but
97 * other modal dialogs can still be open
98 */
97 $('.kernel-modal').modal('hide');
99 $('.kernel-modal').modal('hide');
98 var dialog = modal(options);
100 var dialog = modal(options);
99 dialog.addClass('kernel-modal');
101 dialog.addClass('kernel-modal');
100 return dialog;
102 return dialog;
101 };
103 };
102
104
103 var edit_metadata = function (options) {
105 var edit_metadata = function (options) {
104 options.name = options.name || "Cell";
106 options.name = options.name || "Cell";
105 var error_div = $('<div/>').css('color', 'red');
107 var error_div = $('<div/>').css('color', 'red');
106 var message =
108 var message =
107 "Manually edit the JSON below to manipulate the metadata for this " + options.name + "." +
109 "Manually edit the JSON below to manipulate the metadata for this " + options.name + "." +
108 " We recommend putting custom metadata attributes in an appropriately named sub-structure," +
110 " We recommend putting custom metadata attributes in an appropriately named sub-structure," +
109 " so they don't conflict with those of others.";
111 " so they don't conflict with those of others.";
110
112
111 var textarea = $('<textarea/>')
113 var textarea = $('<textarea/>')
112 .attr('rows', '13')
114 .attr('rows', '13')
113 .attr('cols', '80')
115 .attr('cols', '80')
114 .attr('name', 'metadata')
116 .attr('name', 'metadata')
115 .text(JSON.stringify(options.md || {}, null, 2));
117 .text(JSON.stringify(options.md || {}, null, 2));
116
118
117 var dialogform = $('<div/>').attr('title', 'Edit the metadata')
119 var dialogform = $('<div/>').attr('title', 'Edit the metadata')
118 .append(
120 .append(
119 $('<form/>').append(
121 $('<form/>').append(
120 $('<fieldset/>').append(
122 $('<fieldset/>').append(
121 $('<label/>')
123 $('<label/>')
122 .attr('for','metadata')
124 .attr('for','metadata')
123 .text(message)
125 .text(message)
124 )
126 )
125 .append(error_div)
127 .append(error_div)
126 .append($('<br/>'))
128 .append($('<br/>'))
127 .append(textarea)
129 .append(textarea)
128 )
130 )
129 );
131 );
130 var editor = CodeMirror.fromTextArea(textarea[0], {
132 var editor = CodeMirror.fromTextArea(textarea[0], {
131 lineNumbers: true,
133 lineNumbers: true,
132 matchBrackets: true,
134 matchBrackets: true,
133 indentUnit: 2,
135 indentUnit: 2,
134 autoIndent: true,
136 autoIndent: true,
135 mode: 'application/json',
137 mode: 'application/json',
136 });
138 });
137 var modal_obj = modal({
139 var modal_obj = modal({
138 title: "Edit " + options.name + " Metadata",
140 title: "Edit " + options.name + " Metadata",
139 body: dialogform,
141 body: dialogform,
140 buttons: {
142 buttons: {
141 OK: { class : "btn-primary",
143 OK: { class : "btn-primary",
142 click: function() {
144 click: function() {
143 // validate json and set it
145 /**
146 * validate json and set it
147 */
144 var new_md;
148 var new_md;
145 try {
149 try {
146 new_md = JSON.parse(editor.getValue());
150 new_md = JSON.parse(editor.getValue());
147 } catch(e) {
151 } catch(e) {
148 console.log(e);
152 console.log(e);
149 error_div.text('WARNING: Could not save invalid JSON.');
153 error_div.text('WARNING: Could not save invalid JSON.');
150 return false;
154 return false;
151 }
155 }
152 options.callback(new_md);
156 options.callback(new_md);
153 }
157 }
154 },
158 },
155 Cancel: {}
159 Cancel: {}
156 },
160 },
157 notebook: options.notebook,
161 notebook: options.notebook,
158 keyboard_manager: options.keyboard_manager,
162 keyboard_manager: options.keyboard_manager,
159 });
163 });
160
164
161 modal_obj.on('shown.bs.modal', function(){ editor.refresh(); });
165 modal_obj.on('shown.bs.modal', function(){ editor.refresh(); });
162 };
166 };
163
167
164 var dialog = {
168 var dialog = {
165 modal : modal,
169 modal : modal,
166 kernel_modal : kernel_modal,
170 kernel_modal : kernel_modal,
167 edit_metadata : edit_metadata,
171 edit_metadata : edit_metadata,
168 };
172 };
169
173
170 // Backwards compatability.
174 // Backwards compatability.
171 IPython.dialog = dialog;
175 IPython.dialog = dialog;
172
176
173 return dialog;
177 return dialog;
174 });
178 });
@@ -1,41 +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 'jquery',
6 'jquery',
7 ], function(IPython, $){
7 ], function(IPython, $){
8 "use strict";
8 "use strict";
9
9
10 var Page = function () {
10 var Page = function () {
11 this.bind_events();
11 this.bind_events();
12 };
12 };
13
13
14 Page.prototype.bind_events = function () {
14 Page.prototype.bind_events = function () {
15 };
15 };
16
16
17 Page.prototype.show = function () {
17 Page.prototype.show = function () {
18 // The header and site divs start out hidden to prevent FLOUC.
18 /**
19 // Main scripts should call this method after styling everything.
19 * The header and site divs start out hidden to prevent FLOUC.
20 * Main scripts should call this method after styling everything.
21 */
20 this.show_header();
22 this.show_header();
21 this.show_site();
23 this.show_site();
22 };
24 };
23
25
24 Page.prototype.show_header = function () {
26 Page.prototype.show_header = function () {
25 // The header and site divs start out hidden to prevent FLOUC.
27 /**
26 // Main scripts should call this method after styling everything.
28 * The header and site divs start out hidden to prevent FLOUC.
27 // TODO: selector are hardcoded, pass as constructor argument
29 * Main scripts should call this method after styling everything.
30 * TODO: selector are hardcoded, pass as constructor argument
31 */
28 $('div#header').css('display','block');
32 $('div#header').css('display','block');
29 };
33 };
30
34
31 Page.prototype.show_site = function () {
35 Page.prototype.show_site = function () {
32 // The header and site divs start out hidden to prevent FLOUC.
36 /**
33 // Main scripts should call this method after styling everything.
37 * The header and site divs start out hidden to prevent FLOUC.
34 // TODO: selector are hardcoded, pass as constructor argument
38 * Main scripts should call this method after styling everything.
39 * TODO: selector are hardcoded, pass as constructor argument
40 */
35 $('div#site').css('display','block');
41 $('div#site').css('display','block');
36 };
42 };
37
43
38 // Register self in the global namespace for convenience.
44 // Register self in the global namespace for convenience.
39 IPython.Page = Page;
45 IPython.Page = Page;
40 return {'Page': Page};
46 return {'Page': Page};
41 });
47 });
@@ -1,121 +1,129 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/google-caja/html-css-sanitizer-minified',
7 'components/google-caja/html-css-sanitizer-minified',
8 ], function(IPython, $) {
8 ], function(IPython, $) {
9 "use strict";
9 "use strict";
10
10
11 var noop = function (x) { return x; };
11 var noop = function (x) { return x; };
12
12
13 var caja;
13 var caja;
14 if (window && window.html) {
14 if (window && window.html) {
15 caja = window.html;
15 caja = window.html;
16 caja.html4 = window.html4;
16 caja.html4 = window.html4;
17 caja.sanitizeStylesheet = window.sanitizeStylesheet;
17 caja.sanitizeStylesheet = window.sanitizeStylesheet;
18 }
18 }
19
19
20 var sanitizeAttribs = function (tagName, attribs, opt_naiveUriRewriter, opt_nmTokenPolicy, opt_logger) {
20 var sanitizeAttribs = function (tagName, attribs, opt_naiveUriRewriter, opt_nmTokenPolicy, opt_logger) {
21 // add trusting data-attributes to the default sanitizeAttribs from caja
21 /**
22 // this function is mostly copied from the caja source
22 * add trusting data-attributes to the default sanitizeAttribs from caja
23 * this function is mostly copied from the caja source
24 */
23 var ATTRIBS = caja.html4.ATTRIBS;
25 var ATTRIBS = caja.html4.ATTRIBS;
24 for (var i = 0; i < attribs.length; i += 2) {
26 for (var i = 0; i < attribs.length; i += 2) {
25 var attribName = attribs[i];
27 var attribName = attribs[i];
26 if (attribName.substr(0,5) == 'data-') {
28 if (attribName.substr(0,5) == 'data-') {
27 var attribKey = '*::' + attribName;
29 var attribKey = '*::' + attribName;
28 if (!ATTRIBS.hasOwnProperty(attribKey)) {
30 if (!ATTRIBS.hasOwnProperty(attribKey)) {
29 ATTRIBS[attribKey] = 0;
31 ATTRIBS[attribKey] = 0;
30 }
32 }
31 }
33 }
32 }
34 }
33 return caja.sanitizeAttribs(tagName, attribs, opt_naiveUriRewriter, opt_nmTokenPolicy, opt_logger);
35 return caja.sanitizeAttribs(tagName, attribs, opt_naiveUriRewriter, opt_nmTokenPolicy, opt_logger);
34 };
36 };
35
37
36 var sanitize_css = function (css, tagPolicy) {
38 var sanitize_css = function (css, tagPolicy) {
37 // sanitize CSS
39 /**
38 // like sanitize_html, but for CSS
40 * sanitize CSS
39 // called by sanitize_stylesheets
41 * like sanitize_html, but for CSS
42 * called by sanitize_stylesheets
43 */
40 return caja.sanitizeStylesheet(
44 return caja.sanitizeStylesheet(
41 window.location.pathname,
45 window.location.pathname,
42 css,
46 css,
43 {
47 {
44 containerClass: null,
48 containerClass: null,
45 idSuffix: '',
49 idSuffix: '',
46 tagPolicy: tagPolicy,
50 tagPolicy: tagPolicy,
47 virtualizeAttrName: noop
51 virtualizeAttrName: noop
48 },
52 },
49 noop
53 noop
50 );
54 );
51 };
55 };
52
56
53 var sanitize_stylesheets = function (html, tagPolicy) {
57 var sanitize_stylesheets = function (html, tagPolicy) {
54 // sanitize just the css in style tags in a block of html
58 /**
55 // called by sanitize_html, if allow_css is true
59 * sanitize just the css in style tags in a block of html
60 * called by sanitize_html, if allow_css is true
61 */
56 var h = $("<div/>").append(html);
62 var h = $("<div/>").append(html);
57 var style_tags = h.find("style");
63 var style_tags = h.find("style");
58 if (!style_tags.length) {
64 if (!style_tags.length) {
59 // no style tags to sanitize
65 // no style tags to sanitize
60 return html;
66 return html;
61 }
67 }
62 style_tags.each(function(i, style) {
68 style_tags.each(function(i, style) {
63 style.innerHTML = sanitize_css(style.innerHTML, tagPolicy);
69 style.innerHTML = sanitize_css(style.innerHTML, tagPolicy);
64 });
70 });
65 return h.html();
71 return h.html();
66 };
72 };
67
73
68 var sanitize_html = function (html, allow_css) {
74 var sanitize_html = function (html, allow_css) {
69 // sanitize HTML
75 /**
70 // if allow_css is true (default: false), CSS is sanitized as well.
76 * sanitize HTML
71 // otherwise, CSS elements and attributes are simply removed.
77 * if allow_css is true (default: false), CSS is sanitized as well.
78 * otherwise, CSS elements and attributes are simply removed.
79 */
72 var html4 = caja.html4;
80 var html4 = caja.html4;
73
81
74 if (allow_css) {
82 if (allow_css) {
75 // allow sanitization of style tags,
83 // allow sanitization of style tags,
76 // not just scrubbing
84 // not just scrubbing
77 html4.ELEMENTS.style &= ~html4.eflags.UNSAFE;
85 html4.ELEMENTS.style &= ~html4.eflags.UNSAFE;
78 html4.ATTRIBS.style = html4.atype.STYLE;
86 html4.ATTRIBS.style = html4.atype.STYLE;
79 } else {
87 } else {
80 // scrub all CSS
88 // scrub all CSS
81 html4.ELEMENTS.style |= html4.eflags.UNSAFE;
89 html4.ELEMENTS.style |= html4.eflags.UNSAFE;
82 html4.ATTRIBS.style = html4.atype.SCRIPT;
90 html4.ATTRIBS.style = html4.atype.SCRIPT;
83 }
91 }
84
92
85 var record_messages = function (msg, opts) {
93 var record_messages = function (msg, opts) {
86 console.log("HTML Sanitizer", msg, opts);
94 console.log("HTML Sanitizer", msg, opts);
87 };
95 };
88
96
89 var policy = function (tagName, attribs) {
97 var policy = function (tagName, attribs) {
90 if (!(html4.ELEMENTS[tagName] & html4.eflags.UNSAFE)) {
98 if (!(html4.ELEMENTS[tagName] & html4.eflags.UNSAFE)) {
91 return {
99 return {
92 'attribs': sanitizeAttribs(tagName, attribs,
100 'attribs': sanitizeAttribs(tagName, attribs,
93 noop, noop, record_messages)
101 noop, noop, record_messages)
94 };
102 };
95 } else {
103 } else {
96 record_messages(tagName + " removed", {
104 record_messages(tagName + " removed", {
97 change: "removed",
105 change: "removed",
98 tagName: tagName
106 tagName: tagName
99 });
107 });
100 }
108 }
101 };
109 };
102
110
103 var sanitized = caja.sanitizeWithPolicy(html, policy);
111 var sanitized = caja.sanitizeWithPolicy(html, policy);
104
112
105 if (allow_css) {
113 if (allow_css) {
106 // sanitize style tags as stylesheets
114 // sanitize style tags as stylesheets
107 sanitized = sanitize_stylesheets(result.sanitized, policy);
115 sanitized = sanitize_stylesheets(result.sanitized, policy);
108 }
116 }
109
117
110 return sanitized;
118 return sanitized;
111 };
119 };
112
120
113 var security = {
121 var security = {
114 caja: caja,
122 caja: caja,
115 sanitize_html: sanitize_html
123 sanitize_html: sanitize_html
116 };
124 };
117
125
118 IPython.security = security;
126 IPython.security = security;
119
127
120 return security;
128 return security;
121 });
129 });
@@ -1,760 +1,810 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 'codemirror/lib/codemirror',
7 'codemirror/lib/codemirror',
8 ], function(IPython, $, CodeMirror){
8 ], function(IPython, $, CodeMirror){
9 "use strict";
9 "use strict";
10
10
11 IPython.load_extensions = function () {
11 IPython.load_extensions = function () {
12 // load one or more IPython notebook extensions with requirejs
12 // load one or more IPython notebook extensions with requirejs
13
13
14 var extensions = [];
14 var extensions = [];
15 var extension_names = arguments;
15 var extension_names = arguments;
16 for (var i = 0; i < extension_names.length; i++) {
16 for (var i = 0; i < extension_names.length; i++) {
17 extensions.push("nbextensions/" + arguments[i]);
17 extensions.push("nbextensions/" + arguments[i]);
18 }
18 }
19
19
20 require(extensions,
20 require(extensions,
21 function () {
21 function () {
22 for (var i = 0; i < arguments.length; i++) {
22 for (var i = 0; i < arguments.length; i++) {
23 var ext = arguments[i];
23 var ext = arguments[i];
24 var ext_name = extension_names[i];
24 var ext_name = extension_names[i];
25 // success callback
25 // success callback
26 console.log("Loaded extension: " + ext_name);
26 console.log("Loaded extension: " + ext_name);
27 if (ext && ext.load_ipython_extension !== undefined) {
27 if (ext && ext.load_ipython_extension !== undefined) {
28 ext.load_ipython_extension();
28 ext.load_ipython_extension();
29 }
29 }
30 }
30 }
31 },
31 },
32 function (err) {
32 function (err) {
33 // failure callback
33 // failure callback
34 console.log("Failed to load extension(s):", err.requireModules, err);
34 console.log("Failed to load extension(s):", err.requireModules, err);
35 }
35 }
36 );
36 );
37 };
37 };
38
38
39 //============================================================================
39 //============================================================================
40 // Cross-browser RegEx Split
40 // Cross-browser RegEx Split
41 //============================================================================
41 //============================================================================
42
42
43 // This code has been MODIFIED from the code licensed below to not replace the
43 // This code has been MODIFIED from the code licensed below to not replace the
44 // default browser split. The license is reproduced here.
44 // default browser split. The license is reproduced here.
45
45
46 // see http://blog.stevenlevithan.com/archives/cross-browser-split for more info:
46 // see http://blog.stevenlevithan.com/archives/cross-browser-split for more info:
47 /*!
47 /*!
48 * Cross-Browser Split 1.1.1
48 * Cross-Browser Split 1.1.1
49 * Copyright 2007-2012 Steven Levithan <stevenlevithan.com>
49 * Copyright 2007-2012 Steven Levithan <stevenlevithan.com>
50 * Available under the MIT License
50 * Available under the MIT License
51 * ECMAScript compliant, uniform cross-browser split method
51 * ECMAScript compliant, uniform cross-browser split method
52 */
52 */
53
53
54 /**
54 /**
55 * Splits a string into an array of strings using a regex or string
55 * Splits a string into an array of strings using a regex or string
56 * separator. Matches of the separator are not included in the result array.
56 * separator. Matches of the separator are not included in the result array.
57 * However, if `separator` is a regex that contains capturing groups,
57 * However, if `separator` is a regex that contains capturing groups,
58 * backreferences are spliced into the result each time `separator` is
58 * backreferences are spliced into the result each time `separator` is
59 * matched. Fixes browser bugs compared to the native
59 * matched. Fixes browser bugs compared to the native
60 * `String.prototype.split` and can be used reliably cross-browser.
60 * `String.prototype.split` and can be used reliably cross-browser.
61 * @param {String} str String to split.
61 * @param {String} str String to split.
62 * @param {RegExp|String} separator Regex or string to use for separating
62 * @param {RegExp|String} separator Regex or string to use for separating
63 * the string.
63 * the string.
64 * @param {Number} [limit] Maximum number of items to include in the result
64 * @param {Number} [limit] Maximum number of items to include in the result
65 * array.
65 * array.
66 * @returns {Array} Array of substrings.
66 * @returns {Array} Array of substrings.
67 * @example
67 * @example
68 *
68 *
69 * // Basic use
69 * // Basic use
70 * regex_split('a b c d', ' ');
70 * regex_split('a b c d', ' ');
71 * // -> ['a', 'b', 'c', 'd']
71 * // -> ['a', 'b', 'c', 'd']
72 *
72 *
73 * // With limit
73 * // With limit
74 * regex_split('a b c d', ' ', 2);
74 * regex_split('a b c d', ' ', 2);
75 * // -> ['a', 'b']
75 * // -> ['a', 'b']
76 *
76 *
77 * // Backreferences in result array
77 * // Backreferences in result array
78 * regex_split('..word1 word2..', /([a-z]+)(\d+)/i);
78 * regex_split('..word1 word2..', /([a-z]+)(\d+)/i);
79 * // -> ['..', 'word', '1', ' ', 'word', '2', '..']
79 * // -> ['..', 'word', '1', ' ', 'word', '2', '..']
80 */
80 */
81 var regex_split = function (str, separator, limit) {
81 var regex_split = function (str, separator, limit) {
82 // If `separator` is not a regex, use `split`
82 /**
83 * If `separator` is not a regex, use `split`
84 */
83 if (Object.prototype.toString.call(separator) !== "[object RegExp]") {
85 if (Object.prototype.toString.call(separator) !== "[object RegExp]") {
84 return split.call(str, separator, limit);
86 return split.call(str, separator, limit);
85 }
87 }
86 var output = [],
88 var output = [],
87 flags = (separator.ignoreCase ? "i" : "") +
89 flags = (separator.ignoreCase ? "i" : "") +
88 (separator.multiline ? "m" : "") +
90 (separator.multiline ? "m" : "") +
89 (separator.extended ? "x" : "") + // Proposed for ES6
91 (separator.extended ? "x" : "") + // Proposed for ES6
90 (separator.sticky ? "y" : ""), // Firefox 3+
92 (separator.sticky ? "y" : ""), // Firefox 3+
91 lastLastIndex = 0,
93 lastLastIndex = 0,
92 // Make `global` and avoid `lastIndex` issues by working with a copy
94 // Make `global` and avoid `lastIndex` issues by working with a copy
93 separator = new RegExp(separator.source, flags + "g"),
95 separator = new RegExp(separator.source, flags + "g"),
94 separator2, match, lastIndex, lastLength;
96 separator2, match, lastIndex, lastLength;
95 str += ""; // Type-convert
97 str += ""; // Type-convert
96
98
97 var compliantExecNpcg = typeof(/()??/.exec("")[1]) === "undefined";
99 var compliantExecNpcg = typeof(/()??/.exec("")[1]) === "undefined";
98 if (!compliantExecNpcg) {
100 if (!compliantExecNpcg) {
99 // Doesn't need flags gy, but they don't hurt
101 // Doesn't need flags gy, but they don't hurt
100 separator2 = new RegExp("^" + separator.source + "$(?!\\s)", flags);
102 separator2 = new RegExp("^" + separator.source + "$(?!\\s)", flags);
101 }
103 }
102 /* Values for `limit`, per the spec:
104 /* Values for `limit`, per the spec:
103 * If undefined: 4294967295 // Math.pow(2, 32) - 1
105 * If undefined: 4294967295 // Math.pow(2, 32) - 1
104 * If 0, Infinity, or NaN: 0
106 * If 0, Infinity, or NaN: 0
105 * If positive number: limit = Math.floor(limit); if (limit > 4294967295) limit -= 4294967296;
107 * If positive number: limit = Math.floor(limit); if (limit > 4294967295) limit -= 4294967296;
106 * If negative number: 4294967296 - Math.floor(Math.abs(limit))
108 * If negative number: 4294967296 - Math.floor(Math.abs(limit))
107 * If other: Type-convert, then use the above rules
109 * If other: Type-convert, then use the above rules
108 */
110 */
109 limit = typeof(limit) === "undefined" ?
111 limit = typeof(limit) === "undefined" ?
110 -1 >>> 0 : // Math.pow(2, 32) - 1
112 -1 >>> 0 : // Math.pow(2, 32) - 1
111 limit >>> 0; // ToUint32(limit)
113 limit >>> 0; // ToUint32(limit)
112 while (match = separator.exec(str)) {
114 while (match = separator.exec(str)) {
113 // `separator.lastIndex` is not reliable cross-browser
115 // `separator.lastIndex` is not reliable cross-browser
114 lastIndex = match.index + match[0].length;
116 lastIndex = match.index + match[0].length;
115 if (lastIndex > lastLastIndex) {
117 if (lastIndex > lastLastIndex) {
116 output.push(str.slice(lastLastIndex, match.index));
118 output.push(str.slice(lastLastIndex, match.index));
117 // Fix browsers whose `exec` methods don't consistently return `undefined` for
119 // Fix browsers whose `exec` methods don't consistently return `undefined` for
118 // nonparticipating capturing groups
120 // nonparticipating capturing groups
119 if (!compliantExecNpcg && match.length > 1) {
121 if (!compliantExecNpcg && match.length > 1) {
120 match[0].replace(separator2, function () {
122 match[0].replace(separator2, function () {
121 for (var i = 1; i < arguments.length - 2; i++) {
123 for (var i = 1; i < arguments.length - 2; i++) {
122 if (typeof(arguments[i]) === "undefined") {
124 if (typeof(arguments[i]) === "undefined") {
123 match[i] = undefined;
125 match[i] = undefined;
124 }
126 }
125 }
127 }
126 });
128 });
127 }
129 }
128 if (match.length > 1 && match.index < str.length) {
130 if (match.length > 1 && match.index < str.length) {
129 Array.prototype.push.apply(output, match.slice(1));
131 Array.prototype.push.apply(output, match.slice(1));
130 }
132 }
131 lastLength = match[0].length;
133 lastLength = match[0].length;
132 lastLastIndex = lastIndex;
134 lastLastIndex = lastIndex;
133 if (output.length >= limit) {
135 if (output.length >= limit) {
134 break;
136 break;
135 }
137 }
136 }
138 }
137 if (separator.lastIndex === match.index) {
139 if (separator.lastIndex === match.index) {
138 separator.lastIndex++; // Avoid an infinite loop
140 separator.lastIndex++; // Avoid an infinite loop
139 }
141 }
140 }
142 }
141 if (lastLastIndex === str.length) {
143 if (lastLastIndex === str.length) {
142 if (lastLength || !separator.test("")) {
144 if (lastLength || !separator.test("")) {
143 output.push("");
145 output.push("");
144 }
146 }
145 } else {
147 } else {
146 output.push(str.slice(lastLastIndex));
148 output.push(str.slice(lastLastIndex));
147 }
149 }
148 return output.length > limit ? output.slice(0, limit) : output;
150 return output.length > limit ? output.slice(0, limit) : output;
149 };
151 };
150
152
151 //============================================================================
153 //============================================================================
152 // End contributed Cross-browser RegEx Split
154 // End contributed Cross-browser RegEx Split
153 //============================================================================
155 //============================================================================
154
156
155
157
156 var uuid = function () {
158 var uuid = function () {
157 // http://www.ietf.org/rfc/rfc4122.txt
159 /**
160 * http://www.ietf.org/rfc/rfc4122.txt
161 */
158 var s = [];
162 var s = [];
159 var hexDigits = "0123456789ABCDEF";
163 var hexDigits = "0123456789ABCDEF";
160 for (var i = 0; i < 32; i++) {
164 for (var i = 0; i < 32; i++) {
161 s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
165 s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
162 }
166 }
163 s[12] = "4"; // bits 12-15 of the time_hi_and_version field to 0010
167 s[12] = "4"; // bits 12-15 of the time_hi_and_version field to 0010
164 s[16] = hexDigits.substr((s[16] & 0x3) | 0x8, 1); // bits 6-7 of the clock_seq_hi_and_reserved to 01
168 s[16] = hexDigits.substr((s[16] & 0x3) | 0x8, 1); // bits 6-7 of the clock_seq_hi_and_reserved to 01
165
169
166 var uuid = s.join("");
170 var uuid = s.join("");
167 return uuid;
171 return uuid;
168 };
172 };
169
173
170
174
171 //Fix raw text to parse correctly in crazy XML
175 //Fix raw text to parse correctly in crazy XML
172 function xmlencode(string) {
176 function xmlencode(string) {
173 return string.replace(/\&/g,'&'+'amp;')
177 return string.replace(/\&/g,'&'+'amp;')
174 .replace(/</g,'&'+'lt;')
178 .replace(/</g,'&'+'lt;')
175 .replace(/>/g,'&'+'gt;')
179 .replace(/>/g,'&'+'gt;')
176 .replace(/\'/g,'&'+'apos;')
180 .replace(/\'/g,'&'+'apos;')
177 .replace(/\"/g,'&'+'quot;')
181 .replace(/\"/g,'&'+'quot;')
178 .replace(/`/g,'&'+'#96;');
182 .replace(/`/g,'&'+'#96;');
179 }
183 }
180
184
181
185
182 //Map from terminal commands to CSS classes
186 //Map from terminal commands to CSS classes
183 var ansi_colormap = {
187 var ansi_colormap = {
184 "01":"ansibold",
188 "01":"ansibold",
185
189
186 "30":"ansiblack",
190 "30":"ansiblack",
187 "31":"ansired",
191 "31":"ansired",
188 "32":"ansigreen",
192 "32":"ansigreen",
189 "33":"ansiyellow",
193 "33":"ansiyellow",
190 "34":"ansiblue",
194 "34":"ansiblue",
191 "35":"ansipurple",
195 "35":"ansipurple",
192 "36":"ansicyan",
196 "36":"ansicyan",
193 "37":"ansigray",
197 "37":"ansigray",
194
198
195 "40":"ansibgblack",
199 "40":"ansibgblack",
196 "41":"ansibgred",
200 "41":"ansibgred",
197 "42":"ansibggreen",
201 "42":"ansibggreen",
198 "43":"ansibgyellow",
202 "43":"ansibgyellow",
199 "44":"ansibgblue",
203 "44":"ansibgblue",
200 "45":"ansibgpurple",
204 "45":"ansibgpurple",
201 "46":"ansibgcyan",
205 "46":"ansibgcyan",
202 "47":"ansibggray"
206 "47":"ansibggray"
203 };
207 };
204
208
205 function _process_numbers(attrs, numbers) {
209 function _process_numbers(attrs, numbers) {
206 // process ansi escapes
210 // process ansi escapes
207 var n = numbers.shift();
211 var n = numbers.shift();
208 if (ansi_colormap[n]) {
212 if (ansi_colormap[n]) {
209 if ( ! attrs["class"] ) {
213 if ( ! attrs["class"] ) {
210 attrs["class"] = ansi_colormap[n];
214 attrs["class"] = ansi_colormap[n];
211 } else {
215 } else {
212 attrs["class"] += " " + ansi_colormap[n];
216 attrs["class"] += " " + ansi_colormap[n];
213 }
217 }
214 } else if (n == "38" || n == "48") {
218 } else if (n == "38" || n == "48") {
215 // VT100 256 color or 24 bit RGB
219 // VT100 256 color or 24 bit RGB
216 if (numbers.length < 2) {
220 if (numbers.length < 2) {
217 console.log("Not enough fields for VT100 color", numbers);
221 console.log("Not enough fields for VT100 color", numbers);
218 return;
222 return;
219 }
223 }
220
224
221 var index_or_rgb = numbers.shift();
225 var index_or_rgb = numbers.shift();
222 var r,g,b;
226 var r,g,b;
223 if (index_or_rgb == "5") {
227 if (index_or_rgb == "5") {
224 // 256 color
228 // 256 color
225 var idx = parseInt(numbers.shift());
229 var idx = parseInt(numbers.shift());
226 if (idx < 16) {
230 if (idx < 16) {
227 // indexed ANSI
231 // indexed ANSI
228 // ignore bright / non-bright distinction
232 // ignore bright / non-bright distinction
229 idx = idx % 8;
233 idx = idx % 8;
230 var ansiclass = ansi_colormap[n[0] + (idx % 8).toString()];
234 var ansiclass = ansi_colormap[n[0] + (idx % 8).toString()];
231 if ( ! attrs["class"] ) {
235 if ( ! attrs["class"] ) {
232 attrs["class"] = ansiclass;
236 attrs["class"] = ansiclass;
233 } else {
237 } else {
234 attrs["class"] += " " + ansiclass;
238 attrs["class"] += " " + ansiclass;
235 }
239 }
236 return;
240 return;
237 } else if (idx < 232) {
241 } else if (idx < 232) {
238 // 216 color 6x6x6 RGB
242 // 216 color 6x6x6 RGB
239 idx = idx - 16;
243 idx = idx - 16;
240 b = idx % 6;
244 b = idx % 6;
241 g = Math.floor(idx / 6) % 6;
245 g = Math.floor(idx / 6) % 6;
242 r = Math.floor(idx / 36) % 6;
246 r = Math.floor(idx / 36) % 6;
243 // convert to rgb
247 // convert to rgb
244 r = (r * 51);
248 r = (r * 51);
245 g = (g * 51);
249 g = (g * 51);
246 b = (b * 51);
250 b = (b * 51);
247 } else {
251 } else {
248 // grayscale
252 // grayscale
249 idx = idx - 231;
253 idx = idx - 231;
250 // it's 1-24 and should *not* include black or white,
254 // it's 1-24 and should *not* include black or white,
251 // so a 26 point scale
255 // so a 26 point scale
252 r = g = b = Math.floor(idx * 256 / 26);
256 r = g = b = Math.floor(idx * 256 / 26);
253 }
257 }
254 } else if (index_or_rgb == "2") {
258 } else if (index_or_rgb == "2") {
255 // Simple 24 bit RGB
259 // Simple 24 bit RGB
256 if (numbers.length > 3) {
260 if (numbers.length > 3) {
257 console.log("Not enough fields for RGB", numbers);
261 console.log("Not enough fields for RGB", numbers);
258 return;
262 return;
259 }
263 }
260 r = numbers.shift();
264 r = numbers.shift();
261 g = numbers.shift();
265 g = numbers.shift();
262 b = numbers.shift();
266 b = numbers.shift();
263 } else {
267 } else {
264 console.log("unrecognized control", numbers);
268 console.log("unrecognized control", numbers);
265 return;
269 return;
266 }
270 }
267 if (r !== undefined) {
271 if (r !== undefined) {
268 // apply the rgb color
272 // apply the rgb color
269 var line;
273 var line;
270 if (n == "38") {
274 if (n == "38") {
271 line = "color: ";
275 line = "color: ";
272 } else {
276 } else {
273 line = "background-color: ";
277 line = "background-color: ";
274 }
278 }
275 line = line + "rgb(" + r + "," + g + "," + b + ");";
279 line = line + "rgb(" + r + "," + g + "," + b + ");";
276 if ( !attrs.style ) {
280 if ( !attrs.style ) {
277 attrs.style = line;
281 attrs.style = line;
278 } else {
282 } else {
279 attrs.style += " " + line;
283 attrs.style += " " + line;
280 }
284 }
281 }
285 }
282 }
286 }
283 }
287 }
284
288
285 function ansispan(str) {
289 function ansispan(str) {
286 // ansispan function adapted from github.com/mmalecki/ansispan (MIT License)
290 // ansispan function adapted from github.com/mmalecki/ansispan (MIT License)
287 // regular ansi escapes (using the table above)
291 // regular ansi escapes (using the table above)
288 var is_open = false;
292 var is_open = false;
289 return str.replace(/\033\[(0?[01]|22|39)?([;\d]+)?m/g, function(match, prefix, pattern) {
293 return str.replace(/\033\[(0?[01]|22|39)?([;\d]+)?m/g, function(match, prefix, pattern) {
290 if (!pattern) {
294 if (!pattern) {
291 // [(01|22|39|)m close spans
295 // [(01|22|39|)m close spans
292 if (is_open) {
296 if (is_open) {
293 is_open = false;
297 is_open = false;
294 return "</span>";
298 return "</span>";
295 } else {
299 } else {
296 return "";
300 return "";
297 }
301 }
298 } else {
302 } else {
299 is_open = true;
303 is_open = true;
300
304
301 // consume sequence of color escapes
305 // consume sequence of color escapes
302 var numbers = pattern.match(/\d+/g);
306 var numbers = pattern.match(/\d+/g);
303 var attrs = {};
307 var attrs = {};
304 while (numbers.length > 0) {
308 while (numbers.length > 0) {
305 _process_numbers(attrs, numbers);
309 _process_numbers(attrs, numbers);
306 }
310 }
307
311
308 var span = "<span ";
312 var span = "<span ";
309 for (var attr in attrs) {
313 for (var attr in attrs) {
310 var value = attrs[attr];
314 var value = attrs[attr];
311 span = span + " " + attr + '="' + attrs[attr] + '"';
315 span = span + " " + attr + '="' + attrs[attr] + '"';
312 }
316 }
313 return span + ">";
317 return span + ">";
314 }
318 }
315 });
319 });
316 }
320 }
317
321
318 // Transform ANSI color escape codes into HTML <span> tags with css
322 // Transform ANSI color escape codes into HTML <span> tags with css
319 // classes listed in the above ansi_colormap object. The actual color used
323 // classes listed in the above ansi_colormap object. The actual color used
320 // are set in the css file.
324 // are set in the css file.
321 function fixConsole(txt) {
325 function fixConsole(txt) {
322 txt = xmlencode(txt);
326 txt = xmlencode(txt);
323 var re = /\033\[([\dA-Fa-f;]*?)m/;
327 var re = /\033\[([\dA-Fa-f;]*?)m/;
324 var opened = false;
328 var opened = false;
325 var cmds = [];
329 var cmds = [];
326 var opener = "";
330 var opener = "";
327 var closer = "";
331 var closer = "";
328
332
329 // Strip all ANSI codes that are not color related. Matches
333 // Strip all ANSI codes that are not color related. Matches
330 // all ANSI codes that do not end with "m".
334 // all ANSI codes that do not end with "m".
331 var ignored_re = /(?=(\033\[[\d;=]*[a-ln-zA-Z]{1}))\1(?!m)/g;
335 var ignored_re = /(?=(\033\[[\d;=]*[a-ln-zA-Z]{1}))\1(?!m)/g;
332 txt = txt.replace(ignored_re, "");
336 txt = txt.replace(ignored_re, "");
333
337
334 // color ansi codes
338 // color ansi codes
335 txt = ansispan(txt);
339 txt = ansispan(txt);
336 return txt;
340 return txt;
337 }
341 }
338
342
339 // Remove chunks that should be overridden by the effect of
343 // Remove chunks that should be overridden by the effect of
340 // carriage return characters
344 // carriage return characters
341 function fixCarriageReturn(txt) {
345 function fixCarriageReturn(txt) {
342 var tmp = txt;
346 var tmp = txt;
343 do {
347 do {
344 txt = tmp;
348 txt = tmp;
345 tmp = txt.replace(/\r+\n/gm, '\n'); // \r followed by \n --> newline
349 tmp = txt.replace(/\r+\n/gm, '\n'); // \r followed by \n --> newline
346 tmp = tmp.replace(/^.*\r+/gm, ''); // Other \r --> clear line
350 tmp = tmp.replace(/^.*\r+/gm, ''); // Other \r --> clear line
347 } while (tmp.length < txt.length);
351 } while (tmp.length < txt.length);
348 return txt;
352 return txt;
349 }
353 }
350
354
351 // Locate any URLs and convert them to a anchor tag
355 // Locate any URLs and convert them to a anchor tag
352 function autoLinkUrls(txt) {
356 function autoLinkUrls(txt) {
353 return txt.replace(/(^|\s)(https?|ftp)(:[^'">\s]+)/gi,
357 return txt.replace(/(^|\s)(https?|ftp)(:[^'">\s]+)/gi,
354 "$1<a target=\"_blank\" href=\"$2$3\">$2$3</a>");
358 "$1<a target=\"_blank\" href=\"$2$3\">$2$3</a>");
355 }
359 }
356
360
357 var points_to_pixels = function (points) {
361 var points_to_pixels = function (points) {
358 // A reasonably good way of converting between points and pixels.
362 /**
363 * A reasonably good way of converting between points and pixels.
364 */
359 var test = $('<div style="display: none; width: 10000pt; padding:0; border:0;"></div>');
365 var test = $('<div style="display: none; width: 10000pt; padding:0; border:0;"></div>');
360 $(body).append(test);
366 $(body).append(test);
361 var pixel_per_point = test.width()/10000;
367 var pixel_per_point = test.width()/10000;
362 test.remove();
368 test.remove();
363 return Math.floor(points*pixel_per_point);
369 return Math.floor(points*pixel_per_point);
364 };
370 };
365
371
366 var always_new = function (constructor) {
372 var always_new = function (constructor) {
367 // wrapper around contructor to avoid requiring `var a = new constructor()`
373 /**
368 // useful for passing constructors as callbacks,
374 * wrapper around contructor to avoid requiring `var a = new constructor()`
369 // not for programmer laziness.
375 * useful for passing constructors as callbacks,
370 // from http://programmers.stackexchange.com/questions/118798
376 * not for programmer laziness.
377 * from http://programmers.stackexchange.com/questions/118798
378 */
371 return function () {
379 return function () {
372 var obj = Object.create(constructor.prototype);
380 var obj = Object.create(constructor.prototype);
373 constructor.apply(obj, arguments);
381 constructor.apply(obj, arguments);
374 return obj;
382 return obj;
375 };
383 };
376 };
384 };
377
385
378 var url_path_join = function () {
386 var url_path_join = function () {
379 // join a sequence of url components with '/'
387 /**
388 * join a sequence of url components with '/'
389 */
380 var url = '';
390 var url = '';
381 for (var i = 0; i < arguments.length; i++) {
391 for (var i = 0; i < arguments.length; i++) {
382 if (arguments[i] === '') {
392 if (arguments[i] === '') {
383 continue;
393 continue;
384 }
394 }
385 if (url.length > 0 && url[url.length-1] != '/') {
395 if (url.length > 0 && url[url.length-1] != '/') {
386 url = url + '/' + arguments[i];
396 url = url + '/' + arguments[i];
387 } else {
397 } else {
388 url = url + arguments[i];
398 url = url + arguments[i];
389 }
399 }
390 }
400 }
391 url = url.replace(/\/\/+/, '/');
401 url = url.replace(/\/\/+/, '/');
392 return url;
402 return url;
393 };
403 };
394
404
395 var url_path_split = function (path) {
405 var url_path_split = function (path) {
396 // Like os.path.split for URLs.
406 /**
397 // Always returns two strings, the directory path and the base filename
407 * Like os.path.split for URLs.
408 * Always returns two strings, the directory path and the base filename
409 */
398
410
399 var idx = path.lastIndexOf('/');
411 var idx = path.lastIndexOf('/');
400 if (idx === -1) {
412 if (idx === -1) {
401 return ['', path];
413 return ['', path];
402 } else {
414 } else {
403 return [ path.slice(0, idx), path.slice(idx + 1) ];
415 return [ path.slice(0, idx), path.slice(idx + 1) ];
404 }
416 }
405 };
417 };
406
418
407 var parse_url = function (url) {
419 var parse_url = function (url) {
408 // an `a` element with an href allows attr-access to the parsed segments of a URL
420 /**
409 // a = parse_url("http://localhost:8888/path/name#hash")
421 * an `a` element with an href allows attr-access to the parsed segments of a URL
410 // a.protocol = "http:"
422 * a = parse_url("http://localhost:8888/path/name#hash")
411 // a.host = "localhost:8888"
423 * a.protocol = "http:"
412 // a.hostname = "localhost"
424 * a.host = "localhost:8888"
413 // a.port = 8888
425 * a.hostname = "localhost"
414 // a.pathname = "/path/name"
426 * a.port = 8888
415 // a.hash = "#hash"
427 * a.pathname = "/path/name"
428 * a.hash = "#hash"
429 */
416 var a = document.createElement("a");
430 var a = document.createElement("a");
417 a.href = url;
431 a.href = url;
418 return a;
432 return a;
419 };
433 };
420
434
421 var encode_uri_components = function (uri) {
435 var encode_uri_components = function (uri) {
422 // encode just the components of a multi-segment uri,
436 /**
423 // leaving '/' separators
437 * encode just the components of a multi-segment uri,
438 * leaving '/' separators
439 */
424 return uri.split('/').map(encodeURIComponent).join('/');
440 return uri.split('/').map(encodeURIComponent).join('/');
425 };
441 };
426
442
427 var url_join_encode = function () {
443 var url_join_encode = function () {
428 // join a sequence of url components with '/',
444 /**
429 // encoding each component with encodeURIComponent
445 * join a sequence of url components with '/',
446 * encoding each component with encodeURIComponent
447 */
430 return encode_uri_components(url_path_join.apply(null, arguments));
448 return encode_uri_components(url_path_join.apply(null, arguments));
431 };
449 };
432
450
433
451
434 var splitext = function (filename) {
452 var splitext = function (filename) {
435 // mimic Python os.path.splitext
453 /**
436 // Returns ['base', '.ext']
454 * mimic Python os.path.splitext
455 * Returns ['base', '.ext']
456 */
437 var idx = filename.lastIndexOf('.');
457 var idx = filename.lastIndexOf('.');
438 if (idx > 0) {
458 if (idx > 0) {
439 return [filename.slice(0, idx), filename.slice(idx)];
459 return [filename.slice(0, idx), filename.slice(idx)];
440 } else {
460 } else {
441 return [filename, ''];
461 return [filename, ''];
442 }
462 }
443 };
463 };
444
464
445
465
446 var escape_html = function (text) {
466 var escape_html = function (text) {
447 // escape text to HTML
467 /**
468 * escape text to HTML
469 */
448 return $("<div/>").text(text).html();
470 return $("<div/>").text(text).html();
449 };
471 };
450
472
451
473
452 var get_body_data = function(key) {
474 var get_body_data = function(key) {
453 // get a url-encoded item from body.data and decode it
475 /**
454 // we should never have any encoded URLs anywhere else in code
476 * get a url-encoded item from body.data and decode it
455 // until we are building an actual request
477 * we should never have any encoded URLs anywhere else in code
478 * until we are building an actual request
479 */
456 return decodeURIComponent($('body').data(key));
480 return decodeURIComponent($('body').data(key));
457 };
481 };
458
482
459 var to_absolute_cursor_pos = function (cm, cursor) {
483 var to_absolute_cursor_pos = function (cm, cursor) {
460 // get the absolute cursor position from CodeMirror's col, ch
484 /**
485 * get the absolute cursor position from CodeMirror's col, ch
486 */
461 if (!cursor) {
487 if (!cursor) {
462 cursor = cm.getCursor();
488 cursor = cm.getCursor();
463 }
489 }
464 var cursor_pos = cursor.ch;
490 var cursor_pos = cursor.ch;
465 for (var i = 0; i < cursor.line; i++) {
491 for (var i = 0; i < cursor.line; i++) {
466 cursor_pos += cm.getLine(i).length + 1;
492 cursor_pos += cm.getLine(i).length + 1;
467 }
493 }
468 return cursor_pos;
494 return cursor_pos;
469 };
495 };
470
496
471 var from_absolute_cursor_pos = function (cm, cursor_pos) {
497 var from_absolute_cursor_pos = function (cm, cursor_pos) {
472 // turn absolute cursor postion into CodeMirror col, ch cursor
498 /**
499 * turn absolute cursor postion into CodeMirror col, ch cursor
500 */
473 var i, line;
501 var i, line;
474 var offset = 0;
502 var offset = 0;
475 for (i = 0, line=cm.getLine(i); line !== undefined; i++, line=cm.getLine(i)) {
503 for (i = 0, line=cm.getLine(i); line !== undefined; i++, line=cm.getLine(i)) {
476 if (offset + line.length < cursor_pos) {
504 if (offset + line.length < cursor_pos) {
477 offset += line.length + 1;
505 offset += line.length + 1;
478 } else {
506 } else {
479 return {
507 return {
480 line : i,
508 line : i,
481 ch : cursor_pos - offset,
509 ch : cursor_pos - offset,
482 };
510 };
483 }
511 }
484 }
512 }
485 // reached end, return endpoint
513 // reached end, return endpoint
486 return {
514 return {
487 ch : line.length - 1,
515 ch : line.length - 1,
488 line : i - 1,
516 line : i - 1,
489 };
517 };
490 };
518 };
491
519
492 // http://stackoverflow.com/questions/2400935/browser-detection-in-javascript
520 // http://stackoverflow.com/questions/2400935/browser-detection-in-javascript
493 var browser = (function() {
521 var browser = (function() {
494 if (typeof navigator === 'undefined') {
522 if (typeof navigator === 'undefined') {
495 // navigator undefined in node
523 // navigator undefined in node
496 return 'None';
524 return 'None';
497 }
525 }
498 var N= navigator.appName, ua= navigator.userAgent, tem;
526 var N= navigator.appName, ua= navigator.userAgent, tem;
499 var M= ua.match(/(opera|chrome|safari|firefox|msie)\/?\s*(\.?\d+(\.\d+)*)/i);
527 var M= ua.match(/(opera|chrome|safari|firefox|msie)\/?\s*(\.?\d+(\.\d+)*)/i);
500 if (M && (tem= ua.match(/version\/([\.\d]+)/i)) !== null) M[2]= tem[1];
528 if (M && (tem= ua.match(/version\/([\.\d]+)/i)) !== null) M[2]= tem[1];
501 M= M? [M[1], M[2]]: [N, navigator.appVersion,'-?'];
529 M= M? [M[1], M[2]]: [N, navigator.appVersion,'-?'];
502 return M;
530 return M;
503 })();
531 })();
504
532
505 // http://stackoverflow.com/questions/11219582/how-to-detect-my-browser-version-and-operating-system-using-javascript
533 // http://stackoverflow.com/questions/11219582/how-to-detect-my-browser-version-and-operating-system-using-javascript
506 var platform = (function () {
534 var platform = (function () {
507 if (typeof navigator === 'undefined') {
535 if (typeof navigator === 'undefined') {
508 // navigator undefined in node
536 // navigator undefined in node
509 return 'None';
537 return 'None';
510 }
538 }
511 var OSName="None";
539 var OSName="None";
512 if (navigator.appVersion.indexOf("Win")!=-1) OSName="Windows";
540 if (navigator.appVersion.indexOf("Win")!=-1) OSName="Windows";
513 if (navigator.appVersion.indexOf("Mac")!=-1) OSName="MacOS";
541 if (navigator.appVersion.indexOf("Mac")!=-1) OSName="MacOS";
514 if (navigator.appVersion.indexOf("X11")!=-1) OSName="UNIX";
542 if (navigator.appVersion.indexOf("X11")!=-1) OSName="UNIX";
515 if (navigator.appVersion.indexOf("Linux")!=-1) OSName="Linux";
543 if (navigator.appVersion.indexOf("Linux")!=-1) OSName="Linux";
516 return OSName;
544 return OSName;
517 })();
545 })();
518
546
519 var is_or_has = function (a, b) {
547 var is_or_has = function (a, b) {
520 // Is b a child of a or a itself?
548 /**
549 * Is b a child of a or a itself?
550 */
521 return a.has(b).length !==0 || a.is(b);
551 return a.has(b).length !==0 || a.is(b);
522 };
552 };
523
553
524 var is_focused = function (e) {
554 var is_focused = function (e) {
525 // Is element e, or one of its children focused?
555 /**
556 * Is element e, or one of its children focused?
557 */
526 e = $(e);
558 e = $(e);
527 var target = $(document.activeElement);
559 var target = $(document.activeElement);
528 if (target.length > 0) {
560 if (target.length > 0) {
529 if (is_or_has(e, target)) {
561 if (is_or_has(e, target)) {
530 return true;
562 return true;
531 } else {
563 } else {
532 return false;
564 return false;
533 }
565 }
534 } else {
566 } else {
535 return false;
567 return false;
536 }
568 }
537 };
569 };
538
570
539 var mergeopt = function(_class, options, overwrite){
571 var mergeopt = function(_class, options, overwrite){
540 options = options || {};
572 options = options || {};
541 overwrite = overwrite || {};
573 overwrite = overwrite || {};
542 return $.extend(true, {}, _class.options_default, options, overwrite);
574 return $.extend(true, {}, _class.options_default, options, overwrite);
543 };
575 };
544
576
545 var ajax_error_msg = function (jqXHR) {
577 var ajax_error_msg = function (jqXHR) {
546 // Return a JSON error message if there is one,
578 /**
547 // otherwise the basic HTTP status text.
579 * Return a JSON error message if there is one,
580 * otherwise the basic HTTP status text.
581 */
548 if (jqXHR.responseJSON && jqXHR.responseJSON.traceback) {
582 if (jqXHR.responseJSON && jqXHR.responseJSON.traceback) {
549 return jqXHR.responseJSON.traceback;
583 return jqXHR.responseJSON.traceback;
550 } else if (jqXHR.responseJSON && jqXHR.responseJSON.message) {
584 } else if (jqXHR.responseJSON && jqXHR.responseJSON.message) {
551 return jqXHR.responseJSON.message;
585 return jqXHR.responseJSON.message;
552 } else {
586 } else {
553 return jqXHR.statusText;
587 return jqXHR.statusText;
554 }
588 }
555 };
589 };
556 var log_ajax_error = function (jqXHR, status, error) {
590 var log_ajax_error = function (jqXHR, status, error) {
557 // log ajax failures with informative messages
591 /**
592 * log ajax failures with informative messages
593 */
558 var msg = "API request failed (" + jqXHR.status + "): ";
594 var msg = "API request failed (" + jqXHR.status + "): ";
559 console.log(jqXHR);
595 console.log(jqXHR);
560 msg += ajax_error_msg(jqXHR);
596 msg += ajax_error_msg(jqXHR);
561 console.log(msg);
597 console.log(msg);
562 };
598 };
563
599
564 var requireCodeMirrorMode = function (mode, callback, errback) {
600 var requireCodeMirrorMode = function (mode, callback, errback) {
565 // load a mode with requirejs
601 /**
602 * load a mode with requirejs
603 */
566 if (typeof mode != "string") mode = mode.name;
604 if (typeof mode != "string") mode = mode.name;
567 if (CodeMirror.modes.hasOwnProperty(mode)) {
605 if (CodeMirror.modes.hasOwnProperty(mode)) {
568 callback(CodeMirror.modes.mode);
606 callback(CodeMirror.modes.mode);
569 return;
607 return;
570 }
608 }
571 require([
609 require([
572 // might want to use CodeMirror.modeURL here
610 // might want to use CodeMirror.modeURL here
573 ['codemirror/mode', mode, mode].join('/'),
611 ['codemirror/mode', mode, mode].join('/'),
574 ], callback, errback
612 ], callback, errback
575 );
613 );
576 };
614 };
577
615
578 /** Error type for wrapped XHR errors. */
616 /** Error type for wrapped XHR errors. */
579 var XHR_ERROR = 'XhrError';
617 var XHR_ERROR = 'XhrError';
580
618
581 /**
619 /**
582 * Wraps an AJAX error as an Error object.
620 * Wraps an AJAX error as an Error object.
583 */
621 */
584 var wrap_ajax_error = function (jqXHR, status, error) {
622 var wrap_ajax_error = function (jqXHR, status, error) {
585 var wrapped_error = new Error(ajax_error_msg(jqXHR));
623 var wrapped_error = new Error(ajax_error_msg(jqXHR));
586 wrapped_error.name = XHR_ERROR;
624 wrapped_error.name = XHR_ERROR;
587 // provide xhr response
625 // provide xhr response
588 wrapped_error.xhr = jqXHR;
626 wrapped_error.xhr = jqXHR;
589 wrapped_error.xhr_status = status;
627 wrapped_error.xhr_status = status;
590 wrapped_error.xhr_error = error;
628 wrapped_error.xhr_error = error;
591 return wrapped_error;
629 return wrapped_error;
592 };
630 };
593
631
594 var promising_ajax = function(url, settings) {
632 var promising_ajax = function(url, settings) {
595 // Like $.ajax, but returning an ES6 promise. success and error settings
633 /**
596 // will be ignored.
634 * Like $.ajax, but returning an ES6 promise. success and error settings
635 * will be ignored.
636 */
597 return new Promise(function(resolve, reject) {
637 return new Promise(function(resolve, reject) {
598 settings.success = function(data, status, jqXHR) {
638 settings.success = function(data, status, jqXHR) {
599 resolve(data);
639 resolve(data);
600 };
640 };
601 settings.error = function(jqXHR, status, error) {
641 settings.error = function(jqXHR, status, error) {
602 log_ajax_error(jqXHR, status, error);
642 log_ajax_error(jqXHR, status, error);
603 reject(wrap_ajax_error(jqXHR, status, error));
643 reject(wrap_ajax_error(jqXHR, status, error));
604 };
644 };
605 $.ajax(url, settings);
645 $.ajax(url, settings);
606 });
646 });
607 };
647 };
608
648
609 var WrappedError = function(message, error){
649 var WrappedError = function(message, error){
610 // Wrappable Error class
650 /**
611
651 * Wrappable Error class
612 // The Error class doesn't actually act on `this`. Instead it always
652 *
613 // returns a new instance of Error. Here we capture that instance so we
653 * The Error class doesn't actually act on `this`. Instead it always
614 // can apply it's properties to `this`.
654 * returns a new instance of Error. Here we capture that instance so we
655 * can apply it's properties to `this`.
656 */
615 var tmp = Error.apply(this, [message]);
657 var tmp = Error.apply(this, [message]);
616
658
617 // Copy the properties of the error over to this.
659 // Copy the properties of the error over to this.
618 var properties = Object.getOwnPropertyNames(tmp);
660 var properties = Object.getOwnPropertyNames(tmp);
619 for (var i = 0; i < properties.length; i++) {
661 for (var i = 0; i < properties.length; i++) {
620 this[properties[i]] = tmp[properties[i]];
662 this[properties[i]] = tmp[properties[i]];
621 }
663 }
622
664
623 // Keep a stack of the original error messages.
665 // Keep a stack of the original error messages.
624 if (error instanceof WrappedError) {
666 if (error instanceof WrappedError) {
625 this.error_stack = error.error_stack;
667 this.error_stack = error.error_stack;
626 } else {
668 } else {
627 this.error_stack = [error];
669 this.error_stack = [error];
628 }
670 }
629 this.error_stack.push(tmp);
671 this.error_stack.push(tmp);
630
672
631 return this;
673 return this;
632 };
674 };
633
675
634 WrappedError.prototype = Object.create(Error.prototype, {});
676 WrappedError.prototype = Object.create(Error.prototype, {});
635
677
636
678
637 var load_class = function(class_name, module_name, registry) {
679 var load_class = function(class_name, module_name, registry) {
638 // Tries to load a class
680 /**
639 //
681 * Tries to load a class
640 // Tries to load a class from a module using require.js, if a module
682 *
641 // is specified, otherwise tries to load a class from the global
683 * Tries to load a class from a module using require.js, if a module
642 // registry, if the global registry is provided.
684 * is specified, otherwise tries to load a class from the global
685 * registry, if the global registry is provided.
686 */
643 return new Promise(function(resolve, reject) {
687 return new Promise(function(resolve, reject) {
644
688
645 // Try loading the view module using require.js
689 // Try loading the view module using require.js
646 if (module_name) {
690 if (module_name) {
647 require([module_name], function(module) {
691 require([module_name], function(module) {
648 if (module[class_name] === undefined) {
692 if (module[class_name] === undefined) {
649 reject(new Error('Class '+class_name+' not found in module '+module_name));
693 reject(new Error('Class '+class_name+' not found in module '+module_name));
650 } else {
694 } else {
651 resolve(module[class_name]);
695 resolve(module[class_name]);
652 }
696 }
653 }, reject);
697 }, reject);
654 } else {
698 } else {
655 if (registry && registry[class_name]) {
699 if (registry && registry[class_name]) {
656 resolve(registry[class_name]);
700 resolve(registry[class_name]);
657 } else {
701 } else {
658 reject(new Error('Class '+class_name+' not found in registry '));
702 reject(new Error('Class '+class_name+' not found in registry '));
659 }
703 }
660 }
704 }
661 });
705 });
662 };
706 };
663
707
664 var resolve_promises_dict = function(d) {
708 var resolve_promises_dict = function(d) {
665 // Resolve a promiseful dictionary.
709 /**
666 // Returns a single Promise.
710 * Resolve a promiseful dictionary.
711 * Returns a single Promise.
712 */
667 var keys = Object.keys(d);
713 var keys = Object.keys(d);
668 var values = [];
714 var values = [];
669 keys.forEach(function(key) {
715 keys.forEach(function(key) {
670 values.push(d[key]);
716 values.push(d[key]);
671 });
717 });
672 return Promise.all(values).then(function(v) {
718 return Promise.all(values).then(function(v) {
673 d = {};
719 d = {};
674 for(var i=0; i<keys.length; i++) {
720 for(var i=0; i<keys.length; i++) {
675 d[keys[i]] = v[i];
721 d[keys[i]] = v[i];
676 }
722 }
677 return d;
723 return d;
678 });
724 });
679 };
725 };
680
726
681 var WrappedError = function(message, error){
727 var WrappedError = function(message, error){
682 // Wrappable Error class
728 /**
683
729 * Wrappable Error class
684 // The Error class doesn't actually act on `this`. Instead it always
730 *
685 // returns a new instance of Error. Here we capture that instance so we
731 * The Error class doesn't actually act on `this`. Instead it always
686 // can apply it's properties to `this`.
732 * returns a new instance of Error. Here we capture that instance so we
733 * can apply it's properties to `this`.
734 */
687 var tmp = Error.apply(this, [message]);
735 var tmp = Error.apply(this, [message]);
688
736
689 // Copy the properties of the error over to this.
737 // Copy the properties of the error over to this.
690 var properties = Object.getOwnPropertyNames(tmp);
738 var properties = Object.getOwnPropertyNames(tmp);
691 for (var i = 0; i < properties.length; i++) {
739 for (var i = 0; i < properties.length; i++) {
692 this[properties[i]] = tmp[properties[i]];
740 this[properties[i]] = tmp[properties[i]];
693 }
741 }
694
742
695 // Keep a stack of the original error messages.
743 // Keep a stack of the original error messages.
696 if (error instanceof WrappedError) {
744 if (error instanceof WrappedError) {
697 this.error_stack = error.error_stack;
745 this.error_stack = error.error_stack;
698 } else {
746 } else {
699 this.error_stack = [error];
747 this.error_stack = [error];
700 }
748 }
701 this.error_stack.push(tmp);
749 this.error_stack.push(tmp);
702
750
703 return this;
751 return this;
704 };
752 };
705
753
706 WrappedError.prototype = Object.create(Error.prototype, {});
754 WrappedError.prototype = Object.create(Error.prototype, {});
707
755
708 var reject = function(message, log) {
756 var reject = function(message, log) {
709 // Creates a wrappable Promise rejection function.
757 /**
710 //
758 * Creates a wrappable Promise rejection function.
711 // Creates a function that returns a Promise.reject with a new WrappedError
759 *
712 // that has the provided message and wraps the original error that
760 * Creates a function that returns a Promise.reject with a new WrappedError
713 // caused the promise to reject.
761 * that has the provided message and wraps the original error that
762 * caused the promise to reject.
763 */
714 return function(error) {
764 return function(error) {
715 var wrapped_error = new WrappedError(message, error);
765 var wrapped_error = new WrappedError(message, error);
716 if (log) console.error(wrapped_error);
766 if (log) console.error(wrapped_error);
717 return Promise.reject(wrapped_error);
767 return Promise.reject(wrapped_error);
718 };
768 };
719 };
769 };
720
770
721 var utils = {
771 var utils = {
722 regex_split : regex_split,
772 regex_split : regex_split,
723 uuid : uuid,
773 uuid : uuid,
724 fixConsole : fixConsole,
774 fixConsole : fixConsole,
725 fixCarriageReturn : fixCarriageReturn,
775 fixCarriageReturn : fixCarriageReturn,
726 autoLinkUrls : autoLinkUrls,
776 autoLinkUrls : autoLinkUrls,
727 points_to_pixels : points_to_pixels,
777 points_to_pixels : points_to_pixels,
728 get_body_data : get_body_data,
778 get_body_data : get_body_data,
729 parse_url : parse_url,
779 parse_url : parse_url,
730 url_path_split : url_path_split,
780 url_path_split : url_path_split,
731 url_path_join : url_path_join,
781 url_path_join : url_path_join,
732 url_join_encode : url_join_encode,
782 url_join_encode : url_join_encode,
733 encode_uri_components : encode_uri_components,
783 encode_uri_components : encode_uri_components,
734 splitext : splitext,
784 splitext : splitext,
735 escape_html : escape_html,
785 escape_html : escape_html,
736 always_new : always_new,
786 always_new : always_new,
737 to_absolute_cursor_pos : to_absolute_cursor_pos,
787 to_absolute_cursor_pos : to_absolute_cursor_pos,
738 from_absolute_cursor_pos : from_absolute_cursor_pos,
788 from_absolute_cursor_pos : from_absolute_cursor_pos,
739 browser : browser,
789 browser : browser,
740 platform: platform,
790 platform: platform,
741 is_or_has : is_or_has,
791 is_or_has : is_or_has,
742 is_focused : is_focused,
792 is_focused : is_focused,
743 mergeopt: mergeopt,
793 mergeopt: mergeopt,
744 ajax_error_msg : ajax_error_msg,
794 ajax_error_msg : ajax_error_msg,
745 log_ajax_error : log_ajax_error,
795 log_ajax_error : log_ajax_error,
746 requireCodeMirrorMode : requireCodeMirrorMode,
796 requireCodeMirrorMode : requireCodeMirrorMode,
747 XHR_ERROR : XHR_ERROR,
797 XHR_ERROR : XHR_ERROR,
748 wrap_ajax_error : wrap_ajax_error,
798 wrap_ajax_error : wrap_ajax_error,
749 promising_ajax : promising_ajax,
799 promising_ajax : promising_ajax,
750 WrappedError: WrappedError,
800 WrappedError: WrappedError,
751 load_class: load_class,
801 load_class: load_class,
752 resolve_promises_dict: resolve_promises_dict,
802 resolve_promises_dict: resolve_promises_dict,
753 reject: reject,
803 reject: reject,
754 };
804 };
755
805
756 // Backwards compatability.
806 // Backwards compatability.
757 IPython.utils = utils;
807 IPython.utils = utils;
758
808
759 return utils;
809 return utils;
760 });
810 });
@@ -1,46 +1,50 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 'bootstrap',
8 'bootstrap',
9 ], function(IPython, $, utils, bootstrap) {
9 ], function(IPython, $, utils, bootstrap) {
10 "use strict";
10 "use strict";
11
11
12 var MenuBar = function (selector, options) {
12 var MenuBar = function (selector, options) {
13 // Constructor
13 /**
14 //
14 * Constructor
15 // A MenuBar Class to generate the menubar of IPython notebook
15 *
16 //
16 * A MenuBar Class to generate the menubar of IPython notebook
17 // Parameters:
17 *
18 // selector: string
18 * Parameters:
19 // options: dictionary
19 * selector: string
20 // Dictionary of keyword arguments.
20 * options: dictionary
21 // codemirror: CodeMirror instance
21 * Dictionary of keyword arguments.
22 // contents: ContentManager instance
22 * codemirror: CodeMirror instance
23 // events: $(Events) instance
23 * contents: ContentManager instance
24 // base_url : string
24 * events: $(Events) instance
25 // file_path : string
25 * base_url : string
26 * file_path : string
27 */
26 options = options || {};
28 options = options || {};
27 this.base_url = options.base_url || utils.get_body_data("baseUrl");
29 this.base_url = options.base_url || utils.get_body_data("baseUrl");
28 this.selector = selector;
30 this.selector = selector;
29 this.editor = options.editor;
31 this.editor = options.editor;
30
32
31 if (this.selector !== undefined) {
33 if (this.selector !== undefined) {
32 this.element = $(selector);
34 this.element = $(selector);
33 this.bind_events();
35 this.bind_events();
34 }
36 }
35 };
37 };
36
38
37 MenuBar.prototype.bind_events = function () {
39 MenuBar.prototype.bind_events = function () {
38 // File
40 /**
41 * File
42 */
39 var that = this;
43 var that = this;
40 this.element.find('#save_file').click(function () {
44 this.element.find('#save_file').click(function () {
41 that.editor.save();
45 that.editor.save();
42 });
46 });
43 };
47 };
44
48
45 return {'MenuBar': MenuBar};
49 return {'MenuBar': MenuBar};
46 });
50 });
@@ -1,675 +1,681 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 /**
4 /**
5 *
5 *
6 *
6 *
7 * @module cell
7 * @module cell
8 * @namespace cell
8 * @namespace cell
9 * @class Cell
9 * @class Cell
10 */
10 */
11
11
12
12
13 define([
13 define([
14 'base/js/namespace',
14 'base/js/namespace',
15 'jquery',
15 'jquery',
16 'base/js/utils',
16 'base/js/utils',
17 'codemirror/lib/codemirror',
17 'codemirror/lib/codemirror',
18 'codemirror/addon/edit/matchbrackets',
18 'codemirror/addon/edit/matchbrackets',
19 'codemirror/addon/edit/closebrackets',
19 'codemirror/addon/edit/closebrackets',
20 'codemirror/addon/comment/comment'
20 'codemirror/addon/comment/comment'
21 ], function(IPython, $, utils, CodeMirror, cm_match, cm_closeb, cm_comment) {
21 ], function(IPython, $, utils, CodeMirror, cm_match, cm_closeb, cm_comment) {
22 // TODO: remove IPython dependency here
22 // TODO: remove IPython dependency here
23 "use strict";
23 "use strict";
24
24
25 var Cell = function (options) {
25 var Cell = function (options) {
26 /* Constructor
26 /* Constructor
27 *
27 *
28 * The Base `Cell` class from which to inherit.
28 * The Base `Cell` class from which to inherit.
29 * @constructor
29 * @constructor
30 * @param:
30 * @param:
31 * options: dictionary
31 * options: dictionary
32 * Dictionary of keyword arguments.
32 * Dictionary of keyword arguments.
33 * events: $(Events) instance
33 * events: $(Events) instance
34 * config: dictionary
34 * config: dictionary
35 * keyboard_manager: KeyboardManager instance
35 * keyboard_manager: KeyboardManager instance
36 */
36 */
37 options = options || {};
37 options = options || {};
38 this.keyboard_manager = options.keyboard_manager;
38 this.keyboard_manager = options.keyboard_manager;
39 this.events = options.events;
39 this.events = options.events;
40 var config = utils.mergeopt(Cell, options.config);
40 var config = utils.mergeopt(Cell, options.config);
41 // superclass default overwrite our default
41 // superclass default overwrite our default
42
42
43 this.placeholder = config.placeholder || '';
43 this.placeholder = config.placeholder || '';
44 this.read_only = config.cm_config.readOnly;
44 this.read_only = config.cm_config.readOnly;
45 this.selected = false;
45 this.selected = false;
46 this.rendered = false;
46 this.rendered = false;
47 this.mode = 'command';
47 this.mode = 'command';
48
48
49 // Metadata property
49 // Metadata property
50 var that = this;
50 var that = this;
51 this._metadata = {};
51 this._metadata = {};
52 Object.defineProperty(this, 'metadata', {
52 Object.defineProperty(this, 'metadata', {
53 get: function() { return that._metadata; },
53 get: function() { return that._metadata; },
54 set: function(value) {
54 set: function(value) {
55 that._metadata = value;
55 that._metadata = value;
56 if (that.celltoolbar) {
56 if (that.celltoolbar) {
57 that.celltoolbar.rebuild();
57 that.celltoolbar.rebuild();
58 }
58 }
59 }
59 }
60 });
60 });
61
61
62 // load this from metadata later ?
62 // load this from metadata later ?
63 this.user_highlight = 'auto';
63 this.user_highlight = 'auto';
64 this.cm_config = config.cm_config;
64 this.cm_config = config.cm_config;
65 this.cell_id = utils.uuid();
65 this.cell_id = utils.uuid();
66 this._options = config;
66 this._options = config;
67
67
68 // For JS VM engines optimization, attributes should be all set (even
68 // For JS VM engines optimization, attributes should be all set (even
69 // to null) in the constructor, and if possible, if different subclass
69 // to null) in the constructor, and if possible, if different subclass
70 // have new attributes with same name, they should be created in the
70 // have new attributes with same name, they should be created in the
71 // same order. Easiest is to create and set to null in parent class.
71 // same order. Easiest is to create and set to null in parent class.
72
72
73 this.element = null;
73 this.element = null;
74 this.cell_type = this.cell_type || null;
74 this.cell_type = this.cell_type || null;
75 this.code_mirror = null;
75 this.code_mirror = null;
76
76
77 this.create_element();
77 this.create_element();
78 if (this.element !== null) {
78 if (this.element !== null) {
79 this.element.data("cell", this);
79 this.element.data("cell", this);
80 this.bind_events();
80 this.bind_events();
81 this.init_classes();
81 this.init_classes();
82 }
82 }
83 };
83 };
84
84
85 Cell.options_default = {
85 Cell.options_default = {
86 cm_config : {
86 cm_config : {
87 indentUnit : 4,
87 indentUnit : 4,
88 readOnly: false,
88 readOnly: false,
89 theme: "default",
89 theme: "default",
90 extraKeys: {
90 extraKeys: {
91 "Cmd-Right":"goLineRight",
91 "Cmd-Right":"goLineRight",
92 "End":"goLineRight",
92 "End":"goLineRight",
93 "Cmd-Left":"goLineLeft"
93 "Cmd-Left":"goLineLeft"
94 }
94 }
95 }
95 }
96 };
96 };
97
97
98 // FIXME: Workaround CM Bug #332 (Safari segfault on drag)
98 // FIXME: Workaround CM Bug #332 (Safari segfault on drag)
99 // by disabling drag/drop altogether on Safari
99 // by disabling drag/drop altogether on Safari
100 // https://github.com/codemirror/CodeMirror/issues/332
100 // https://github.com/codemirror/CodeMirror/issues/332
101 if (utils.browser[0] == "Safari") {
101 if (utils.browser[0] == "Safari") {
102 Cell.options_default.cm_config.dragDrop = false;
102 Cell.options_default.cm_config.dragDrop = false;
103 }
103 }
104
104
105 /**
105 /**
106 * Empty. Subclasses must implement create_element.
106 * Empty. Subclasses must implement create_element.
107 * This should contain all the code to create the DOM element in notebook
107 * This should contain all the code to create the DOM element in notebook
108 * and will be called by Base Class constructor.
108 * and will be called by Base Class constructor.
109 * @method create_element
109 * @method create_element
110 */
110 */
111 Cell.prototype.create_element = function () {
111 Cell.prototype.create_element = function () {
112 };
112 };
113
113
114 Cell.prototype.init_classes = function () {
114 Cell.prototype.init_classes = function () {
115 // Call after this.element exists to initialize the css classes
115 /**
116 // related to selected, rendered and mode.
116 * Call after this.element exists to initialize the css classes
117 * related to selected, rendered and mode.
118 */
117 if (this.selected) {
119 if (this.selected) {
118 this.element.addClass('selected');
120 this.element.addClass('selected');
119 } else {
121 } else {
120 this.element.addClass('unselected');
122 this.element.addClass('unselected');
121 }
123 }
122 if (this.rendered) {
124 if (this.rendered) {
123 this.element.addClass('rendered');
125 this.element.addClass('rendered');
124 } else {
126 } else {
125 this.element.addClass('unrendered');
127 this.element.addClass('unrendered');
126 }
128 }
127 if (this.mode === 'edit') {
129 if (this.mode === 'edit') {
128 this.element.addClass('edit_mode');
130 this.element.addClass('edit_mode');
129 } else {
131 } else {
130 this.element.addClass('command_mode');
132 this.element.addClass('command_mode');
131 }
133 }
132 };
134 };
133
135
134 /**
136 /**
135 * Subclasses can implement override bind_events.
137 * Subclasses can implement override bind_events.
136 * Be carefull to call the parent method when overwriting as it fires event.
138 * Be carefull to call the parent method when overwriting as it fires event.
137 * this will be triggerd after create_element in constructor.
139 * this will be triggerd after create_element in constructor.
138 * @method bind_events
140 * @method bind_events
139 */
141 */
140 Cell.prototype.bind_events = function () {
142 Cell.prototype.bind_events = function () {
141 var that = this;
143 var that = this;
142 // We trigger events so that Cell doesn't have to depend on Notebook.
144 // We trigger events so that Cell doesn't have to depend on Notebook.
143 that.element.click(function (event) {
145 that.element.click(function (event) {
144 if (!that.selected) {
146 if (!that.selected) {
145 that.events.trigger('select.Cell', {'cell':that});
147 that.events.trigger('select.Cell', {'cell':that});
146 }
148 }
147 });
149 });
148 that.element.focusin(function (event) {
150 that.element.focusin(function (event) {
149 if (!that.selected) {
151 if (!that.selected) {
150 that.events.trigger('select.Cell', {'cell':that});
152 that.events.trigger('select.Cell', {'cell':that});
151 }
153 }
152 });
154 });
153 if (this.code_mirror) {
155 if (this.code_mirror) {
154 this.code_mirror.on("change", function(cm, change) {
156 this.code_mirror.on("change", function(cm, change) {
155 that.events.trigger("set_dirty.Notebook", {value: true});
157 that.events.trigger("set_dirty.Notebook", {value: true});
156 });
158 });
157 }
159 }
158 if (this.code_mirror) {
160 if (this.code_mirror) {
159 this.code_mirror.on('focus', function(cm, change) {
161 this.code_mirror.on('focus', function(cm, change) {
160 that.events.trigger('edit_mode.Cell', {cell: that});
162 that.events.trigger('edit_mode.Cell', {cell: that});
161 });
163 });
162 }
164 }
163 if (this.code_mirror) {
165 if (this.code_mirror) {
164 this.code_mirror.on('blur', function(cm, change) {
166 this.code_mirror.on('blur', function(cm, change) {
165 that.events.trigger('command_mode.Cell', {cell: that});
167 that.events.trigger('command_mode.Cell', {cell: that});
166 });
168 });
167 }
169 }
168
170
169 this.element.dblclick(function () {
171 this.element.dblclick(function () {
170 if (that.selected === false) {
172 if (that.selected === false) {
171 this.events.trigger('select.Cell', {'cell':that});
173 this.events.trigger('select.Cell', {'cell':that});
172 }
174 }
173 var cont = that.unrender();
175 var cont = that.unrender();
174 if (cont) {
176 if (cont) {
175 that.focus_editor();
177 that.focus_editor();
176 }
178 }
177 });
179 });
178 };
180 };
179
181
180 /**
182 /**
181 * This method gets called in CodeMirror's onKeyDown/onKeyPress
183 * This method gets called in CodeMirror's onKeyDown/onKeyPress
182 * handlers and is used to provide custom key handling.
184 * handlers and is used to provide custom key handling.
183 *
185 *
184 * To have custom handling, subclasses should override this method, but still call it
186 * To have custom handling, subclasses should override this method, but still call it
185 * in order to process the Edit mode keyboard shortcuts.
187 * in order to process the Edit mode keyboard shortcuts.
186 *
188 *
187 * @method handle_codemirror_keyevent
189 * @method handle_codemirror_keyevent
188 * @param {CodeMirror} editor - The codemirror instance bound to the cell
190 * @param {CodeMirror} editor - The codemirror instance bound to the cell
189 * @param {event} event - key press event which either should or should not be handled by CodeMirror
191 * @param {event} event - key press event which either should or should not be handled by CodeMirror
190 * @return {Boolean} `true` if CodeMirror should ignore the event, `false` Otherwise
192 * @return {Boolean} `true` if CodeMirror should ignore the event, `false` Otherwise
191 */
193 */
192 Cell.prototype.handle_codemirror_keyevent = function (editor, event) {
194 Cell.prototype.handle_codemirror_keyevent = function (editor, event) {
193 var shortcuts = this.keyboard_manager.edit_shortcuts;
195 var shortcuts = this.keyboard_manager.edit_shortcuts;
194
196
195 var cur = editor.getCursor();
197 var cur = editor.getCursor();
196 if((cur.line !== 0 || cur.ch !==0) && event.keyCode === 38){
198 if((cur.line !== 0 || cur.ch !==0) && event.keyCode === 38){
197 event._ipkmIgnore = true;
199 event._ipkmIgnore = true;
198 }
200 }
199 var nLastLine = editor.lastLine();
201 var nLastLine = editor.lastLine();
200 if ((event.keyCode === 40) &&
202 if ((event.keyCode === 40) &&
201 ((cur.line !== nLastLine) ||
203 ((cur.line !== nLastLine) ||
202 (cur.ch !== editor.getLineHandle(nLastLine).text.length))
204 (cur.ch !== editor.getLineHandle(nLastLine).text.length))
203 ) {
205 ) {
204 event._ipkmIgnore = true;
206 event._ipkmIgnore = true;
205 }
207 }
206 // if this is an edit_shortcuts shortcut, the global keyboard/shortcut
208 // if this is an edit_shortcuts shortcut, the global keyboard/shortcut
207 // manager will handle it
209 // manager will handle it
208 if (shortcuts.handles(event)) {
210 if (shortcuts.handles(event)) {
209 return true;
211 return true;
210 }
212 }
211
213
212 return false;
214 return false;
213 };
215 };
214
216
215
217
216 /**
218 /**
217 * Triger typsetting of math by mathjax on current cell element
219 * Triger typsetting of math by mathjax on current cell element
218 * @method typeset
220 * @method typeset
219 */
221 */
220 Cell.prototype.typeset = function () {
222 Cell.prototype.typeset = function () {
221 if (window.MathJax) {
223 if (window.MathJax) {
222 var cell_math = this.element.get(0);
224 var cell_math = this.element.get(0);
223 MathJax.Hub.Queue(["Typeset", MathJax.Hub, cell_math]);
225 MathJax.Hub.Queue(["Typeset", MathJax.Hub, cell_math]);
224 }
226 }
225 };
227 };
226
228
227 /**
229 /**
228 * handle cell level logic when a cell is selected
230 * handle cell level logic when a cell is selected
229 * @method select
231 * @method select
230 * @return is the action being taken
232 * @return is the action being taken
231 */
233 */
232 Cell.prototype.select = function () {
234 Cell.prototype.select = function () {
233 if (!this.selected) {
235 if (!this.selected) {
234 this.element.addClass('selected');
236 this.element.addClass('selected');
235 this.element.removeClass('unselected');
237 this.element.removeClass('unselected');
236 this.selected = true;
238 this.selected = true;
237 return true;
239 return true;
238 } else {
240 } else {
239 return false;
241 return false;
240 }
242 }
241 };
243 };
242
244
243 /**
245 /**
244 * handle cell level logic when a cell is unselected
246 * handle cell level logic when a cell is unselected
245 * @method unselect
247 * @method unselect
246 * @return is the action being taken
248 * @return is the action being taken
247 */
249 */
248 Cell.prototype.unselect = function () {
250 Cell.prototype.unselect = function () {
249 if (this.selected) {
251 if (this.selected) {
250 this.element.addClass('unselected');
252 this.element.addClass('unselected');
251 this.element.removeClass('selected');
253 this.element.removeClass('selected');
252 this.selected = false;
254 this.selected = false;
253 return true;
255 return true;
254 } else {
256 } else {
255 return false;
257 return false;
256 }
258 }
257 };
259 };
258
260
259 /**
261 /**
260 * should be overritten by subclass
262 * should be overritten by subclass
261 * @method execute
263 * @method execute
262 */
264 */
263 Cell.prototype.execute = function () {
265 Cell.prototype.execute = function () {
264 return;
266 return;
265 };
267 };
266
268
267 /**
269 /**
268 * handle cell level logic when a cell is rendered
270 * handle cell level logic when a cell is rendered
269 * @method render
271 * @method render
270 * @return is the action being taken
272 * @return is the action being taken
271 */
273 */
272 Cell.prototype.render = function () {
274 Cell.prototype.render = function () {
273 if (!this.rendered) {
275 if (!this.rendered) {
274 this.element.addClass('rendered');
276 this.element.addClass('rendered');
275 this.element.removeClass('unrendered');
277 this.element.removeClass('unrendered');
276 this.rendered = true;
278 this.rendered = true;
277 return true;
279 return true;
278 } else {
280 } else {
279 return false;
281 return false;
280 }
282 }
281 };
283 };
282
284
283 /**
285 /**
284 * handle cell level logic when a cell is unrendered
286 * handle cell level logic when a cell is unrendered
285 * @method unrender
287 * @method unrender
286 * @return is the action being taken
288 * @return is the action being taken
287 */
289 */
288 Cell.prototype.unrender = function () {
290 Cell.prototype.unrender = function () {
289 if (this.rendered) {
291 if (this.rendered) {
290 this.element.addClass('unrendered');
292 this.element.addClass('unrendered');
291 this.element.removeClass('rendered');
293 this.element.removeClass('rendered');
292 this.rendered = false;
294 this.rendered = false;
293 return true;
295 return true;
294 } else {
296 } else {
295 return false;
297 return false;
296 }
298 }
297 };
299 };
298
300
299 /**
301 /**
300 * Delegates keyboard shortcut handling to either IPython keyboard
302 * Delegates keyboard shortcut handling to either IPython keyboard
301 * manager when in command mode, or CodeMirror when in edit mode
303 * manager when in command mode, or CodeMirror when in edit mode
302 *
304 *
303 * @method handle_keyevent
305 * @method handle_keyevent
304 * @param {CodeMirror} editor - The codemirror instance bound to the cell
306 * @param {CodeMirror} editor - The codemirror instance bound to the cell
305 * @param {event} - key event to be handled
307 * @param {event} - key event to be handled
306 * @return {Boolean} `true` if CodeMirror should ignore the event, `false` Otherwise
308 * @return {Boolean} `true` if CodeMirror should ignore the event, `false` Otherwise
307 */
309 */
308 Cell.prototype.handle_keyevent = function (editor, event) {
310 Cell.prototype.handle_keyevent = function (editor, event) {
309 if (this.mode === 'command') {
311 if (this.mode === 'command') {
310 return true;
312 return true;
311 } else if (this.mode === 'edit') {
313 } else if (this.mode === 'edit') {
312 return this.handle_codemirror_keyevent(editor, event);
314 return this.handle_codemirror_keyevent(editor, event);
313 }
315 }
314 };
316 };
315
317
316 /**
318 /**
317 * @method at_top
319 * @method at_top
318 * @return {Boolean}
320 * @return {Boolean}
319 */
321 */
320 Cell.prototype.at_top = function () {
322 Cell.prototype.at_top = function () {
321 var cm = this.code_mirror;
323 var cm = this.code_mirror;
322 var cursor = cm.getCursor();
324 var cursor = cm.getCursor();
323 if (cursor.line === 0 && cursor.ch === 0) {
325 if (cursor.line === 0 && cursor.ch === 0) {
324 return true;
326 return true;
325 }
327 }
326 return false;
328 return false;
327 };
329 };
328
330
329 /**
331 /**
330 * @method at_bottom
332 * @method at_bottom
331 * @return {Boolean}
333 * @return {Boolean}
332 * */
334 * */
333 Cell.prototype.at_bottom = function () {
335 Cell.prototype.at_bottom = function () {
334 var cm = this.code_mirror;
336 var cm = this.code_mirror;
335 var cursor = cm.getCursor();
337 var cursor = cm.getCursor();
336 if (cursor.line === (cm.lineCount()-1) && cursor.ch === cm.getLine(cursor.line).length) {
338 if (cursor.line === (cm.lineCount()-1) && cursor.ch === cm.getLine(cursor.line).length) {
337 return true;
339 return true;
338 }
340 }
339 return false;
341 return false;
340 };
342 };
341
343
342 /**
344 /**
343 * enter the command mode for the cell
345 * enter the command mode for the cell
344 * @method command_mode
346 * @method command_mode
345 * @return is the action being taken
347 * @return is the action being taken
346 */
348 */
347 Cell.prototype.command_mode = function () {
349 Cell.prototype.command_mode = function () {
348 if (this.mode !== 'command') {
350 if (this.mode !== 'command') {
349 this.element.addClass('command_mode');
351 this.element.addClass('command_mode');
350 this.element.removeClass('edit_mode');
352 this.element.removeClass('edit_mode');
351 this.mode = 'command';
353 this.mode = 'command';
352 return true;
354 return true;
353 } else {
355 } else {
354 return false;
356 return false;
355 }
357 }
356 };
358 };
357
359
358 /**
360 /**
359 * enter the edit mode for the cell
361 * enter the edit mode for the cell
360 * @method command_mode
362 * @method command_mode
361 * @return is the action being taken
363 * @return is the action being taken
362 */
364 */
363 Cell.prototype.edit_mode = function () {
365 Cell.prototype.edit_mode = function () {
364 if (this.mode !== 'edit') {
366 if (this.mode !== 'edit') {
365 this.element.addClass('edit_mode');
367 this.element.addClass('edit_mode');
366 this.element.removeClass('command_mode');
368 this.element.removeClass('command_mode');
367 this.mode = 'edit';
369 this.mode = 'edit';
368 return true;
370 return true;
369 } else {
371 } else {
370 return false;
372 return false;
371 }
373 }
372 };
374 };
373
375
374 /**
376 /**
375 * Focus the cell in the DOM sense
377 * Focus the cell in the DOM sense
376 * @method focus_cell
378 * @method focus_cell
377 */
379 */
378 Cell.prototype.focus_cell = function () {
380 Cell.prototype.focus_cell = function () {
379 this.element.focus();
381 this.element.focus();
380 };
382 };
381
383
382 /**
384 /**
383 * Focus the editor area so a user can type
385 * Focus the editor area so a user can type
384 *
386 *
385 * NOTE: If codemirror is focused via a mouse click event, you don't want to
387 * NOTE: If codemirror is focused via a mouse click event, you don't want to
386 * call this because it will cause a page jump.
388 * call this because it will cause a page jump.
387 * @method focus_editor
389 * @method focus_editor
388 */
390 */
389 Cell.prototype.focus_editor = function () {
391 Cell.prototype.focus_editor = function () {
390 this.refresh();
392 this.refresh();
391 this.code_mirror.focus();
393 this.code_mirror.focus();
392 };
394 };
393
395
394 /**
396 /**
395 * Refresh codemirror instance
397 * Refresh codemirror instance
396 * @method refresh
398 * @method refresh
397 */
399 */
398 Cell.prototype.refresh = function () {
400 Cell.prototype.refresh = function () {
399 if (this.code_mirror) {
401 if (this.code_mirror) {
400 this.code_mirror.refresh();
402 this.code_mirror.refresh();
401 }
403 }
402 };
404 };
403
405
404 /**
406 /**
405 * should be overritten by subclass
407 * should be overritten by subclass
406 * @method get_text
408 * @method get_text
407 */
409 */
408 Cell.prototype.get_text = function () {
410 Cell.prototype.get_text = function () {
409 };
411 };
410
412
411 /**
413 /**
412 * should be overritten by subclass
414 * should be overritten by subclass
413 * @method set_text
415 * @method set_text
414 * @param {string} text
416 * @param {string} text
415 */
417 */
416 Cell.prototype.set_text = function (text) {
418 Cell.prototype.set_text = function (text) {
417 };
419 };
418
420
419 /**
421 /**
420 * should be overritten by subclass
422 * should be overritten by subclass
421 * serialise cell to json.
423 * serialise cell to json.
422 * @method toJSON
424 * @method toJSON
423 **/
425 **/
424 Cell.prototype.toJSON = function () {
426 Cell.prototype.toJSON = function () {
425 var data = {};
427 var data = {};
426 // deepcopy the metadata so copied cells don't share the same object
428 // deepcopy the metadata so copied cells don't share the same object
427 data.metadata = JSON.parse(JSON.stringify(this.metadata));
429 data.metadata = JSON.parse(JSON.stringify(this.metadata));
428 data.cell_type = this.cell_type;
430 data.cell_type = this.cell_type;
429 return data;
431 return data;
430 };
432 };
431
433
432 /**
434 /**
433 * should be overritten by subclass
435 * should be overritten by subclass
434 * @method fromJSON
436 * @method fromJSON
435 **/
437 **/
436 Cell.prototype.fromJSON = function (data) {
438 Cell.prototype.fromJSON = function (data) {
437 if (data.metadata !== undefined) {
439 if (data.metadata !== undefined) {
438 this.metadata = data.metadata;
440 this.metadata = data.metadata;
439 }
441 }
440 };
442 };
441
443
442
444
443 /**
445 /**
444 * can the cell be split into two cells (false if not deletable)
446 * can the cell be split into two cells (false if not deletable)
445 * @method is_splittable
447 * @method is_splittable
446 **/
448 **/
447 Cell.prototype.is_splittable = function () {
449 Cell.prototype.is_splittable = function () {
448 return this.is_deletable();
450 return this.is_deletable();
449 };
451 };
450
452
451
453
452 /**
454 /**
453 * can the cell be merged with other cells (false if not deletable)
455 * can the cell be merged with other cells (false if not deletable)
454 * @method is_mergeable
456 * @method is_mergeable
455 **/
457 **/
456 Cell.prototype.is_mergeable = function () {
458 Cell.prototype.is_mergeable = function () {
457 return this.is_deletable();
459 return this.is_deletable();
458 };
460 };
459
461
460 /**
462 /**
461 * is the cell deletable? only false (undeletable) if
463 * is the cell deletable? only false (undeletable) if
462 * metadata.deletable is explicitly false -- everything else
464 * metadata.deletable is explicitly false -- everything else
463 * counts as true
465 * counts as true
464 *
466 *
465 * @method is_deletable
467 * @method is_deletable
466 **/
468 **/
467 Cell.prototype.is_deletable = function () {
469 Cell.prototype.is_deletable = function () {
468 if (this.metadata.deletable === false) {
470 if (this.metadata.deletable === false) {
469 return false;
471 return false;
470 }
472 }
471 return true;
473 return true;
472 };
474 };
473
475
474 /**
476 /**
475 * @return {String} - the text before the cursor
477 * @return {String} - the text before the cursor
476 * @method get_pre_cursor
478 * @method get_pre_cursor
477 **/
479 **/
478 Cell.prototype.get_pre_cursor = function () {
480 Cell.prototype.get_pre_cursor = function () {
479 var cursor = this.code_mirror.getCursor();
481 var cursor = this.code_mirror.getCursor();
480 var text = this.code_mirror.getRange({line:0, ch:0}, cursor);
482 var text = this.code_mirror.getRange({line:0, ch:0}, cursor);
481 text = text.replace(/^\n+/, '').replace(/\n+$/, '');
483 text = text.replace(/^\n+/, '').replace(/\n+$/, '');
482 return text;
484 return text;
483 };
485 };
484
486
485
487
486 /**
488 /**
487 * @return {String} - the text after the cursor
489 * @return {String} - the text after the cursor
488 * @method get_post_cursor
490 * @method get_post_cursor
489 **/
491 **/
490 Cell.prototype.get_post_cursor = function () {
492 Cell.prototype.get_post_cursor = function () {
491 var cursor = this.code_mirror.getCursor();
493 var cursor = this.code_mirror.getCursor();
492 var last_line_num = this.code_mirror.lineCount()-1;
494 var last_line_num = this.code_mirror.lineCount()-1;
493 var last_line_len = this.code_mirror.getLine(last_line_num).length;
495 var last_line_len = this.code_mirror.getLine(last_line_num).length;
494 var end = {line:last_line_num, ch:last_line_len};
496 var end = {line:last_line_num, ch:last_line_len};
495 var text = this.code_mirror.getRange(cursor, end);
497 var text = this.code_mirror.getRange(cursor, end);
496 text = text.replace(/^\n+/, '').replace(/\n+$/, '');
498 text = text.replace(/^\n+/, '').replace(/\n+$/, '');
497 return text;
499 return text;
498 };
500 };
499
501
500 /**
502 /**
501 * Show/Hide CodeMirror LineNumber
503 * Show/Hide CodeMirror LineNumber
502 * @method show_line_numbers
504 * @method show_line_numbers
503 *
505 *
504 * @param value {Bool} show (true), or hide (false) the line number in CodeMirror
506 * @param value {Bool} show (true), or hide (false) the line number in CodeMirror
505 **/
507 **/
506 Cell.prototype.show_line_numbers = function (value) {
508 Cell.prototype.show_line_numbers = function (value) {
507 this.code_mirror.setOption('lineNumbers', value);
509 this.code_mirror.setOption('lineNumbers', value);
508 this.code_mirror.refresh();
510 this.code_mirror.refresh();
509 };
511 };
510
512
511 /**
513 /**
512 * Toggle CodeMirror LineNumber
514 * Toggle CodeMirror LineNumber
513 * @method toggle_line_numbers
515 * @method toggle_line_numbers
514 **/
516 **/
515 Cell.prototype.toggle_line_numbers = function () {
517 Cell.prototype.toggle_line_numbers = function () {
516 var val = this.code_mirror.getOption('lineNumbers');
518 var val = this.code_mirror.getOption('lineNumbers');
517 this.show_line_numbers(!val);
519 this.show_line_numbers(!val);
518 };
520 };
519
521
520 /**
522 /**
521 * Force codemirror highlight mode
523 * Force codemirror highlight mode
522 * @method force_highlight
524 * @method force_highlight
523 * @param {object} - CodeMirror mode
525 * @param {object} - CodeMirror mode
524 **/
526 **/
525 Cell.prototype.force_highlight = function(mode) {
527 Cell.prototype.force_highlight = function(mode) {
526 this.user_highlight = mode;
528 this.user_highlight = mode;
527 this.auto_highlight();
529 this.auto_highlight();
528 };
530 };
529
531
530 /**
532 /**
531 * Try to autodetect cell highlight mode, or use selected mode
533 * Try to autodetect cell highlight mode, or use selected mode
532 * @methods _auto_highlight
534 * @methods _auto_highlight
533 * @private
535 * @private
534 * @param {String|object|undefined} - CodeMirror mode | 'auto'
536 * @param {String|object|undefined} - CodeMirror mode | 'auto'
535 **/
537 **/
536 Cell.prototype._auto_highlight = function (modes) {
538 Cell.prototype._auto_highlight = function (modes) {
537 //Here we handle manually selected modes
539 /**
540 *Here we handle manually selected modes
541 */
538 var that = this;
542 var that = this;
539 var mode;
543 var mode;
540 if( this.user_highlight !== undefined && this.user_highlight != 'auto' )
544 if( this.user_highlight !== undefined && this.user_highlight != 'auto' )
541 {
545 {
542 mode = this.user_highlight;
546 mode = this.user_highlight;
543 CodeMirror.autoLoadMode(this.code_mirror, mode);
547 CodeMirror.autoLoadMode(this.code_mirror, mode);
544 this.code_mirror.setOption('mode', mode);
548 this.code_mirror.setOption('mode', mode);
545 return;
549 return;
546 }
550 }
547 var current_mode = this.code_mirror.getOption('mode', mode);
551 var current_mode = this.code_mirror.getOption('mode', mode);
548 var first_line = this.code_mirror.getLine(0);
552 var first_line = this.code_mirror.getLine(0);
549 // loop on every pairs
553 // loop on every pairs
550 for(mode in modes) {
554 for(mode in modes) {
551 var regs = modes[mode].reg;
555 var regs = modes[mode].reg;
552 // only one key every time but regexp can't be keys...
556 // only one key every time but regexp can't be keys...
553 for(var i=0; i<regs.length; i++) {
557 for(var i=0; i<regs.length; i++) {
554 // here we handle non magic_modes
558 // here we handle non magic_modes
555 if(first_line.match(regs[i]) !== null) {
559 if(first_line.match(regs[i]) !== null) {
556 if(current_mode == mode){
560 if(current_mode == mode){
557 return;
561 return;
558 }
562 }
559 if (mode.search('magic_') !== 0) {
563 if (mode.search('magic_') !== 0) {
560 utils.requireCodeMirrorMode(mode, function () {
564 utils.requireCodeMirrorMode(mode, function () {
561 that.code_mirror.setOption('mode', mode);
565 that.code_mirror.setOption('mode', mode);
562 });
566 });
563 return;
567 return;
564 }
568 }
565 var open = modes[mode].open || "%%";
569 var open = modes[mode].open || "%%";
566 var close = modes[mode].close || "%%end";
570 var close = modes[mode].close || "%%end";
567 var magic_mode = mode;
571 var magic_mode = mode;
568 mode = magic_mode.substr(6);
572 mode = magic_mode.substr(6);
569 if(current_mode == magic_mode){
573 if(current_mode == magic_mode){
570 return;
574 return;
571 }
575 }
572 utils.requireCodeMirrorMode(mode, function () {
576 utils.requireCodeMirrorMode(mode, function () {
573 // create on the fly a mode that switch between
577 // create on the fly a mode that switch between
574 // plain/text and something else, otherwise `%%` is
578 // plain/text and something else, otherwise `%%` is
575 // source of some highlight issues.
579 // source of some highlight issues.
576 CodeMirror.defineMode(magic_mode, function(config) {
580 CodeMirror.defineMode(magic_mode, function(config) {
577 return CodeMirror.multiplexingMode(
581 return CodeMirror.multiplexingMode(
578 CodeMirror.getMode(config, 'text/plain'),
582 CodeMirror.getMode(config, 'text/plain'),
579 // always set something on close
583 // always set something on close
580 {open: open, close: close,
584 {open: open, close: close,
581 mode: CodeMirror.getMode(config, mode),
585 mode: CodeMirror.getMode(config, mode),
582 delimStyle: "delimit"
586 delimStyle: "delimit"
583 }
587 }
584 );
588 );
585 });
589 });
586 that.code_mirror.setOption('mode', magic_mode);
590 that.code_mirror.setOption('mode', magic_mode);
587 });
591 });
588 return;
592 return;
589 }
593 }
590 }
594 }
591 }
595 }
592 // fallback on default
596 // fallback on default
593 var default_mode;
597 var default_mode;
594 try {
598 try {
595 default_mode = this._options.cm_config.mode;
599 default_mode = this._options.cm_config.mode;
596 } catch(e) {
600 } catch(e) {
597 default_mode = 'text/plain';
601 default_mode = 'text/plain';
598 }
602 }
599 if( current_mode === default_mode){
603 if( current_mode === default_mode){
600 return;
604 return;
601 }
605 }
602 this.code_mirror.setOption('mode', default_mode);
606 this.code_mirror.setOption('mode', default_mode);
603 };
607 };
604
608
605 var UnrecognizedCell = function (options) {
609 var UnrecognizedCell = function (options) {
606 /** Constructor for unrecognized cells */
610 /** Constructor for unrecognized cells */
607 Cell.apply(this, arguments);
611 Cell.apply(this, arguments);
608 this.cell_type = 'unrecognized';
612 this.cell_type = 'unrecognized';
609 this.celltoolbar = null;
613 this.celltoolbar = null;
610 this.data = {};
614 this.data = {};
611
615
612 Object.seal(this);
616 Object.seal(this);
613 };
617 };
614
618
615 UnrecognizedCell.prototype = Object.create(Cell.prototype);
619 UnrecognizedCell.prototype = Object.create(Cell.prototype);
616
620
617
621
618 // cannot merge or split unrecognized cells
622 // cannot merge or split unrecognized cells
619 UnrecognizedCell.prototype.is_mergeable = function () {
623 UnrecognizedCell.prototype.is_mergeable = function () {
620 return false;
624 return false;
621 };
625 };
622
626
623 UnrecognizedCell.prototype.is_splittable = function () {
627 UnrecognizedCell.prototype.is_splittable = function () {
624 return false;
628 return false;
625 };
629 };
626
630
627 UnrecognizedCell.prototype.toJSON = function () {
631 UnrecognizedCell.prototype.toJSON = function () {
628 // deepcopy the metadata so copied cells don't share the same object
632 /**
633 * deepcopy the metadata so copied cells don't share the same object
634 */
629 return JSON.parse(JSON.stringify(this.data));
635 return JSON.parse(JSON.stringify(this.data));
630 };
636 };
631
637
632 UnrecognizedCell.prototype.fromJSON = function (data) {
638 UnrecognizedCell.prototype.fromJSON = function (data) {
633 this.data = data;
639 this.data = data;
634 if (data.metadata !== undefined) {
640 if (data.metadata !== undefined) {
635 this.metadata = data.metadata;
641 this.metadata = data.metadata;
636 } else {
642 } else {
637 data.metadata = this.metadata;
643 data.metadata = this.metadata;
638 }
644 }
639 this.element.find('.inner_cell').find("a").text("Unrecognized cell type: " + data.cell_type);
645 this.element.find('.inner_cell').find("a").text("Unrecognized cell type: " + data.cell_type);
640 };
646 };
641
647
642 UnrecognizedCell.prototype.create_element = function () {
648 UnrecognizedCell.prototype.create_element = function () {
643 Cell.prototype.create_element.apply(this, arguments);
649 Cell.prototype.create_element.apply(this, arguments);
644 var cell = this.element = $("<div>").addClass('cell unrecognized_cell');
650 var cell = this.element = $("<div>").addClass('cell unrecognized_cell');
645 cell.attr('tabindex','2');
651 cell.attr('tabindex','2');
646
652
647 var prompt = $('<div/>').addClass('prompt input_prompt');
653 var prompt = $('<div/>').addClass('prompt input_prompt');
648 cell.append(prompt);
654 cell.append(prompt);
649 var inner_cell = $('<div/>').addClass('inner_cell');
655 var inner_cell = $('<div/>').addClass('inner_cell');
650 inner_cell.append(
656 inner_cell.append(
651 $("<a>")
657 $("<a>")
652 .attr("href", "#")
658 .attr("href", "#")
653 .text("Unrecognized cell type")
659 .text("Unrecognized cell type")
654 );
660 );
655 cell.append(inner_cell);
661 cell.append(inner_cell);
656 this.element = cell;
662 this.element = cell;
657 };
663 };
658
664
659 UnrecognizedCell.prototype.bind_events = function () {
665 UnrecognizedCell.prototype.bind_events = function () {
660 Cell.prototype.bind_events.apply(this, arguments);
666 Cell.prototype.bind_events.apply(this, arguments);
661 var cell = this;
667 var cell = this;
662
668
663 this.element.find('.inner_cell').find("a").click(function () {
669 this.element.find('.inner_cell').find("a").click(function () {
664 cell.events.trigger('unrecognized_cell.Cell', {cell: cell})
670 cell.events.trigger('unrecognized_cell.Cell', {cell: cell})
665 });
671 });
666 };
672 };
667
673
668 // Backwards compatibility.
674 // Backwards compatibility.
669 IPython.Cell = Cell;
675 IPython.Cell = Cell;
670
676
671 return {
677 return {
672 Cell: Cell,
678 Cell: Cell,
673 UnrecognizedCell: UnrecognizedCell
679 UnrecognizedCell: UnrecognizedCell
674 };
680 };
675 });
681 });
@@ -1,448 +1,452 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/events'
7 'base/js/events'
8 ], function(IPython, $, events) {
8 ], function(IPython, $, events) {
9 "use strict";
9 "use strict";
10
10
11 var CellToolbar = function (options) {
11 var CellToolbar = function (options) {
12 // Constructor
12 /**
13 //
13 * Constructor
14 // Parameters:
14 *
15 // options: dictionary
15 * Parameters:
16 // Dictionary of keyword arguments.
16 * options: dictionary
17 // events: $(Events) instance
17 * Dictionary of keyword arguments.
18 // cell: Cell instance
18 * events: $(Events) instance
19 // notebook: Notebook instance
19 * cell: Cell instance
20 //
20 * notebook: Notebook instance
21 // TODO: This leaks, when cell are deleted
21 *
22 // There is still a reference to each celltoolbars.
22 * TODO: This leaks, when cell are deleted
23 * There is still a reference to each celltoolbars.
24 */
23 CellToolbar._instances.push(this);
25 CellToolbar._instances.push(this);
24 this.notebook = options.notebook;
26 this.notebook = options.notebook;
25 this.cell = options.cell;
27 this.cell = options.cell;
26 this.create_element();
28 this.create_element();
27 this.rebuild();
29 this.rebuild();
28 return this;
30 return this;
29 };
31 };
30
32
31
33
32 CellToolbar.prototype.create_element = function () {
34 CellToolbar.prototype.create_element = function () {
33 this.inner_element = $('<div/>').addClass('celltoolbar');
35 this.inner_element = $('<div/>').addClass('celltoolbar');
34 this.element = $('<div/>').addClass('ctb_hideshow')
36 this.element = $('<div/>').addClass('ctb_hideshow')
35 .append(this.inner_element);
37 .append(this.inner_element);
36 };
38 };
37
39
38
40
39 // The default css style for the outer celltoolbar div
41 // The default css style for the outer celltoolbar div
40 // (ctb_hideshow) is display: none.
42 // (ctb_hideshow) is display: none.
41 // To show the cell toolbar, *both* of the following conditions must be met:
43 // To show the cell toolbar, *both* of the following conditions must be met:
42 // - A parent container has class `ctb_global_show`
44 // - A parent container has class `ctb_global_show`
43 // - The celltoolbar has the class `ctb_show`
45 // - The celltoolbar has the class `ctb_show`
44 // This allows global show/hide, as well as per-cell show/hide.
46 // This allows global show/hide, as well as per-cell show/hide.
45
47
46 CellToolbar.global_hide = function () {
48 CellToolbar.global_hide = function () {
47 $('body').removeClass('ctb_global_show');
49 $('body').removeClass('ctb_global_show');
48 };
50 };
49
51
50
52
51 CellToolbar.global_show = function () {
53 CellToolbar.global_show = function () {
52 $('body').addClass('ctb_global_show');
54 $('body').addClass('ctb_global_show');
53 };
55 };
54
56
55
57
56 CellToolbar.prototype.hide = function () {
58 CellToolbar.prototype.hide = function () {
57 this.element.removeClass('ctb_show');
59 this.element.removeClass('ctb_show');
58 };
60 };
59
61
60
62
61 CellToolbar.prototype.show = function () {
63 CellToolbar.prototype.show = function () {
62 this.element.addClass('ctb_show');
64 this.element.addClass('ctb_show');
63 };
65 };
64
66
65
67
66 /**
68 /**
67 * Class variable that should contain a dict of all available callback
69 * Class variable that should contain a dict of all available callback
68 * we need to think of wether or not we allow nested namespace
70 * we need to think of wether or not we allow nested namespace
69 * @property _callback_dict
71 * @property _callback_dict
70 * @private
72 * @private
71 * @static
73 * @static
72 * @type Dict
74 * @type Dict
73 */
75 */
74 CellToolbar._callback_dict = {};
76 CellToolbar._callback_dict = {};
75
77
76
78
77 /**
79 /**
78 * Class variable that should contain the reverse order list of the button
80 * Class variable that should contain the reverse order list of the button
79 * to add to the toolbar of each cell
81 * to add to the toolbar of each cell
80 * @property _ui_controls_list
82 * @property _ui_controls_list
81 * @private
83 * @private
82 * @static
84 * @static
83 * @type List
85 * @type List
84 */
86 */
85 CellToolbar._ui_controls_list = [];
87 CellToolbar._ui_controls_list = [];
86
88
87
89
88 /**
90 /**
89 * Class variable that should contain the CellToolbar instances for each
91 * Class variable that should contain the CellToolbar instances for each
90 * cell of the notebook
92 * cell of the notebook
91 *
93 *
92 * @private
94 * @private
93 * @property _instances
95 * @property _instances
94 * @static
96 * @static
95 * @type List
97 * @type List
96 */
98 */
97 CellToolbar._instances = [];
99 CellToolbar._instances = [];
98
100
99
101
100 /**
102 /**
101 * keep a list of all the available presets for the toolbar
103 * keep a list of all the available presets for the toolbar
102 * @private
104 * @private
103 * @property _presets
105 * @property _presets
104 * @static
106 * @static
105 * @type Dict
107 * @type Dict
106 */
108 */
107 CellToolbar._presets = {};
109 CellToolbar._presets = {};
108
110
109
111
110 // this is by design not a prototype.
112 // this is by design not a prototype.
111 /**
113 /**
112 * Register a callback to create an UI element in a cell toolbar.
114 * Register a callback to create an UI element in a cell toolbar.
113 * @method register_callback
115 * @method register_callback
114 * @param name {String} name to use to refer to the callback. It is advised to use a prefix with the name
116 * @param name {String} name to use to refer to the callback. It is advised to use a prefix with the name
115 * for easier sorting and avoid collision
117 * for easier sorting and avoid collision
116 * @param callback {function(div, cell)} callback that will be called to generate the ui element
118 * @param callback {function(div, cell)} callback that will be called to generate the ui element
117 * @param [cell_types] {List_of_String|undefined} optional list of cell types. If present the UI element
119 * @param [cell_types] {List_of_String|undefined} optional list of cell types. If present the UI element
118 * will be added only to cells of types in the list.
120 * will be added only to cells of types in the list.
119 *
121 *
120 *
122 *
121 * The callback will receive the following element :
123 * The callback will receive the following element :
122 *
124 *
123 * * a div in which to add element.
125 * * a div in which to add element.
124 * * the cell it is responsible from
126 * * the cell it is responsible from
125 *
127 *
126 * @example
128 * @example
127 *
129 *
128 * Example that create callback for a button that toggle between `true` and `false` label,
130 * Example that create callback for a button that toggle between `true` and `false` label,
129 * with the metadata under the key 'foo' to reflect the status of the button.
131 * with the metadata under the key 'foo' to reflect the status of the button.
130 *
132 *
131 * // first param reference to a DOM div
133 * // first param reference to a DOM div
132 * // second param reference to the cell.
134 * // second param reference to the cell.
133 * var toggle = function(div, cell) {
135 * var toggle = function(div, cell) {
134 * var button_container = $(div)
136 * var button_container = $(div)
135 *
137 *
136 * // let's create a button that show the current value of the metadata
138 * // let's create a button that show the current value of the metadata
137 * var button = $('<div/>').button({label:String(cell.metadata.foo)});
139 * var button = $('<div/>').button({label:String(cell.metadata.foo)});
138 *
140 *
139 * // On click, change the metadata value and update the button label
141 * // On click, change the metadata value and update the button label
140 * button.click(function(){
142 * button.click(function(){
141 * var v = cell.metadata.foo;
143 * var v = cell.metadata.foo;
142 * cell.metadata.foo = !v;
144 * cell.metadata.foo = !v;
143 * button.button("option", "label", String(!v));
145 * button.button("option", "label", String(!v));
144 * })
146 * })
145 *
147 *
146 * // add the button to the DOM div.
148 * // add the button to the DOM div.
147 * button_container.append(button);
149 * button_container.append(button);
148 * }
150 * }
149 *
151 *
150 * // now we register the callback under the name `foo` to give the
152 * // now we register the callback under the name `foo` to give the
151 * // user the ability to use it later
153 * // user the ability to use it later
152 * CellToolbar.register_callback('foo', toggle);
154 * CellToolbar.register_callback('foo', toggle);
153 */
155 */
154 CellToolbar.register_callback = function(name, callback, cell_types) {
156 CellToolbar.register_callback = function(name, callback, cell_types) {
155 // Overwrite if it already exists.
157 // Overwrite if it already exists.
156 CellToolbar._callback_dict[name] = cell_types ? {callback: callback, cell_types: cell_types} : callback;
158 CellToolbar._callback_dict[name] = cell_types ? {callback: callback, cell_types: cell_types} : callback;
157 };
159 };
158
160
159
161
160 /**
162 /**
161 * Register a preset of UI element in a cell toolbar.
163 * Register a preset of UI element in a cell toolbar.
162 * Not supported Yet.
164 * Not supported Yet.
163 * @method register_preset
165 * @method register_preset
164 * @param name {String} name to use to refer to the preset. It is advised to use a prefix with the name
166 * @param name {String} name to use to refer to the preset. It is advised to use a prefix with the name
165 * for easier sorting and avoid collision
167 * for easier sorting and avoid collision
166 * @param preset_list {List_of_String} reverse order of the button in the toolbar. Each String of the list
168 * @param preset_list {List_of_String} reverse order of the button in the toolbar. Each String of the list
167 * should correspond to a name of a registerd callback.
169 * should correspond to a name of a registerd callback.
168 *
170 *
169 * @private
171 * @private
170 * @example
172 * @example
171 *
173 *
172 * CellToolbar.register_callback('foo.c1', function(div, cell){...});
174 * CellToolbar.register_callback('foo.c1', function(div, cell){...});
173 * CellToolbar.register_callback('foo.c2', function(div, cell){...});
175 * CellToolbar.register_callback('foo.c2', function(div, cell){...});
174 * CellToolbar.register_callback('foo.c3', function(div, cell){...});
176 * CellToolbar.register_callback('foo.c3', function(div, cell){...});
175 * CellToolbar.register_callback('foo.c4', function(div, cell){...});
177 * CellToolbar.register_callback('foo.c4', function(div, cell){...});
176 * CellToolbar.register_callback('foo.c5', function(div, cell){...});
178 * CellToolbar.register_callback('foo.c5', function(div, cell){...});
177 *
179 *
178 * CellToolbar.register_preset('foo.foo_preset1', ['foo.c1', 'foo.c2', 'foo.c5'])
180 * CellToolbar.register_preset('foo.foo_preset1', ['foo.c1', 'foo.c2', 'foo.c5'])
179 * CellToolbar.register_preset('foo.foo_preset2', ['foo.c4', 'foo.c5'])
181 * CellToolbar.register_preset('foo.foo_preset2', ['foo.c4', 'foo.c5'])
180 */
182 */
181 CellToolbar.register_preset = function(name, preset_list, notebook) {
183 CellToolbar.register_preset = function(name, preset_list, notebook) {
182 CellToolbar._presets[name] = preset_list;
184 CellToolbar._presets[name] = preset_list;
183 events.trigger('preset_added.CellToolbar', {name: name});
185 events.trigger('preset_added.CellToolbar', {name: name});
184 // When "register_callback" is called by a custom extension, it may be executed after notebook is loaded.
186 // When "register_callback" is called by a custom extension, it may be executed after notebook is loaded.
185 // In that case, activate the preset if needed.
187 // In that case, activate the preset if needed.
186 if (notebook && notebook.metadata && notebook.metadata.celltoolbar === name){
188 if (notebook && notebook.metadata && notebook.metadata.celltoolbar === name){
187 CellToolbar.activate_preset(name);
189 CellToolbar.activate_preset(name);
188 }
190 }
189 };
191 };
190
192
191
193
192 /**
194 /**
193 * List the names of the presets that are currently registered.
195 * List the names of the presets that are currently registered.
194 *
196 *
195 * @method list_presets
197 * @method list_presets
196 * @static
198 * @static
197 */
199 */
198 CellToolbar.list_presets = function() {
200 CellToolbar.list_presets = function() {
199 var keys = [];
201 var keys = [];
200 for (var k in CellToolbar._presets) {
202 for (var k in CellToolbar._presets) {
201 keys.push(k);
203 keys.push(k);
202 }
204 }
203 return keys;
205 return keys;
204 };
206 };
205
207
206
208
207 /**
209 /**
208 * Activate an UI preset from `register_preset`
210 * Activate an UI preset from `register_preset`
209 *
211 *
210 * This does not update the selection UI.
212 * This does not update the selection UI.
211 *
213 *
212 * @method activate_preset
214 * @method activate_preset
213 * @param preset_name {String} string corresponding to the preset name
215 * @param preset_name {String} string corresponding to the preset name
214 *
216 *
215 * @static
217 * @static
216 * @private
218 * @private
217 * @example
219 * @example
218 *
220 *
219 * CellToolbar.activate_preset('foo.foo_preset1');
221 * CellToolbar.activate_preset('foo.foo_preset1');
220 */
222 */
221 CellToolbar.activate_preset = function(preset_name){
223 CellToolbar.activate_preset = function(preset_name){
222 var preset = CellToolbar._presets[preset_name];
224 var preset = CellToolbar._presets[preset_name];
223
225
224 if(preset !== undefined){
226 if(preset !== undefined){
225 CellToolbar._ui_controls_list = preset;
227 CellToolbar._ui_controls_list = preset;
226 CellToolbar.rebuild_all();
228 CellToolbar.rebuild_all();
227 }
229 }
228
230
229 events.trigger('preset_activated.CellToolbar', {name: preset_name});
231 events.trigger('preset_activated.CellToolbar', {name: preset_name});
230 };
232 };
231
233
232
234
233 /**
235 /**
234 * This should be called on the class and not on a instance as it will trigger
236 * This should be called on the class and not on a instance as it will trigger
235 * rebuild of all the instances.
237 * rebuild of all the instances.
236 * @method rebuild_all
238 * @method rebuild_all
237 * @static
239 * @static
238 *
240 *
239 */
241 */
240 CellToolbar.rebuild_all = function(){
242 CellToolbar.rebuild_all = function(){
241 for(var i=0; i < CellToolbar._instances.length; i++){
243 for(var i=0; i < CellToolbar._instances.length; i++){
242 CellToolbar._instances[i].rebuild();
244 CellToolbar._instances[i].rebuild();
243 }
245 }
244 };
246 };
245
247
246 /**
248 /**
247 * Rebuild all the button on the toolbar to update its state.
249 * Rebuild all the button on the toolbar to update its state.
248 * @method rebuild
250 * @method rebuild
249 */
251 */
250 CellToolbar.prototype.rebuild = function(){
252 CellToolbar.prototype.rebuild = function(){
251 // strip evrything from the div
253 /**
252 // which is probably inner_element
254 * strip evrything from the div
253 // or this.element.
255 * which is probably inner_element
256 * or this.element.
257 */
254 this.inner_element.empty();
258 this.inner_element.empty();
255 this.ui_controls_list = [];
259 this.ui_controls_list = [];
256
260
257 var callbacks = CellToolbar._callback_dict;
261 var callbacks = CellToolbar._callback_dict;
258 var preset = CellToolbar._ui_controls_list;
262 var preset = CellToolbar._ui_controls_list;
259 // Yes we iterate on the class variable, not the instance one.
263 // Yes we iterate on the class variable, not the instance one.
260 for (var i=0; i < preset.length; i++) {
264 for (var i=0; i < preset.length; i++) {
261 var key = preset[i];
265 var key = preset[i];
262 var callback = callbacks[key];
266 var callback = callbacks[key];
263 if (!callback) continue;
267 if (!callback) continue;
264
268
265 if (typeof callback === 'object') {
269 if (typeof callback === 'object') {
266 if (callback.cell_types.indexOf(this.cell.cell_type) === -1) continue;
270 if (callback.cell_types.indexOf(this.cell.cell_type) === -1) continue;
267 callback = callback.callback;
271 callback = callback.callback;
268 }
272 }
269
273
270 var local_div = $('<div/>').addClass('button_container');
274 var local_div = $('<div/>').addClass('button_container');
271 try {
275 try {
272 callback(local_div, this.cell, this);
276 callback(local_div, this.cell, this);
273 this.ui_controls_list.push(key);
277 this.ui_controls_list.push(key);
274 } catch (e) {
278 } catch (e) {
275 console.log("Error in cell toolbar callback " + key, e);
279 console.log("Error in cell toolbar callback " + key, e);
276 continue;
280 continue;
277 }
281 }
278 // only append if callback succeeded.
282 // only append if callback succeeded.
279 this.inner_element.append(local_div);
283 this.inner_element.append(local_div);
280 }
284 }
281
285
282 // If there are no controls or the cell is a rendered TextCell hide the toolbar.
286 // If there are no controls or the cell is a rendered TextCell hide the toolbar.
283 if (!this.ui_controls_list.length) {
287 if (!this.ui_controls_list.length) {
284 this.hide();
288 this.hide();
285 } else {
289 } else {
286 this.show();
290 this.show();
287 }
291 }
288 };
292 };
289
293
290
294
291 CellToolbar.utils = {};
295 CellToolbar.utils = {};
292
296
293
297
294 /**
298 /**
295 * A utility function to generate bindings between a checkbox and cell/metadata
299 * A utility function to generate bindings between a checkbox and cell/metadata
296 * @method utils.checkbox_ui_generator
300 * @method utils.checkbox_ui_generator
297 * @static
301 * @static
298 *
302 *
299 * @param name {string} Label in front of the checkbox
303 * @param name {string} Label in front of the checkbox
300 * @param setter {function( cell, newValue )}
304 * @param setter {function( cell, newValue )}
301 * A setter method to set the newValue
305 * A setter method to set the newValue
302 * @param getter {function( cell )}
306 * @param getter {function( cell )}
303 * A getter methods which return the current value.
307 * A getter methods which return the current value.
304 *
308 *
305 * @return callback {function( div, cell )} Callback to be passed to `register_callback`
309 * @return callback {function( div, cell )} Callback to be passed to `register_callback`
306 *
310 *
307 * @example
311 * @example
308 *
312 *
309 * An exmple that bind the subkey `slideshow.isSectionStart` to a checkbox with a `New Slide` label
313 * An exmple that bind the subkey `slideshow.isSectionStart` to a checkbox with a `New Slide` label
310 *
314 *
311 * var newSlide = CellToolbar.utils.checkbox_ui_generator('New Slide',
315 * var newSlide = CellToolbar.utils.checkbox_ui_generator('New Slide',
312 * // setter
316 * // setter
313 * function(cell, value){
317 * function(cell, value){
314 * // we check that the slideshow namespace exist and create it if needed
318 * // we check that the slideshow namespace exist and create it if needed
315 * if (cell.metadata.slideshow == undefined){cell.metadata.slideshow = {}}
319 * if (cell.metadata.slideshow == undefined){cell.metadata.slideshow = {}}
316 * // set the value
320 * // set the value
317 * cell.metadata.slideshow.isSectionStart = value
321 * cell.metadata.slideshow.isSectionStart = value
318 * },
322 * },
319 * //geter
323 * //geter
320 * function(cell){ var ns = cell.metadata.slideshow;
324 * function(cell){ var ns = cell.metadata.slideshow;
321 * // if the slideshow namespace does not exist return `undefined`
325 * // if the slideshow namespace does not exist return `undefined`
322 * // (will be interpreted as `false` by checkbox) otherwise
326 * // (will be interpreted as `false` by checkbox) otherwise
323 * // return the value
327 * // return the value
324 * return (ns == undefined)? undefined: ns.isSectionStart
328 * return (ns == undefined)? undefined: ns.isSectionStart
325 * }
329 * }
326 * );
330 * );
327 *
331 *
328 * CellToolbar.register_callback('newSlide', newSlide);
332 * CellToolbar.register_callback('newSlide', newSlide);
329 *
333 *
330 */
334 */
331 CellToolbar.utils.checkbox_ui_generator = function(name, setter, getter){
335 CellToolbar.utils.checkbox_ui_generator = function(name, setter, getter){
332 return function(div, cell, celltoolbar) {
336 return function(div, cell, celltoolbar) {
333 var button_container = $(div);
337 var button_container = $(div);
334
338
335 var chkb = $('<input/>').attr('type', 'checkbox');
339 var chkb = $('<input/>').attr('type', 'checkbox');
336 var lbl = $('<label/>').append($('<span/>').text(name));
340 var lbl = $('<label/>').append($('<span/>').text(name));
337 lbl.append(chkb);
341 lbl.append(chkb);
338 chkb.attr("checked", getter(cell));
342 chkb.attr("checked", getter(cell));
339
343
340 chkb.click(function(){
344 chkb.click(function(){
341 var v = getter(cell);
345 var v = getter(cell);
342 setter(cell, !v);
346 setter(cell, !v);
343 chkb.attr("checked", !v);
347 chkb.attr("checked", !v);
344 });
348 });
345 button_container.append($('<span/>').append(lbl));
349 button_container.append($('<span/>').append(lbl));
346 };
350 };
347 };
351 };
348
352
349
353
350 /**
354 /**
351 * A utility function to generate bindings between a input field and cell/metadata
355 * A utility function to generate bindings between a input field and cell/metadata
352 * @method utils.input_ui_generator
356 * @method utils.input_ui_generator
353 * @static
357 * @static
354 *
358 *
355 * @param name {string} Label in front of the input field
359 * @param name {string} Label in front of the input field
356 * @param setter {function( cell, newValue )}
360 * @param setter {function( cell, newValue )}
357 * A setter method to set the newValue
361 * A setter method to set the newValue
358 * @param getter {function( cell )}
362 * @param getter {function( cell )}
359 * A getter methods which return the current value.
363 * A getter methods which return the current value.
360 *
364 *
361 * @return callback {function( div, cell )} Callback to be passed to `register_callback`
365 * @return callback {function( div, cell )} Callback to be passed to `register_callback`
362 *
366 *
363 */
367 */
364 CellToolbar.utils.input_ui_generator = function(name, setter, getter){
368 CellToolbar.utils.input_ui_generator = function(name, setter, getter){
365 return function(div, cell, celltoolbar) {
369 return function(div, cell, celltoolbar) {
366 var button_container = $(div);
370 var button_container = $(div);
367
371
368 var text = $('<input/>').attr('type', 'text');
372 var text = $('<input/>').attr('type', 'text');
369 var lbl = $('<label/>').append($('<span/>').text(name));
373 var lbl = $('<label/>').append($('<span/>').text(name));
370 lbl.append(text);
374 lbl.append(text);
371 text.attr("value", getter(cell));
375 text.attr("value", getter(cell));
372
376
373 text.keyup(function(){
377 text.keyup(function(){
374 setter(cell, text.val());
378 setter(cell, text.val());
375 });
379 });
376 button_container.append($('<span/>').append(lbl));
380 button_container.append($('<span/>').append(lbl));
377 IPython.keyboard_manager.register_events(text);
381 IPython.keyboard_manager.register_events(text);
378 };
382 };
379 };
383 };
380
384
381 /**
385 /**
382 * A utility function to generate bindings between a dropdown list cell
386 * A utility function to generate bindings between a dropdown list cell
383 * @method utils.select_ui_generator
387 * @method utils.select_ui_generator
384 * @static
388 * @static
385 *
389 *
386 * @param list_list {list_of_sublist} List of sublist of metadata value and name in the dropdown list.
390 * @param list_list {list_of_sublist} List of sublist of metadata value and name in the dropdown list.
387 * subslit shoud contain 2 element each, first a string that woul be displayed in the dropdown list,
391 * subslit shoud contain 2 element each, first a string that woul be displayed in the dropdown list,
388 * and second the corresponding value to be passed to setter/return by getter. the corresponding value
392 * and second the corresponding value to be passed to setter/return by getter. the corresponding value
389 * should not be "undefined" or behavior can be unexpected.
393 * should not be "undefined" or behavior can be unexpected.
390 * @param setter {function( cell, newValue )}
394 * @param setter {function( cell, newValue )}
391 * A setter method to set the newValue
395 * A setter method to set the newValue
392 * @param getter {function( cell )}
396 * @param getter {function( cell )}
393 * A getter methods which return the current value of the metadata.
397 * A getter methods which return the current value of the metadata.
394 * @param [label=""] {String} optionnal label for the dropdown menu
398 * @param [label=""] {String} optionnal label for the dropdown menu
395 *
399 *
396 * @return callback {function( div, cell )} Callback to be passed to `register_callback`
400 * @return callback {function( div, cell )} Callback to be passed to `register_callback`
397 *
401 *
398 * @example
402 * @example
399 *
403 *
400 * var select_type = CellToolbar.utils.select_ui_generator([
404 * var select_type = CellToolbar.utils.select_ui_generator([
401 * ["<None>" , "None" ],
405 * ["<None>" , "None" ],
402 * ["Header Slide" , "header_slide" ],
406 * ["Header Slide" , "header_slide" ],
403 * ["Slide" , "slide" ],
407 * ["Slide" , "slide" ],
404 * ["Fragment" , "fragment" ],
408 * ["Fragment" , "fragment" ],
405 * ["Skip" , "skip" ],
409 * ["Skip" , "skip" ],
406 * ],
410 * ],
407 * // setter
411 * // setter
408 * function(cell, value){
412 * function(cell, value){
409 * // we check that the slideshow namespace exist and create it if needed
413 * // we check that the slideshow namespace exist and create it if needed
410 * if (cell.metadata.slideshow == undefined){cell.metadata.slideshow = {}}
414 * if (cell.metadata.slideshow == undefined){cell.metadata.slideshow = {}}
411 * // set the value
415 * // set the value
412 * cell.metadata.slideshow.slide_type = value
416 * cell.metadata.slideshow.slide_type = value
413 * },
417 * },
414 * //geter
418 * //geter
415 * function(cell){ var ns = cell.metadata.slideshow;
419 * function(cell){ var ns = cell.metadata.slideshow;
416 * // if the slideshow namespace does not exist return `undefined`
420 * // if the slideshow namespace does not exist return `undefined`
417 * // (will be interpreted as `false` by checkbox) otherwise
421 * // (will be interpreted as `false` by checkbox) otherwise
418 * // return the value
422 * // return the value
419 * return (ns == undefined)? undefined: ns.slide_type
423 * return (ns == undefined)? undefined: ns.slide_type
420 * }
424 * }
421 * CellToolbar.register_callback('slideshow.select', select_type);
425 * CellToolbar.register_callback('slideshow.select', select_type);
422 *
426 *
423 */
427 */
424 CellToolbar.utils.select_ui_generator = function(list_list, setter, getter, label) {
428 CellToolbar.utils.select_ui_generator = function(list_list, setter, getter, label) {
425 label = label || "";
429 label = label || "";
426 return function(div, cell, celltoolbar) {
430 return function(div, cell, celltoolbar) {
427 var button_container = $(div);
431 var button_container = $(div);
428 var lbl = $("<label/>").append($('<span/>').text(label));
432 var lbl = $("<label/>").append($('<span/>').text(label));
429 var select = $('<select/>');
433 var select = $('<select/>');
430 for(var i=0; i < list_list.length; i++){
434 for(var i=0; i < list_list.length; i++){
431 var opt = $('<option/>')
435 var opt = $('<option/>')
432 .attr('value', list_list[i][1])
436 .attr('value', list_list[i][1])
433 .text(list_list[i][0]);
437 .text(list_list[i][0]);
434 select.append(opt);
438 select.append(opt);
435 }
439 }
436 select.val(getter(cell));
440 select.val(getter(cell));
437 select.change(function(){
441 select.change(function(){
438 setter(cell, select.val());
442 setter(cell, select.val());
439 });
443 });
440 button_container.append($('<span/>').append(lbl).append(select));
444 button_container.append($('<span/>').append(lbl).append(select));
441 };
445 };
442 };
446 };
443
447
444 // Backwards compatability.
448 // Backwards compatability.
445 IPython.CellToolbar = CellToolbar;
449 IPython.CellToolbar = CellToolbar;
446
450
447 return {'CellToolbar': CellToolbar};
451 return {'CellToolbar': CellToolbar};
448 });
452 });
@@ -1,148 +1,150 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 // Example Use for the CellToolbar library
4 // Example Use for the CellToolbar library
5 // add the following to your custom.js to load
5 // add the following to your custom.js to load
6 // Celltoolbar UI for slideshow
6 // Celltoolbar UI for slideshow
7
7
8 // ```
8 // ```
9 // $.getScript('/static/js/celltoolbarpresets/example.js');
9 // $.getScript('/static/js/celltoolbarpresets/example.js');
10 // ```
10 // ```
11 define([
11 define([
12 'jquery',
12 'jquery',
13 'notebook/js/celltoolbar',
13 'notebook/js/celltoolbar',
14 ], function($, celltoolbar) {
14 ], function($, celltoolbar) {
15 "use strict";
15 "use strict";
16
16
17 var CellToolbar = celltoolbar.CellToolbar;
17 var CellToolbar = celltoolbar.CellToolbar;
18
18
19 var example_preset = [];
19 var example_preset = [];
20
20
21 var simple_button = function(div, cell) {
21 var simple_button = function(div, cell) {
22 var button_container = $(div);
22 var button_container = $(div);
23 var button = $('<div/>').button({icons:{primary:'ui-icon-locked'}});
23 var button = $('<div/>').button({icons:{primary:'ui-icon-locked'}});
24 var fun = function(value){
24 var fun = function(value){
25 try{
25 try{
26 if(value){
26 if(value){
27 cell.code_mirror.setOption('readOnly','nocursor');
27 cell.code_mirror.setOption('readOnly','nocursor');
28 button.button('option','icons',{primary:'ui-icon-locked'});
28 button.button('option','icons',{primary:'ui-icon-locked'});
29 } else {
29 } else {
30 cell.code_mirror.setOption('readOnly',false);
30 cell.code_mirror.setOption('readOnly',false);
31 button.button('option','icons',{primary:'ui-icon-unlocked'});
31 button.button('option','icons',{primary:'ui-icon-unlocked'});
32 }
32 }
33 } catch(e){}
33 } catch(e){}
34
34
35 };
35 };
36 fun(cell.metadata.ro);
36 fun(cell.metadata.ro);
37 button.click(function(){
37 button.click(function(){
38 var v = cell.metadata.ro;
38 var v = cell.metadata.ro;
39 var locked = !v;
39 var locked = !v;
40 cell.metadata.ro = locked;
40 cell.metadata.ro = locked;
41 fun(locked);
41 fun(locked);
42 })
42 })
43 .css('height','16px')
43 .css('height','16px')
44 .css('width','35px');
44 .css('width','35px');
45 button_container.append(button);
45 button_container.append(button);
46 };
46 };
47
47
48 CellToolbar.register_callback('example.lock',simple_button);
48 CellToolbar.register_callback('example.lock',simple_button);
49 example_preset.push('example.lock');
49 example_preset.push('example.lock');
50
50
51 var toggle_test = function(div, cell) {
51 var toggle_test = function(div, cell) {
52 var button_container = $(div);
52 var button_container = $(div);
53 var button = $('<div/>')
53 var button = $('<div/>')
54 .button({label:String(cell.metadata.foo)}).
54 .button({label:String(cell.metadata.foo)}).
55 css('width','65px');
55 css('width','65px');
56 button.click(function(){
56 button.click(function(){
57 var v = cell.metadata.foo;
57 var v = cell.metadata.foo;
58 cell.metadata.foo = !v;
58 cell.metadata.foo = !v;
59 button.button("option","label",String(!v));
59 button.button("option","label",String(!v));
60 });
60 });
61 button_container.append(button);
61 button_container.append(button);
62 };
62 };
63
63
64 CellToolbar.register_callback('example.toggle',toggle_test);
64 CellToolbar.register_callback('example.toggle',toggle_test);
65 example_preset.push('example.toggle');
65 example_preset.push('example.toggle');
66
66
67 var checkbox_test = CellToolbar.utils.checkbox_ui_generator('Yes/No',
67 var checkbox_test = CellToolbar.utils.checkbox_ui_generator('Yes/No',
68 // setter
68 // setter
69 function(cell, value){
69 function(cell, value){
70 // we check that the slideshow namespace exist and create it if needed
70 // we check that the slideshow namespace exist and create it if needed
71 if (cell.metadata.yn_test === undefined){cell.metadata.yn_test = {};}
71 if (cell.metadata.yn_test === undefined){cell.metadata.yn_test = {};}
72 // set the value
72 // set the value
73 cell.metadata.yn_test.value = value;
73 cell.metadata.yn_test.value = value;
74 },
74 },
75 //geter
75 //geter
76 function(cell){ var ns = cell.metadata.yn_test;
76 function(cell){ var ns = cell.metadata.yn_test;
77 // if the slideshow namespace does not exist return `undefined`
77 // if the slideshow namespace does not exist return `undefined`
78 // (will be interpreted as `false` by checkbox) otherwise
78 // (will be interpreted as `false` by checkbox) otherwise
79 // return the value
79 // return the value
80 return (ns === undefined)? undefined: ns.value;
80 return (ns === undefined)? undefined: ns.value;
81 }
81 }
82 );
82 );
83
83
84
84
85 CellToolbar.register_callback('example.checkbox',checkbox_test);
85 CellToolbar.register_callback('example.checkbox',checkbox_test);
86 example_preset.push('example.checkbox');
86 example_preset.push('example.checkbox');
87
87
88 var select_test = CellToolbar.utils.select_ui_generator([
88 var select_test = CellToolbar.utils.select_ui_generator([
89 ["-" ,undefined ],
89 ["-" ,undefined ],
90 ["Header Slide" ,"header_slide" ],
90 ["Header Slide" ,"header_slide" ],
91 ["Slide" ,"slide" ],
91 ["Slide" ,"slide" ],
92 ["Fragment" ,"fragment" ],
92 ["Fragment" ,"fragment" ],
93 ["Skip" ,"skip" ],
93 ["Skip" ,"skip" ],
94 ],
94 ],
95 // setter
95 // setter
96 function(cell,value){
96 function(cell,value){
97 // we check that the slideshow namespace exist and create it if needed
97 // we check that the slideshow namespace exist and create it if needed
98 if (cell.metadata.test === undefined){cell.metadata.test = {};}
98 if (cell.metadata.test === undefined){cell.metadata.test = {};}
99 // set the value
99 // set the value
100 cell.metadata.test.slide_type = value;
100 cell.metadata.test.slide_type = value;
101 },
101 },
102 //geter
102 //geter
103 function(cell){ var ns = cell.metadata.test;
103 function(cell){ var ns = cell.metadata.test;
104 // if the slideshow namespace does not exist return `undefined`
104 // if the slideshow namespace does not exist return `undefined`
105 // (will be interpreted as `false` by checkbox) otherwise
105 // (will be interpreted as `false` by checkbox) otherwise
106 // return the value
106 // return the value
107 return (ns === undefined)? undefined: ns.slide_type;
107 return (ns === undefined)? undefined: ns.slide_type;
108 });
108 });
109
109
110 CellToolbar.register_callback('example.select',select_test);
110 CellToolbar.register_callback('example.select',select_test);
111 example_preset.push('example.select');
111 example_preset.push('example.select');
112
112
113 var simple_dialog = function(title,text){
113 var simple_dialog = function(title,text){
114 var dlg = $('<div/>').attr('title',title)
114 var dlg = $('<div/>').attr('title',title)
115 .append($('<p/>').text(text));
115 .append($('<p/>').text(text));
116 $(dlg).dialog({
116 $(dlg).dialog({
117 autoOpen: true,
117 autoOpen: true,
118 height: 300,
118 height: 300,
119 width: 650,
119 width: 650,
120 modal: true,
120 modal: true,
121 close: function() {
121 close: function() {
122 //cleanup on close
122 /**
123 *cleanup on close
124 */
123 $(this).remove();
125 $(this).remove();
124 }
126 }
125 });
127 });
126 };
128 };
127
129
128 var add_simple_dialog_button = function(div, cell) {
130 var add_simple_dialog_button = function(div, cell) {
129 var help_text = ["This is the Metadata editting UI.",
131 var help_text = ["This is the Metadata editting UI.",
130 "It heavily rely on plugin to work ",
132 "It heavily rely on plugin to work ",
131 "and is still under developpement. You shouldn't wait too long before",
133 "and is still under developpement. You shouldn't wait too long before",
132 " seeing some customisable buttons in those toolbar."
134 " seeing some customisable buttons in those toolbar."
133 ].join('\n');
135 ].join('\n');
134 var button_container = $(div);
136 var button_container = $(div);
135 var button = $('<div/>').button({label:'?'})
137 var button = $('<div/>').button({label:'?'})
136 .click(function(){simple_dialog('help',help_text); return false;});
138 .click(function(){simple_dialog('help',help_text); return false;});
137 button_container.append(button);
139 button_container.append(button);
138 };
140 };
139
141
140 var register = function (notebook) {
142 var register = function (notebook) {
141 CellToolbar.register_callback('example.help',add_simple_dialog_button);
143 CellToolbar.register_callback('example.help',add_simple_dialog_button);
142 example_preset.push('example.help');
144 example_preset.push('example.help');
143
145
144 CellToolbar.register_preset('Example',example_preset, notebook);
146 CellToolbar.register_preset('Example',example_preset, notebook);
145 console.log('Example extension for metadata editing loaded.');
147 console.log('Example extension for metadata editing loaded.');
146 };
148 };
147 return {'register': register};
149 return {'register': register};
148 });
150 });
@@ -1,571 +1,573 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 *
4 *
5 *
5 *
6 * @module codecell
6 * @module codecell
7 * @namespace codecell
7 * @namespace codecell
8 * @class CodeCell
8 * @class CodeCell
9 */
9 */
10
10
11
11
12 define([
12 define([
13 'base/js/namespace',
13 'base/js/namespace',
14 'jquery',
14 'jquery',
15 'base/js/utils',
15 'base/js/utils',
16 'base/js/keyboard',
16 'base/js/keyboard',
17 'notebook/js/cell',
17 'notebook/js/cell',
18 'notebook/js/outputarea',
18 'notebook/js/outputarea',
19 'notebook/js/completer',
19 'notebook/js/completer',
20 'notebook/js/celltoolbar',
20 'notebook/js/celltoolbar',
21 'codemirror/lib/codemirror',
21 'codemirror/lib/codemirror',
22 'codemirror/mode/python/python',
22 'codemirror/mode/python/python',
23 'notebook/js/codemirror-ipython'
23 'notebook/js/codemirror-ipython'
24 ], function(IPython, $, utils, keyboard, cell, outputarea, completer, celltoolbar, CodeMirror, cmpython, cmip) {
24 ], function(IPython, $, utils, keyboard, cell, outputarea, completer, celltoolbar, CodeMirror, cmpython, cmip) {
25 "use strict";
25 "use strict";
26
26
27 var Cell = cell.Cell;
27 var Cell = cell.Cell;
28
28
29 /* local util for codemirror */
29 /* local util for codemirror */
30 var posEq = function(a, b) {return a.line == b.line && a.ch == b.ch;};
30 var posEq = function(a, b) {return a.line == b.line && a.ch == b.ch;};
31
31
32 /**
32 /**
33 *
33 *
34 * function to delete until previous non blanking space character
34 * function to delete until previous non blanking space character
35 * or first multiple of 4 tabstop.
35 * or first multiple of 4 tabstop.
36 * @private
36 * @private
37 */
37 */
38 CodeMirror.commands.delSpaceToPrevTabStop = function(cm){
38 CodeMirror.commands.delSpaceToPrevTabStop = function(cm){
39 var from = cm.getCursor(true), to = cm.getCursor(false), sel = !posEq(from, to);
39 var from = cm.getCursor(true), to = cm.getCursor(false), sel = !posEq(from, to);
40 if (!posEq(from, to)) { cm.replaceRange("", from, to); return; }
40 if (!posEq(from, to)) { cm.replaceRange("", from, to); return; }
41 var cur = cm.getCursor(), line = cm.getLine(cur.line);
41 var cur = cm.getCursor(), line = cm.getLine(cur.line);
42 var tabsize = cm.getOption('tabSize');
42 var tabsize = cm.getOption('tabSize');
43 var chToPrevTabStop = cur.ch-(Math.ceil(cur.ch/tabsize)-1)*tabsize;
43 var chToPrevTabStop = cur.ch-(Math.ceil(cur.ch/tabsize)-1)*tabsize;
44 from = {ch:cur.ch-chToPrevTabStop,line:cur.line};
44 from = {ch:cur.ch-chToPrevTabStop,line:cur.line};
45 var select = cm.getRange(from,cur);
45 var select = cm.getRange(from,cur);
46 if( select.match(/^\ +$/) !== null){
46 if( select.match(/^\ +$/) !== null){
47 cm.replaceRange("",from,cur);
47 cm.replaceRange("",from,cur);
48 } else {
48 } else {
49 cm.deleteH(-1,"char");
49 cm.deleteH(-1,"char");
50 }
50 }
51 };
51 };
52
52
53 var keycodes = keyboard.keycodes;
53 var keycodes = keyboard.keycodes;
54
54
55 var CodeCell = function (kernel, options) {
55 var CodeCell = function (kernel, options) {
56 // Constructor
56 /**
57 //
57 * Constructor
58 // A Cell conceived to write code.
58 *
59 //
59 * A Cell conceived to write code.
60 // Parameters:
60 *
61 // kernel: Kernel instance
61 * Parameters:
62 // The kernel doesn't have to be set at creation time, in that case
62 * kernel: Kernel instance
63 // it will be null and set_kernel has to be called later.
63 * The kernel doesn't have to be set at creation time, in that case
64 // options: dictionary
64 * it will be null and set_kernel has to be called later.
65 // Dictionary of keyword arguments.
65 * options: dictionary
66 // events: $(Events) instance
66 * Dictionary of keyword arguments.
67 // config: dictionary
67 * events: $(Events) instance
68 // keyboard_manager: KeyboardManager instance
68 * config: dictionary
69 // notebook: Notebook instance
69 * keyboard_manager: KeyboardManager instance
70 // tooltip: Tooltip instance
70 * notebook: Notebook instance
71 * tooltip: Tooltip instance
72 */
71 this.kernel = kernel || null;
73 this.kernel = kernel || null;
72 this.notebook = options.notebook;
74 this.notebook = options.notebook;
73 this.collapsed = false;
75 this.collapsed = false;
74 this.events = options.events;
76 this.events = options.events;
75 this.tooltip = options.tooltip;
77 this.tooltip = options.tooltip;
76 this.config = options.config;
78 this.config = options.config;
77
79
78 // create all attributed in constructor function
80 // create all attributed in constructor function
79 // even if null for V8 VM optimisation
81 // even if null for V8 VM optimisation
80 this.input_prompt_number = null;
82 this.input_prompt_number = null;
81 this.celltoolbar = null;
83 this.celltoolbar = null;
82 this.output_area = null;
84 this.output_area = null;
83 // Keep a stack of the 'active' output areas (where active means the
85 // Keep a stack of the 'active' output areas (where active means the
84 // output area that recieves output). When a user activates an output
86 // output area that recieves output). When a user activates an output
85 // area, it gets pushed to the stack. Then, when the output area is
87 // area, it gets pushed to the stack. Then, when the output area is
86 // deactivated, it's popped from the stack. When the stack is empty,
88 // deactivated, it's popped from the stack. When the stack is empty,
87 // the cell's output area is used.
89 // the cell's output area is used.
88 this.active_output_areas = [];
90 this.active_output_areas = [];
89 var that = this;
91 var that = this;
90 Object.defineProperty(this, 'active_output_area', {
92 Object.defineProperty(this, 'active_output_area', {
91 get: function() {
93 get: function() {
92 if (that.active_output_areas && that.active_output_areas.length > 0) {
94 if (that.active_output_areas && that.active_output_areas.length > 0) {
93 return that.active_output_areas[that.active_output_areas.length-1];
95 return that.active_output_areas[that.active_output_areas.length-1];
94 } else {
96 } else {
95 return that.output_area;
97 return that.output_area;
96 }
98 }
97 },
99 },
98 });
100 });
99
101
100 this.last_msg_id = null;
102 this.last_msg_id = null;
101 this.completer = null;
103 this.completer = null;
102
104
103
105
104 var config = utils.mergeopt(CodeCell, this.config);
106 var config = utils.mergeopt(CodeCell, this.config);
105 Cell.apply(this,[{
107 Cell.apply(this,[{
106 config: config,
108 config: config,
107 keyboard_manager: options.keyboard_manager,
109 keyboard_manager: options.keyboard_manager,
108 events: this.events}]);
110 events: this.events}]);
109
111
110 // Attributes we want to override in this subclass.
112 // Attributes we want to override in this subclass.
111 this.cell_type = "code";
113 this.cell_type = "code";
112 this.element.focusout(
114 this.element.focusout(
113 function() { that.auto_highlight(); }
115 function() { that.auto_highlight(); }
114 );
116 );
115 };
117 };
116
118
117 CodeCell.options_default = {
119 CodeCell.options_default = {
118 cm_config : {
120 cm_config : {
119 extraKeys: {
121 extraKeys: {
120 "Tab" : "indentMore",
122 "Tab" : "indentMore",
121 "Shift-Tab" : "indentLess",
123 "Shift-Tab" : "indentLess",
122 "Backspace" : "delSpaceToPrevTabStop",
124 "Backspace" : "delSpaceToPrevTabStop",
123 "Cmd-/" : "toggleComment",
125 "Cmd-/" : "toggleComment",
124 "Ctrl-/" : "toggleComment"
126 "Ctrl-/" : "toggleComment"
125 },
127 },
126 mode: 'ipython',
128 mode: 'ipython',
127 theme: 'ipython',
129 theme: 'ipython',
128 matchBrackets: true
130 matchBrackets: true
129 }
131 }
130 };
132 };
131
133
132 CodeCell.msg_cells = {};
134 CodeCell.msg_cells = {};
133
135
134 CodeCell.prototype = Object.create(Cell.prototype);
136 CodeCell.prototype = Object.create(Cell.prototype);
135
137
136 /**
138 /**
137 * @method push_output_area
139 * @method push_output_area
138 */
140 */
139 CodeCell.prototype.push_output_area = function (output_area) {
141 CodeCell.prototype.push_output_area = function (output_area) {
140 this.active_output_areas.push(output_area);
142 this.active_output_areas.push(output_area);
141 };
143 };
142
144
143 /**
145 /**
144 * @method pop_output_area
146 * @method pop_output_area
145 */
147 */
146 CodeCell.prototype.pop_output_area = function (output_area) {
148 CodeCell.prototype.pop_output_area = function (output_area) {
147 var index = this.active_output_areas.lastIndexOf(output_area);
149 var index = this.active_output_areas.lastIndexOf(output_area);
148 if (index > -1) {
150 if (index > -1) {
149 this.active_output_areas.splice(index, 1);
151 this.active_output_areas.splice(index, 1);
150 }
152 }
151 };
153 };
152
154
153 /**
155 /**
154 * @method auto_highlight
156 * @method auto_highlight
155 */
157 */
156 CodeCell.prototype.auto_highlight = function () {
158 CodeCell.prototype.auto_highlight = function () {
157 this._auto_highlight(this.config.cell_magic_highlight);
159 this._auto_highlight(this.config.cell_magic_highlight);
158 };
160 };
159
161
160 /** @method create_element */
162 /** @method create_element */
161 CodeCell.prototype.create_element = function () {
163 CodeCell.prototype.create_element = function () {
162 Cell.prototype.create_element.apply(this, arguments);
164 Cell.prototype.create_element.apply(this, arguments);
163
165
164 var cell = $('<div></div>').addClass('cell code_cell');
166 var cell = $('<div></div>').addClass('cell code_cell');
165 cell.attr('tabindex','2');
167 cell.attr('tabindex','2');
166
168
167 var input = $('<div></div>').addClass('input');
169 var input = $('<div></div>').addClass('input');
168 var prompt = $('<div/>').addClass('prompt input_prompt');
170 var prompt = $('<div/>').addClass('prompt input_prompt');
169 var inner_cell = $('<div/>').addClass('inner_cell');
171 var inner_cell = $('<div/>').addClass('inner_cell');
170 this.celltoolbar = new celltoolbar.CellToolbar({
172 this.celltoolbar = new celltoolbar.CellToolbar({
171 cell: this,
173 cell: this,
172 notebook: this.notebook});
174 notebook: this.notebook});
173 inner_cell.append(this.celltoolbar.element);
175 inner_cell.append(this.celltoolbar.element);
174 var input_area = $('<div/>').addClass('input_area');
176 var input_area = $('<div/>').addClass('input_area');
175 this.code_mirror = new CodeMirror(input_area.get(0), this.cm_config);
177 this.code_mirror = new CodeMirror(input_area.get(0), this.cm_config);
176 this.code_mirror.on('keydown', $.proxy(this.handle_keyevent,this))
178 this.code_mirror.on('keydown', $.proxy(this.handle_keyevent,this))
177 $(this.code_mirror.getInputField()).attr("spellcheck", "false");
179 $(this.code_mirror.getInputField()).attr("spellcheck", "false");
178 inner_cell.append(input_area);
180 inner_cell.append(input_area);
179 input.append(prompt).append(inner_cell);
181 input.append(prompt).append(inner_cell);
180
182
181 var widget_area = $('<div/>')
183 var widget_area = $('<div/>')
182 .addClass('widget-area')
184 .addClass('widget-area')
183 .hide();
185 .hide();
184 this.widget_area = widget_area;
186 this.widget_area = widget_area;
185 var widget_prompt = $('<div/>')
187 var widget_prompt = $('<div/>')
186 .addClass('prompt')
188 .addClass('prompt')
187 .appendTo(widget_area);
189 .appendTo(widget_area);
188 var widget_subarea = $('<div/>')
190 var widget_subarea = $('<div/>')
189 .addClass('widget-subarea')
191 .addClass('widget-subarea')
190 .appendTo(widget_area);
192 .appendTo(widget_area);
191 this.widget_subarea = widget_subarea;
193 this.widget_subarea = widget_subarea;
192 var widget_clear_buton = $('<button />')
194 var widget_clear_buton = $('<button />')
193 .addClass('close')
195 .addClass('close')
194 .html('&times;')
196 .html('&times;')
195 .click(function() {
197 .click(function() {
196 widget_area.slideUp('', function(){ widget_subarea.html(''); });
198 widget_area.slideUp('', function(){ widget_subarea.html(''); });
197 })
199 })
198 .appendTo(widget_prompt);
200 .appendTo(widget_prompt);
199
201
200 var output = $('<div></div>');
202 var output = $('<div></div>');
201 cell.append(input).append(widget_area).append(output);
203 cell.append(input).append(widget_area).append(output);
202 this.element = cell;
204 this.element = cell;
203 this.output_area = new outputarea.OutputArea({
205 this.output_area = new outputarea.OutputArea({
204 selector: output,
206 selector: output,
205 prompt_area: true,
207 prompt_area: true,
206 events: this.events,
208 events: this.events,
207 keyboard_manager: this.keyboard_manager});
209 keyboard_manager: this.keyboard_manager});
208 this.completer = new completer.Completer(this, this.events);
210 this.completer = new completer.Completer(this, this.events);
209 };
211 };
210
212
211 /** @method bind_events */
213 /** @method bind_events */
212 CodeCell.prototype.bind_events = function () {
214 CodeCell.prototype.bind_events = function () {
213 Cell.prototype.bind_events.apply(this);
215 Cell.prototype.bind_events.apply(this);
214 var that = this;
216 var that = this;
215
217
216 this.element.focusout(
218 this.element.focusout(
217 function() { that.auto_highlight(); }
219 function() { that.auto_highlight(); }
218 );
220 );
219 };
221 };
220
222
221
223
222 /**
224 /**
223 * This method gets called in CodeMirror's onKeyDown/onKeyPress
225 * This method gets called in CodeMirror's onKeyDown/onKeyPress
224 * handlers and is used to provide custom key handling. Its return
226 * handlers and is used to provide custom key handling. Its return
225 * value is used to determine if CodeMirror should ignore the event:
227 * value is used to determine if CodeMirror should ignore the event:
226 * true = ignore, false = don't ignore.
228 * true = ignore, false = don't ignore.
227 * @method handle_codemirror_keyevent
229 * @method handle_codemirror_keyevent
228 */
230 */
229
231
230 CodeCell.prototype.handle_codemirror_keyevent = function (editor, event) {
232 CodeCell.prototype.handle_codemirror_keyevent = function (editor, event) {
231
233
232 var that = this;
234 var that = this;
233 // whatever key is pressed, first, cancel the tooltip request before
235 // whatever key is pressed, first, cancel the tooltip request before
234 // they are sent, and remove tooltip if any, except for tab again
236 // they are sent, and remove tooltip if any, except for tab again
235 var tooltip_closed = null;
237 var tooltip_closed = null;
236 if (event.type === 'keydown' && event.which != keycodes.tab ) {
238 if (event.type === 'keydown' && event.which != keycodes.tab ) {
237 tooltip_closed = this.tooltip.remove_and_cancel_tooltip();
239 tooltip_closed = this.tooltip.remove_and_cancel_tooltip();
238 }
240 }
239
241
240 var cur = editor.getCursor();
242 var cur = editor.getCursor();
241 if (event.keyCode === keycodes.enter){
243 if (event.keyCode === keycodes.enter){
242 this.auto_highlight();
244 this.auto_highlight();
243 }
245 }
244
246
245 if (event.which === keycodes.down && event.type === 'keypress' && this.tooltip.time_before_tooltip >= 0) {
247 if (event.which === keycodes.down && event.type === 'keypress' && this.tooltip.time_before_tooltip >= 0) {
246 // triger on keypress (!) otherwise inconsistent event.which depending on plateform
248 // triger on keypress (!) otherwise inconsistent event.which depending on plateform
247 // browser and keyboard layout !
249 // browser and keyboard layout !
248 // Pressing '(' , request tooltip, don't forget to reappend it
250 // Pressing '(' , request tooltip, don't forget to reappend it
249 // The second argument says to hide the tooltip if the docstring
251 // The second argument says to hide the tooltip if the docstring
250 // is actually empty
252 // is actually empty
251 this.tooltip.pending(that, true);
253 this.tooltip.pending(that, true);
252 } else if ( tooltip_closed && event.which === keycodes.esc && event.type === 'keydown') {
254 } else if ( tooltip_closed && event.which === keycodes.esc && event.type === 'keydown') {
253 // If tooltip is active, cancel it. The call to
255 // If tooltip is active, cancel it. The call to
254 // remove_and_cancel_tooltip above doesn't pass, force=true.
256 // remove_and_cancel_tooltip above doesn't pass, force=true.
255 // Because of this it won't actually close the tooltip
257 // Because of this it won't actually close the tooltip
256 // if it is in sticky mode. Thus, we have to check again if it is open
258 // if it is in sticky mode. Thus, we have to check again if it is open
257 // and close it with force=true.
259 // and close it with force=true.
258 if (!this.tooltip._hidden) {
260 if (!this.tooltip._hidden) {
259 this.tooltip.remove_and_cancel_tooltip(true);
261 this.tooltip.remove_and_cancel_tooltip(true);
260 }
262 }
261 // If we closed the tooltip, don't let CM or the global handlers
263 // If we closed the tooltip, don't let CM or the global handlers
262 // handle this event.
264 // handle this event.
263 event.codemirrorIgnore = true;
265 event.codemirrorIgnore = true;
264 event.preventDefault();
266 event.preventDefault();
265 return true;
267 return true;
266 } else if (event.keyCode === keycodes.tab && event.type === 'keydown' && event.shiftKey) {
268 } else if (event.keyCode === keycodes.tab && event.type === 'keydown' && event.shiftKey) {
267 if (editor.somethingSelected() || editor.getSelections().length !== 1){
269 if (editor.somethingSelected() || editor.getSelections().length !== 1){
268 var anchor = editor.getCursor("anchor");
270 var anchor = editor.getCursor("anchor");
269 var head = editor.getCursor("head");
271 var head = editor.getCursor("head");
270 if( anchor.line != head.line){
272 if( anchor.line != head.line){
271 return false;
273 return false;
272 }
274 }
273 }
275 }
274 this.tooltip.request(that);
276 this.tooltip.request(that);
275 event.codemirrorIgnore = true;
277 event.codemirrorIgnore = true;
276 event.preventDefault();
278 event.preventDefault();
277 return true;
279 return true;
278 } else if (event.keyCode === keycodes.tab && event.type == 'keydown') {
280 } else if (event.keyCode === keycodes.tab && event.type == 'keydown') {
279 // Tab completion.
281 // Tab completion.
280 this.tooltip.remove_and_cancel_tooltip();
282 this.tooltip.remove_and_cancel_tooltip();
281
283
282 // completion does not work on multicursor, it might be possible though in some cases
284 // completion does not work on multicursor, it might be possible though in some cases
283 if (editor.somethingSelected() || editor.getSelections().length > 1) {
285 if (editor.somethingSelected() || editor.getSelections().length > 1) {
284 return false;
286 return false;
285 }
287 }
286 var pre_cursor = editor.getRange({line:cur.line,ch:0},cur);
288 var pre_cursor = editor.getRange({line:cur.line,ch:0},cur);
287 if (pre_cursor.trim() === "") {
289 if (pre_cursor.trim() === "") {
288 // Don't autocomplete if the part of the line before the cursor
290 // Don't autocomplete if the part of the line before the cursor
289 // is empty. In this case, let CodeMirror handle indentation.
291 // is empty. In this case, let CodeMirror handle indentation.
290 return false;
292 return false;
291 } else {
293 } else {
292 event.codemirrorIgnore = true;
294 event.codemirrorIgnore = true;
293 event.preventDefault();
295 event.preventDefault();
294 this.completer.startCompletion();
296 this.completer.startCompletion();
295 return true;
297 return true;
296 }
298 }
297 }
299 }
298
300
299 // keyboard event wasn't one of those unique to code cells, let's see
301 // keyboard event wasn't one of those unique to code cells, let's see
300 // if it's one of the generic ones (i.e. check edit mode shortcuts)
302 // if it's one of the generic ones (i.e. check edit mode shortcuts)
301 return Cell.prototype.handle_codemirror_keyevent.apply(this, [editor, event]);
303 return Cell.prototype.handle_codemirror_keyevent.apply(this, [editor, event]);
302 };
304 };
303
305
304 // Kernel related calls.
306 // Kernel related calls.
305
307
306 CodeCell.prototype.set_kernel = function (kernel) {
308 CodeCell.prototype.set_kernel = function (kernel) {
307 this.kernel = kernel;
309 this.kernel = kernel;
308 };
310 };
309
311
310 /**
312 /**
311 * Execute current code cell to the kernel
313 * Execute current code cell to the kernel
312 * @method execute
314 * @method execute
313 */
315 */
314 CodeCell.prototype.execute = function () {
316 CodeCell.prototype.execute = function () {
315 if (!this.kernel || !this.kernel.is_connected()) {
317 if (!this.kernel || !this.kernel.is_connected()) {
316 console.log("Can't execute, kernel is not connected.");
318 console.log("Can't execute, kernel is not connected.");
317 return;
319 return;
318 }
320 }
319
321
320 this.active_output_area.clear_output();
322 this.active_output_area.clear_output();
321
323
322 // Clear widget area
324 // Clear widget area
323 this.widget_subarea.html('');
325 this.widget_subarea.html('');
324 this.widget_subarea.height('');
326 this.widget_subarea.height('');
325 this.widget_area.height('');
327 this.widget_area.height('');
326 this.widget_area.hide();
328 this.widget_area.hide();
327
329
328 this.set_input_prompt('*');
330 this.set_input_prompt('*');
329 this.element.addClass("running");
331 this.element.addClass("running");
330 if (this.last_msg_id) {
332 if (this.last_msg_id) {
331 this.kernel.clear_callbacks_for_msg(this.last_msg_id);
333 this.kernel.clear_callbacks_for_msg(this.last_msg_id);
332 }
334 }
333 var callbacks = this.get_callbacks();
335 var callbacks = this.get_callbacks();
334
336
335 var old_msg_id = this.last_msg_id;
337 var old_msg_id = this.last_msg_id;
336 this.last_msg_id = this.kernel.execute(this.get_text(), callbacks, {silent: false, store_history: true});
338 this.last_msg_id = this.kernel.execute(this.get_text(), callbacks, {silent: false, store_history: true});
337 if (old_msg_id) {
339 if (old_msg_id) {
338 delete CodeCell.msg_cells[old_msg_id];
340 delete CodeCell.msg_cells[old_msg_id];
339 }
341 }
340 CodeCell.msg_cells[this.last_msg_id] = this;
342 CodeCell.msg_cells[this.last_msg_id] = this;
341 this.render();
343 this.render();
342 this.events.trigger('execute.CodeCell', {cell: this});
344 this.events.trigger('execute.CodeCell', {cell: this});
343 };
345 };
344
346
345 /**
347 /**
346 * Construct the default callbacks for
348 * Construct the default callbacks for
347 * @method get_callbacks
349 * @method get_callbacks
348 */
350 */
349 CodeCell.prototype.get_callbacks = function () {
351 CodeCell.prototype.get_callbacks = function () {
350 var that = this;
352 var that = this;
351 return {
353 return {
352 shell : {
354 shell : {
353 reply : $.proxy(this._handle_execute_reply, this),
355 reply : $.proxy(this._handle_execute_reply, this),
354 payload : {
356 payload : {
355 set_next_input : $.proxy(this._handle_set_next_input, this),
357 set_next_input : $.proxy(this._handle_set_next_input, this),
356 page : $.proxy(this._open_with_pager, this)
358 page : $.proxy(this._open_with_pager, this)
357 }
359 }
358 },
360 },
359 iopub : {
361 iopub : {
360 output : function() {
362 output : function() {
361 that.active_output_area.handle_output.apply(that.active_output_area, arguments);
363 that.active_output_area.handle_output.apply(that.active_output_area, arguments);
362 },
364 },
363 clear_output : function() {
365 clear_output : function() {
364 that.active_output_area.handle_clear_output.apply(that.active_output_area, arguments);
366 that.active_output_area.handle_clear_output.apply(that.active_output_area, arguments);
365 },
367 },
366 },
368 },
367 input : $.proxy(this._handle_input_request, this)
369 input : $.proxy(this._handle_input_request, this)
368 };
370 };
369 };
371 };
370
372
371 CodeCell.prototype._open_with_pager = function (payload) {
373 CodeCell.prototype._open_with_pager = function (payload) {
372 this.events.trigger('open_with_text.Pager', payload);
374 this.events.trigger('open_with_text.Pager', payload);
373 };
375 };
374
376
375 /**
377 /**
376 * @method _handle_execute_reply
378 * @method _handle_execute_reply
377 * @private
379 * @private
378 */
380 */
379 CodeCell.prototype._handle_execute_reply = function (msg) {
381 CodeCell.prototype._handle_execute_reply = function (msg) {
380 this.set_input_prompt(msg.content.execution_count);
382 this.set_input_prompt(msg.content.execution_count);
381 this.element.removeClass("running");
383 this.element.removeClass("running");
382 this.events.trigger('set_dirty.Notebook', {value: true});
384 this.events.trigger('set_dirty.Notebook', {value: true});
383 };
385 };
384
386
385 /**
387 /**
386 * @method _handle_set_next_input
388 * @method _handle_set_next_input
387 * @private
389 * @private
388 */
390 */
389 CodeCell.prototype._handle_set_next_input = function (payload) {
391 CodeCell.prototype._handle_set_next_input = function (payload) {
390 var data = {'cell': this, 'text': payload.text};
392 var data = {'cell': this, 'text': payload.text};
391 this.events.trigger('set_next_input.Notebook', data);
393 this.events.trigger('set_next_input.Notebook', data);
392 };
394 };
393
395
394 /**
396 /**
395 * @method _handle_input_request
397 * @method _handle_input_request
396 * @private
398 * @private
397 */
399 */
398 CodeCell.prototype._handle_input_request = function (msg) {
400 CodeCell.prototype._handle_input_request = function (msg) {
399 this.active_output_area.append_raw_input(msg);
401 this.active_output_area.append_raw_input(msg);
400 };
402 };
401
403
402
404
403 // Basic cell manipulation.
405 // Basic cell manipulation.
404
406
405 CodeCell.prototype.select = function () {
407 CodeCell.prototype.select = function () {
406 var cont = Cell.prototype.select.apply(this);
408 var cont = Cell.prototype.select.apply(this);
407 if (cont) {
409 if (cont) {
408 this.code_mirror.refresh();
410 this.code_mirror.refresh();
409 this.auto_highlight();
411 this.auto_highlight();
410 }
412 }
411 return cont;
413 return cont;
412 };
414 };
413
415
414 CodeCell.prototype.render = function () {
416 CodeCell.prototype.render = function () {
415 var cont = Cell.prototype.render.apply(this);
417 var cont = Cell.prototype.render.apply(this);
416 // Always execute, even if we are already in the rendered state
418 // Always execute, even if we are already in the rendered state
417 return cont;
419 return cont;
418 };
420 };
419
421
420 CodeCell.prototype.select_all = function () {
422 CodeCell.prototype.select_all = function () {
421 var start = {line: 0, ch: 0};
423 var start = {line: 0, ch: 0};
422 var nlines = this.code_mirror.lineCount();
424 var nlines = this.code_mirror.lineCount();
423 var last_line = this.code_mirror.getLine(nlines-1);
425 var last_line = this.code_mirror.getLine(nlines-1);
424 var end = {line: nlines-1, ch: last_line.length};
426 var end = {line: nlines-1, ch: last_line.length};
425 this.code_mirror.setSelection(start, end);
427 this.code_mirror.setSelection(start, end);
426 };
428 };
427
429
428
430
429 CodeCell.prototype.collapse_output = function () {
431 CodeCell.prototype.collapse_output = function () {
430 this.output_area.collapse();
432 this.output_area.collapse();
431 };
433 };
432
434
433
435
434 CodeCell.prototype.expand_output = function () {
436 CodeCell.prototype.expand_output = function () {
435 this.output_area.expand();
437 this.output_area.expand();
436 this.output_area.unscroll_area();
438 this.output_area.unscroll_area();
437 };
439 };
438
440
439 CodeCell.prototype.scroll_output = function () {
441 CodeCell.prototype.scroll_output = function () {
440 this.output_area.expand();
442 this.output_area.expand();
441 this.output_area.scroll_if_long();
443 this.output_area.scroll_if_long();
442 };
444 };
443
445
444 CodeCell.prototype.toggle_output = function () {
446 CodeCell.prototype.toggle_output = function () {
445 this.output_area.toggle_output();
447 this.output_area.toggle_output();
446 };
448 };
447
449
448 CodeCell.prototype.toggle_output_scroll = function () {
450 CodeCell.prototype.toggle_output_scroll = function () {
449 this.output_area.toggle_scroll();
451 this.output_area.toggle_scroll();
450 };
452 };
451
453
452
454
453 CodeCell.input_prompt_classical = function (prompt_value, lines_number) {
455 CodeCell.input_prompt_classical = function (prompt_value, lines_number) {
454 var ns;
456 var ns;
455 if (prompt_value === undefined || prompt_value === null) {
457 if (prompt_value === undefined || prompt_value === null) {
456 ns = "&nbsp;";
458 ns = "&nbsp;";
457 } else {
459 } else {
458 ns = encodeURIComponent(prompt_value);
460 ns = encodeURIComponent(prompt_value);
459 }
461 }
460 return 'In&nbsp;[' + ns + ']:';
462 return 'In&nbsp;[' + ns + ']:';
461 };
463 };
462
464
463 CodeCell.input_prompt_continuation = function (prompt_value, lines_number) {
465 CodeCell.input_prompt_continuation = function (prompt_value, lines_number) {
464 var html = [CodeCell.input_prompt_classical(prompt_value, lines_number)];
466 var html = [CodeCell.input_prompt_classical(prompt_value, lines_number)];
465 for(var i=1; i < lines_number; i++) {
467 for(var i=1; i < lines_number; i++) {
466 html.push(['...:']);
468 html.push(['...:']);
467 }
469 }
468 return html.join('<br/>');
470 return html.join('<br/>');
469 };
471 };
470
472
471 CodeCell.input_prompt_function = CodeCell.input_prompt_classical;
473 CodeCell.input_prompt_function = CodeCell.input_prompt_classical;
472
474
473
475
474 CodeCell.prototype.set_input_prompt = function (number) {
476 CodeCell.prototype.set_input_prompt = function (number) {
475 var nline = 1;
477 var nline = 1;
476 if (this.code_mirror !== undefined) {
478 if (this.code_mirror !== undefined) {
477 nline = this.code_mirror.lineCount();
479 nline = this.code_mirror.lineCount();
478 }
480 }
479 this.input_prompt_number = number;
481 this.input_prompt_number = number;
480 var prompt_html = CodeCell.input_prompt_function(this.input_prompt_number, nline);
482 var prompt_html = CodeCell.input_prompt_function(this.input_prompt_number, nline);
481 // This HTML call is okay because the user contents are escaped.
483 // This HTML call is okay because the user contents are escaped.
482 this.element.find('div.input_prompt').html(prompt_html);
484 this.element.find('div.input_prompt').html(prompt_html);
483 };
485 };
484
486
485
487
486 CodeCell.prototype.clear_input = function () {
488 CodeCell.prototype.clear_input = function () {
487 this.code_mirror.setValue('');
489 this.code_mirror.setValue('');
488 };
490 };
489
491
490
492
491 CodeCell.prototype.get_text = function () {
493 CodeCell.prototype.get_text = function () {
492 return this.code_mirror.getValue();
494 return this.code_mirror.getValue();
493 };
495 };
494
496
495
497
496 CodeCell.prototype.set_text = function (code) {
498 CodeCell.prototype.set_text = function (code) {
497 return this.code_mirror.setValue(code);
499 return this.code_mirror.setValue(code);
498 };
500 };
499
501
500
502
501 CodeCell.prototype.clear_output = function (wait) {
503 CodeCell.prototype.clear_output = function (wait) {
502 this.active_output_area.clear_output(wait);
504 this.active_output_area.clear_output(wait);
503 this.set_input_prompt();
505 this.set_input_prompt();
504 };
506 };
505
507
506
508
507 // JSON serialization
509 // JSON serialization
508
510
509 CodeCell.prototype.fromJSON = function (data) {
511 CodeCell.prototype.fromJSON = function (data) {
510 Cell.prototype.fromJSON.apply(this, arguments);
512 Cell.prototype.fromJSON.apply(this, arguments);
511 if (data.cell_type === 'code') {
513 if (data.cell_type === 'code') {
512 if (data.source !== undefined) {
514 if (data.source !== undefined) {
513 this.set_text(data.source);
515 this.set_text(data.source);
514 // make this value the starting point, so that we can only undo
516 // make this value the starting point, so that we can only undo
515 // to this state, instead of a blank cell
517 // to this state, instead of a blank cell
516 this.code_mirror.clearHistory();
518 this.code_mirror.clearHistory();
517 this.auto_highlight();
519 this.auto_highlight();
518 }
520 }
519 this.set_input_prompt(data.execution_count);
521 this.set_input_prompt(data.execution_count);
520 this.output_area.trusted = data.metadata.trusted || false;
522 this.output_area.trusted = data.metadata.trusted || false;
521 this.output_area.fromJSON(data.outputs);
523 this.output_area.fromJSON(data.outputs);
522 if (data.metadata.collapsed !== undefined) {
524 if (data.metadata.collapsed !== undefined) {
523 if (data.metadata.collapsed) {
525 if (data.metadata.collapsed) {
524 this.collapse_output();
526 this.collapse_output();
525 } else {
527 } else {
526 this.expand_output();
528 this.expand_output();
527 }
529 }
528 }
530 }
529 }
531 }
530 };
532 };
531
533
532
534
533 CodeCell.prototype.toJSON = function () {
535 CodeCell.prototype.toJSON = function () {
534 var data = Cell.prototype.toJSON.apply(this);
536 var data = Cell.prototype.toJSON.apply(this);
535 data.source = this.get_text();
537 data.source = this.get_text();
536 // is finite protect against undefined and '*' value
538 // is finite protect against undefined and '*' value
537 if (isFinite(this.input_prompt_number)) {
539 if (isFinite(this.input_prompt_number)) {
538 data.execution_count = this.input_prompt_number;
540 data.execution_count = this.input_prompt_number;
539 } else {
541 } else {
540 data.execution_count = null;
542 data.execution_count = null;
541 }
543 }
542 var outputs = this.output_area.toJSON();
544 var outputs = this.output_area.toJSON();
543 data.outputs = outputs;
545 data.outputs = outputs;
544 data.metadata.trusted = this.output_area.trusted;
546 data.metadata.trusted = this.output_area.trusted;
545 data.metadata.collapsed = this.output_area.collapsed;
547 data.metadata.collapsed = this.output_area.collapsed;
546 return data;
548 return data;
547 };
549 };
548
550
549 /**
551 /**
550 * handle cell level logic when a cell is unselected
552 * handle cell level logic when a cell is unselected
551 * @method unselect
553 * @method unselect
552 * @return is the action being taken
554 * @return is the action being taken
553 */
555 */
554 CodeCell.prototype.unselect = function () {
556 CodeCell.prototype.unselect = function () {
555 var cont = Cell.prototype.unselect.apply(this);
557 var cont = Cell.prototype.unselect.apply(this);
556 if (cont) {
558 if (cont) {
557 // When a code cell is usnelected, make sure that the corresponding
559 // When a code cell is usnelected, make sure that the corresponding
558 // tooltip and completer to that cell is closed.
560 // tooltip and completer to that cell is closed.
559 this.tooltip.remove_and_cancel_tooltip(true);
561 this.tooltip.remove_and_cancel_tooltip(true);
560 if (this.completer !== null) {
562 if (this.completer !== null) {
561 this.completer.close();
563 this.completer.close();
562 }
564 }
563 }
565 }
564 return cont;
566 return cont;
565 };
567 };
566
568
567 // Backwards compatability.
569 // Backwards compatability.
568 IPython.CodeCell = CodeCell;
570 IPython.CodeCell = CodeCell;
569
571
570 return {'CodeCell': CodeCell};
572 return {'CodeCell': CodeCell};
571 });
573 });
@@ -1,406 +1,414 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 'codemirror/lib/codemirror',
10 'codemirror/lib/codemirror',
11 ], function(IPython, $, utils, keyboard, CodeMirror) {
11 ], function(IPython, $, utils, keyboard, CodeMirror) {
12 "use strict";
12 "use strict";
13
13
14 // easier key mapping
14 // easier key mapping
15 var keycodes = keyboard.keycodes;
15 var keycodes = keyboard.keycodes;
16
16
17 var prepend_n_prc = function(str, n) {
17 var prepend_n_prc = function(str, n) {
18 for( var i =0 ; i< n ; i++){
18 for( var i =0 ; i< n ; i++){
19 str = '%'+str ;
19 str = '%'+str ;
20 }
20 }
21 return str;
21 return str;
22 };
22 };
23
23
24 var _existing_completion = function(item, completion_array){
24 var _existing_completion = function(item, completion_array){
25 for( var i=0; i < completion_array.length; i++) {
25 for( var i=0; i < completion_array.length; i++) {
26 if (completion_array[i].trim().substr(-item.length) == item) {
26 if (completion_array[i].trim().substr(-item.length) == item) {
27 return true;
27 return true;
28 }
28 }
29 }
29 }
30 return false;
30 return false;
31 };
31 };
32
32
33 // what is the common start of all completions
33 // what is the common start of all completions
34 function shared_start(B, drop_prct) {
34 function shared_start(B, drop_prct) {
35 if (B.length == 1) {
35 if (B.length == 1) {
36 return B[0];
36 return B[0];
37 }
37 }
38 var A = [];
38 var A = [];
39 var common;
39 var common;
40 var min_lead_prct = 10;
40 var min_lead_prct = 10;
41 for (var i = 0; i < B.length; i++) {
41 for (var i = 0; i < B.length; i++) {
42 var str = B[i].str;
42 var str = B[i].str;
43 var localmin = 0;
43 var localmin = 0;
44 if(drop_prct === true){
44 if(drop_prct === true){
45 while ( str.substr(0, 1) == '%') {
45 while ( str.substr(0, 1) == '%') {
46 localmin = localmin+1;
46 localmin = localmin+1;
47 str = str.substring(1);
47 str = str.substring(1);
48 }
48 }
49 }
49 }
50 min_lead_prct = Math.min(min_lead_prct, localmin);
50 min_lead_prct = Math.min(min_lead_prct, localmin);
51 A.push(str);
51 A.push(str);
52 }
52 }
53
53
54 if (A.length > 1) {
54 if (A.length > 1) {
55 var tem1, tem2, s;
55 var tem1, tem2, s;
56 A = A.slice(0).sort();
56 A = A.slice(0).sort();
57 tem1 = A[0];
57 tem1 = A[0];
58 s = tem1.length;
58 s = tem1.length;
59 tem2 = A.pop();
59 tem2 = A.pop();
60 while (s && tem2.indexOf(tem1) == -1) {
60 while (s && tem2.indexOf(tem1) == -1) {
61 tem1 = tem1.substring(0, --s);
61 tem1 = tem1.substring(0, --s);
62 }
62 }
63 if (tem1 === "" || tem2.indexOf(tem1) !== 0) {
63 if (tem1 === "" || tem2.indexOf(tem1) !== 0) {
64 return {
64 return {
65 str:prepend_n_prc('', min_lead_prct),
65 str:prepend_n_prc('', min_lead_prct),
66 type: "computed",
66 type: "computed",
67 from: B[0].from,
67 from: B[0].from,
68 to: B[0].to
68 to: B[0].to
69 };
69 };
70 }
70 }
71 return {
71 return {
72 str: prepend_n_prc(tem1, min_lead_prct),
72 str: prepend_n_prc(tem1, min_lead_prct),
73 type: "computed",
73 type: "computed",
74 from: B[0].from,
74 from: B[0].from,
75 to: B[0].to
75 to: B[0].to
76 };
76 };
77 }
77 }
78 return null;
78 return null;
79 }
79 }
80
80
81
81
82 var Completer = function (cell, events) {
82 var Completer = function (cell, events) {
83 this.cell = cell;
83 this.cell = cell;
84 this.editor = cell.code_mirror;
84 this.editor = cell.code_mirror;
85 var that = this;
85 var that = this;
86 events.on('kernel_busy.Kernel', function () {
86 events.on('kernel_busy.Kernel', function () {
87 that.skip_kernel_completion = true;
87 that.skip_kernel_completion = true;
88 });
88 });
89 events.on('kernel_idle.Kernel', function () {
89 events.on('kernel_idle.Kernel', function () {
90 that.skip_kernel_completion = false;
90 that.skip_kernel_completion = false;
91 });
91 });
92 };
92 };
93
93
94 Completer.prototype.startCompletion = function () {
94 Completer.prototype.startCompletion = function () {
95 // call for a 'first' completion, that will set the editor and do some
95 /**
96 // special behavior like autopicking if only one completion available.
96 * call for a 'first' completion, that will set the editor and do some
97 * special behavior like autopicking if only one completion available.
98 */
97 if (this.editor.somethingSelected()|| this.editor.getSelections().length > 1) return;
99 if (this.editor.somethingSelected()|| this.editor.getSelections().length > 1) return;
98 this.done = false;
100 this.done = false;
99 // use to get focus back on opera
101 // use to get focus back on opera
100 this.carry_on_completion(true);
102 this.carry_on_completion(true);
101 };
103 };
102
104
103
105
104 // easy access for julia to monkeypatch
106 // easy access for julia to monkeypatch
105 //
107 //
106 Completer.reinvoke_re = /[%0-9a-z._/\\:~-]/i;
108 Completer.reinvoke_re = /[%0-9a-z._/\\:~-]/i;
107
109
108 Completer.prototype.reinvoke= function(pre_cursor, block, cursor){
110 Completer.prototype.reinvoke= function(pre_cursor, block, cursor){
109 return Completer.reinvoke_re.test(pre_cursor);
111 return Completer.reinvoke_re.test(pre_cursor);
110 };
112 };
111
113
112 /**
114 /**
113 *
115 *
114 * pass true as parameter if this is the first invocation of the completer
116 * pass true as parameter if this is the first invocation of the completer
115 * this will prevent the completer to dissmiss itself if it is not on a
117 * this will prevent the completer to dissmiss itself if it is not on a
116 * word boundary like pressing tab after a space, and make it autopick the
118 * word boundary like pressing tab after a space, and make it autopick the
117 * only choice if there is only one which prevent from popping the UI. as
119 * only choice if there is only one which prevent from popping the UI. as
118 * well as fast-forwarding the typing if all completion have a common
120 * well as fast-forwarding the typing if all completion have a common
119 * shared start
121 * shared start
120 **/
122 **/
121 Completer.prototype.carry_on_completion = function (first_invocation) {
123 Completer.prototype.carry_on_completion = function (first_invocation) {
122 // Pass true as parameter if you want the completer to autopick when
124 /**
123 // only one completion. This function is automatically reinvoked at
125 * Pass true as parameter if you want the completer to autopick when
124 // each keystroke with first_invocation = false
126 * only one completion. This function is automatically reinvoked at
127 * each keystroke with first_invocation = false
128 */
125 var cur = this.editor.getCursor();
129 var cur = this.editor.getCursor();
126 var line = this.editor.getLine(cur.line);
130 var line = this.editor.getLine(cur.line);
127 var pre_cursor = this.editor.getRange({
131 var pre_cursor = this.editor.getRange({
128 line: cur.line,
132 line: cur.line,
129 ch: cur.ch - 1
133 ch: cur.ch - 1
130 }, cur);
134 }, cur);
131
135
132 // we need to check that we are still on a word boundary
136 // we need to check that we are still on a word boundary
133 // because while typing the completer is still reinvoking itself
137 // because while typing the completer is still reinvoking itself
134 // so dismiss if we are on a "bad" caracter
138 // so dismiss if we are on a "bad" caracter
135 if (!this.reinvoke(pre_cursor) && !first_invocation) {
139 if (!this.reinvoke(pre_cursor) && !first_invocation) {
136 this.close();
140 this.close();
137 return;
141 return;
138 }
142 }
139
143
140 this.autopick = false;
144 this.autopick = false;
141 if (first_invocation) {
145 if (first_invocation) {
142 this.autopick = true;
146 this.autopick = true;
143 }
147 }
144
148
145 // We want a single cursor position.
149 // We want a single cursor position.
146 if (this.editor.somethingSelected()|| this.editor.getSelections().length > 1) {
150 if (this.editor.somethingSelected()|| this.editor.getSelections().length > 1) {
147 return;
151 return;
148 }
152 }
149
153
150 // one kernel completion came back, finish_completing will be called with the results
154 // one kernel completion came back, finish_completing will be called with the results
151 // we fork here and directly call finish completing if kernel is busy
155 // we fork here and directly call finish completing if kernel is busy
152 var cursor_pos = utils.to_absolute_cursor_pos(this.editor, cur);
156 var cursor_pos = utils.to_absolute_cursor_pos(this.editor, cur);
153 if (this.skip_kernel_completion) {
157 if (this.skip_kernel_completion) {
154 this.finish_completing({ content: {
158 this.finish_completing({ content: {
155 matches: [],
159 matches: [],
156 cursor_start: cursor_pos,
160 cursor_start: cursor_pos,
157 cursor_end: cursor_pos,
161 cursor_end: cursor_pos,
158 }});
162 }});
159 } else {
163 } else {
160 this.cell.kernel.complete(this.editor.getValue(), cursor_pos,
164 this.cell.kernel.complete(this.editor.getValue(), cursor_pos,
161 $.proxy(this.finish_completing, this)
165 $.proxy(this.finish_completing, this)
162 );
166 );
163 }
167 }
164 };
168 };
165
169
166 Completer.prototype.finish_completing = function (msg) {
170 Completer.prototype.finish_completing = function (msg) {
167 // let's build a function that wrap all that stuff into what is needed
171 /**
168 // for the new completer:
172 * let's build a function that wrap all that stuff into what is needed
173 * for the new completer:
174 */
169 var content = msg.content;
175 var content = msg.content;
170 var start = content.cursor_start;
176 var start = content.cursor_start;
171 var end = content.cursor_end;
177 var end = content.cursor_end;
172 var matches = content.matches;
178 var matches = content.matches;
173
179
174 var cur = this.editor.getCursor();
180 var cur = this.editor.getCursor();
175 if (end === null) {
181 if (end === null) {
176 // adapted message spec replies don't have cursor position info,
182 // adapted message spec replies don't have cursor position info,
177 // interpret end=null as current position,
183 // interpret end=null as current position,
178 // and negative start relative to that
184 // and negative start relative to that
179 end = utils.to_absolute_cursor_pos(this.editor, cur);
185 end = utils.to_absolute_cursor_pos(this.editor, cur);
180 if (start < 0) {
186 if (start < 0) {
181 start = end + start;
187 start = end + start;
182 }
188 }
183 }
189 }
184 var results = CodeMirror.contextHint(this.editor);
190 var results = CodeMirror.contextHint(this.editor);
185 var filtered_results = [];
191 var filtered_results = [];
186 //remove results from context completion
192 //remove results from context completion
187 //that are already in kernel completion
193 //that are already in kernel completion
188 var i;
194 var i;
189 for (i=0; i < results.length; i++) {
195 for (i=0; i < results.length; i++) {
190 if (!_existing_completion(results[i].str, matches)) {
196 if (!_existing_completion(results[i].str, matches)) {
191 filtered_results.push(results[i]);
197 filtered_results.push(results[i]);
192 }
198 }
193 }
199 }
194
200
195 // append the introspection result, in order, at at the beginning of
201 // append the introspection result, in order, at at the beginning of
196 // the table and compute the replacement range from current cursor
202 // the table and compute the replacement range from current cursor
197 // positon and matched_text length.
203 // positon and matched_text length.
198 for (i = matches.length - 1; i >= 0; --i) {
204 for (i = matches.length - 1; i >= 0; --i) {
199 filtered_results.unshift({
205 filtered_results.unshift({
200 str: matches[i],
206 str: matches[i],
201 type: "introspection",
207 type: "introspection",
202 from: utils.from_absolute_cursor_pos(this.editor, start),
208 from: utils.from_absolute_cursor_pos(this.editor, start),
203 to: utils.from_absolute_cursor_pos(this.editor, end)
209 to: utils.from_absolute_cursor_pos(this.editor, end)
204 });
210 });
205 }
211 }
206
212
207 // one the 2 sources results have been merge, deal with it
213 // one the 2 sources results have been merge, deal with it
208 this.raw_result = filtered_results;
214 this.raw_result = filtered_results;
209
215
210 // if empty result return
216 // if empty result return
211 if (!this.raw_result || !this.raw_result.length) return;
217 if (!this.raw_result || !this.raw_result.length) return;
212
218
213 // When there is only one completion, use it directly.
219 // When there is only one completion, use it directly.
214 if (this.autopick && this.raw_result.length == 1) {
220 if (this.autopick && this.raw_result.length == 1) {
215 this.insert(this.raw_result[0]);
221 this.insert(this.raw_result[0]);
216 return;
222 return;
217 }
223 }
218
224
219 if (this.raw_result.length == 1) {
225 if (this.raw_result.length == 1) {
220 // test if first and only completion totally matches
226 // test if first and only completion totally matches
221 // what is typed, in this case dismiss
227 // what is typed, in this case dismiss
222 var str = this.raw_result[0].str;
228 var str = this.raw_result[0].str;
223 var pre_cursor = this.editor.getRange({
229 var pre_cursor = this.editor.getRange({
224 line: cur.line,
230 line: cur.line,
225 ch: cur.ch - str.length
231 ch: cur.ch - str.length
226 }, cur);
232 }, cur);
227 if (pre_cursor == str) {
233 if (pre_cursor == str) {
228 this.close();
234 this.close();
229 return;
235 return;
230 }
236 }
231 }
237 }
232
238
233 if (!this.visible) {
239 if (!this.visible) {
234 this.complete = $('<div/>').addClass('completions');
240 this.complete = $('<div/>').addClass('completions');
235 this.complete.attr('id', 'complete');
241 this.complete.attr('id', 'complete');
236
242
237 // Currently webkit doesn't use the size attr correctly. See:
243 // Currently webkit doesn't use the size attr correctly. See:
238 // https://code.google.com/p/chromium/issues/detail?id=4579
244 // https://code.google.com/p/chromium/issues/detail?id=4579
239 this.sel = $('<select/>')
245 this.sel = $('<select/>')
240 .attr('tabindex', -1)
246 .attr('tabindex', -1)
241 .attr('multiple', 'true');
247 .attr('multiple', 'true');
242 this.complete.append(this.sel);
248 this.complete.append(this.sel);
243 this.visible = true;
249 this.visible = true;
244 $('body').append(this.complete);
250 $('body').append(this.complete);
245
251
246 //build the container
252 //build the container
247 var that = this;
253 var that = this;
248 this.sel.dblclick(function () {
254 this.sel.dblclick(function () {
249 that.pick();
255 that.pick();
250 });
256 });
251 this.sel.focus(function () {
257 this.sel.focus(function () {
252 that.editor.focus();
258 that.editor.focus();
253 });
259 });
254 this._handle_keydown = function (cm, event) {
260 this._handle_keydown = function (cm, event) {
255 that.keydown(event);
261 that.keydown(event);
256 };
262 };
257 this.editor.on('keydown', this._handle_keydown);
263 this.editor.on('keydown', this._handle_keydown);
258 this._handle_keypress = function (cm, event) {
264 this._handle_keypress = function (cm, event) {
259 that.keypress(event);
265 that.keypress(event);
260 };
266 };
261 this.editor.on('keypress', this._handle_keypress);
267 this.editor.on('keypress', this._handle_keypress);
262 }
268 }
263 this.sel.attr('size', Math.min(10, this.raw_result.length));
269 this.sel.attr('size', Math.min(10, this.raw_result.length));
264
270
265 // After everything is on the page, compute the postion.
271 // After everything is on the page, compute the postion.
266 // We put it above the code if it is too close to the bottom of the page.
272 // We put it above the code if it is too close to the bottom of the page.
267 var pos = this.editor.cursorCoords(
273 var pos = this.editor.cursorCoords(
268 utils.from_absolute_cursor_pos(this.editor, start)
274 utils.from_absolute_cursor_pos(this.editor, start)
269 );
275 );
270 var left = pos.left-3;
276 var left = pos.left-3;
271 var top;
277 var top;
272 var cheight = this.complete.height();
278 var cheight = this.complete.height();
273 var wheight = $(window).height();
279 var wheight = $(window).height();
274 if (pos.bottom+cheight+5 > wheight) {
280 if (pos.bottom+cheight+5 > wheight) {
275 top = pos.top-cheight-4;
281 top = pos.top-cheight-4;
276 } else {
282 } else {
277 top = pos.bottom+1;
283 top = pos.bottom+1;
278 }
284 }
279 this.complete.css('left', left + 'px');
285 this.complete.css('left', left + 'px');
280 this.complete.css('top', top + 'px');
286 this.complete.css('top', top + 'px');
281
287
282 // Clear and fill the list.
288 // Clear and fill the list.
283 this.sel.text('');
289 this.sel.text('');
284 this.build_gui_list(this.raw_result);
290 this.build_gui_list(this.raw_result);
285 return true;
291 return true;
286 };
292 };
287
293
288 Completer.prototype.insert = function (completion) {
294 Completer.prototype.insert = function (completion) {
289 this.editor.replaceRange(completion.str, completion.from, completion.to);
295 this.editor.replaceRange(completion.str, completion.from, completion.to);
290 };
296 };
291
297
292 Completer.prototype.build_gui_list = function (completions) {
298 Completer.prototype.build_gui_list = function (completions) {
293 for (var i = 0; i < completions.length; ++i) {
299 for (var i = 0; i < completions.length; ++i) {
294 var opt = $('<option/>').text(completions[i].str).addClass(completions[i].type);
300 var opt = $('<option/>').text(completions[i].str).addClass(completions[i].type);
295 this.sel.append(opt);
301 this.sel.append(opt);
296 }
302 }
297 this.sel.children().first().attr('selected', 'true');
303 this.sel.children().first().attr('selected', 'true');
298 this.sel.scrollTop(0);
304 this.sel.scrollTop(0);
299 };
305 };
300
306
301 Completer.prototype.close = function () {
307 Completer.prototype.close = function () {
302 this.done = true;
308 this.done = true;
303 $('#complete').remove();
309 $('#complete').remove();
304 this.editor.off('keydown', this._handle_keydown);
310 this.editor.off('keydown', this._handle_keydown);
305 this.editor.off('keypress', this._handle_keypress);
311 this.editor.off('keypress', this._handle_keypress);
306 this.visible = false;
312 this.visible = false;
307 };
313 };
308
314
309 Completer.prototype.pick = function () {
315 Completer.prototype.pick = function () {
310 this.insert(this.raw_result[this.sel[0].selectedIndex]);
316 this.insert(this.raw_result[this.sel[0].selectedIndex]);
311 this.close();
317 this.close();
312 };
318 };
313
319
314 Completer.prototype.keydown = function (event) {
320 Completer.prototype.keydown = function (event) {
315 var code = event.keyCode;
321 var code = event.keyCode;
316 var that = this;
322 var that = this;
317
323
318 // Enter
324 // Enter
319 if (code == keycodes.enter) {
325 if (code == keycodes.enter) {
320 event.codemirrorIgnore = true;
326 event.codemirrorIgnore = true;
321 event._ipkmIgnore = true;
327 event._ipkmIgnore = true;
322 event.preventDefault();
328 event.preventDefault();
323 this.pick();
329 this.pick();
324 // Escape or backspace
330 // Escape or backspace
325 } else if (code == keycodes.esc || code == keycodes.backspace) {
331 } else if (code == keycodes.esc || code == keycodes.backspace) {
326 event.codemirrorIgnore = true;
332 event.codemirrorIgnore = true;
327 event._ipkmIgnore = true;
333 event._ipkmIgnore = true;
328 event.preventDefault();
334 event.preventDefault();
329 this.close();
335 this.close();
330 } else if (code == keycodes.tab) {
336 } else if (code == keycodes.tab) {
331 //all the fastforwarding operation,
337 //all the fastforwarding operation,
332 //Check that shared start is not null which can append with prefixed completion
338 //Check that shared start is not null which can append with prefixed completion
333 // like %pylab , pylab have no shred start, and ff will result in py<tab><tab>
339 // like %pylab , pylab have no shred start, and ff will result in py<tab><tab>
334 // to erase py
340 // to erase py
335 var sh = shared_start(this.raw_result, true);
341 var sh = shared_start(this.raw_result, true);
336 if (sh) {
342 if (sh) {
337 this.insert(sh);
343 this.insert(sh);
338 }
344 }
339 this.close();
345 this.close();
340 //reinvoke self
346 //reinvoke self
341 setTimeout(function () {
347 setTimeout(function () {
342 that.carry_on_completion();
348 that.carry_on_completion();
343 }, 50);
349 }, 50);
344 } else if (code == keycodes.up || code == keycodes.down) {
350 } else if (code == keycodes.up || code == keycodes.down) {
345 // need to do that to be able to move the arrow
351 // need to do that to be able to move the arrow
346 // when on the first or last line ofo a code cell
352 // when on the first or last line ofo a code cell
347 event.codemirrorIgnore = true;
353 event.codemirrorIgnore = true;
348 event._ipkmIgnore = true;
354 event._ipkmIgnore = true;
349 event.preventDefault();
355 event.preventDefault();
350
356
351 var options = this.sel.find('option');
357 var options = this.sel.find('option');
352 var index = this.sel[0].selectedIndex;
358 var index = this.sel[0].selectedIndex;
353 if (code == keycodes.up) {
359 if (code == keycodes.up) {
354 index--;
360 index--;
355 }
361 }
356 if (code == keycodes.down) {
362 if (code == keycodes.down) {
357 index++;
363 index++;
358 }
364 }
359 index = Math.min(Math.max(index, 0), options.length-1);
365 index = Math.min(Math.max(index, 0), options.length-1);
360 this.sel[0].selectedIndex = index;
366 this.sel[0].selectedIndex = index;
361 } else if (code == keycodes.pageup || code == keycodes.pagedown) {
367 } else if (code == keycodes.pageup || code == keycodes.pagedown) {
362 event._ipkmIgnore = true;
368 event._ipkmIgnore = true;
363
369
364 var options = this.sel.find('option');
370 var options = this.sel.find('option');
365 var index = this.sel[0].selectedIndex;
371 var index = this.sel[0].selectedIndex;
366 if (code == keycodes.pageup) {
372 if (code == keycodes.pageup) {
367 index -= 10; // As 10 is the hard coded size of the drop down menu
373 index -= 10; // As 10 is the hard coded size of the drop down menu
368 } else {
374 } else {
369 index += 10;
375 index += 10;
370 }
376 }
371 index = Math.min(Math.max(index, 0), options.length-1);
377 index = Math.min(Math.max(index, 0), options.length-1);
372 this.sel[0].selectedIndex = index;
378 this.sel[0].selectedIndex = index;
373 } else if (code == keycodes.left || code == keycodes.right) {
379 } else if (code == keycodes.left || code == keycodes.right) {
374 this.close();
380 this.close();
375 }
381 }
376 };
382 };
377
383
378 Completer.prototype.keypress = function (event) {
384 Completer.prototype.keypress = function (event) {
379 // FIXME: This is a band-aid.
385 /**
380 // on keypress, trigger insertion of a single character.
386 * FIXME: This is a band-aid.
381 // This simulates the old behavior of completion as you type,
387 * on keypress, trigger insertion of a single character.
382 // before events were disconnected and CodeMirror stopped
388 * This simulates the old behavior of completion as you type,
383 // receiving events while the completer is focused.
389 * before events were disconnected and CodeMirror stopped
390 * receiving events while the completer is focused.
391 */
384
392
385 var that = this;
393 var that = this;
386 var code = event.keyCode;
394 var code = event.keyCode;
387
395
388 // don't handle keypress if it's not a character (arrows on FF)
396 // don't handle keypress if it's not a character (arrows on FF)
389 // or ENTER/TAB
397 // or ENTER/TAB
390 if (event.charCode === 0 ||
398 if (event.charCode === 0 ||
391 code == keycodes.tab ||
399 code == keycodes.tab ||
392 code == keycodes.enter
400 code == keycodes.enter
393 ) return;
401 ) return;
394
402
395 this.close();
403 this.close();
396 this.editor.focus();
404 this.editor.focus();
397 setTimeout(function () {
405 setTimeout(function () {
398 that.carry_on_completion();
406 that.carry_on_completion();
399 }, 50);
407 }, 50);
400 };
408 };
401
409
402 // For backwards compatability.
410 // For backwards compatability.
403 IPython.Completer = Completer;
411 IPython.Completer = Completer;
404
412
405 return {'Completer': Completer};
413 return {'Completer': Completer};
406 });
414 });
@@ -1,220 +1,224 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, options) {
12 var MainToolBar = function (selector, options) {
13 // Constructor
13 /**
14 //
14 * Constructor
15 // Parameters:
15 *
16 // selector: string
16 * Parameters:
17 // options: dictionary
17 * selector: string
18 // Dictionary of keyword arguments.
18 * options: dictionary
19 // events: $(Events) instance
19 * Dictionary of keyword arguments.
20 // notebook: Notebook instance
20 * events: $(Events) instance
21 * notebook: Notebook instance
22 */
21 toolbar.ToolBar.apply(this, arguments);
23 toolbar.ToolBar.apply(this, arguments);
22 this.events = options.events;
24 this.events = options.events;
23 this.notebook = options.notebook;
25 this.notebook = options.notebook;
24 this.construct();
26 this.construct();
25 this.add_celltype_list();
27 this.add_celltype_list();
26 this.add_celltoolbar_list();
28 this.add_celltoolbar_list();
27 this.bind_events();
29 this.bind_events();
28 };
30 };
29
31
30 MainToolBar.prototype = Object.create(toolbar.ToolBar.prototype);
32 MainToolBar.prototype = Object.create(toolbar.ToolBar.prototype);
31
33
32 MainToolBar.prototype.construct = function () {
34 MainToolBar.prototype.construct = function () {
33 var that = this;
35 var that = this;
34 this.add_buttons_group([
36 this.add_buttons_group([
35 {
37 {
36 id : 'save_b',
38 id : 'save_b',
37 label : 'Save and Checkpoint',
39 label : 'Save and Checkpoint',
38 icon : 'fa-save',
40 icon : 'fa-save',
39 callback : function () {
41 callback : function () {
40 that.notebook.save_checkpoint();
42 that.notebook.save_checkpoint();
41 }
43 }
42 }
44 }
43 ]);
45 ]);
44
46
45 this.add_buttons_group([
47 this.add_buttons_group([
46 {
48 {
47 id : 'insert_below_b',
49 id : 'insert_below_b',
48 label : 'Insert Cell Below',
50 label : 'Insert Cell Below',
49 icon : 'fa-plus',
51 icon : 'fa-plus',
50 callback : function () {
52 callback : function () {
51 that.notebook.insert_cell_below('code');
53 that.notebook.insert_cell_below('code');
52 that.notebook.select_next();
54 that.notebook.select_next();
53 that.notebook.focus_cell();
55 that.notebook.focus_cell();
54 }
56 }
55 }
57 }
56 ],'insert_above_below');
58 ],'insert_above_below');
57
59
58 this.add_buttons_group([
60 this.add_buttons_group([
59 {
61 {
60 id : 'cut_b',
62 id : 'cut_b',
61 label : 'Cut Cell',
63 label : 'Cut Cell',
62 icon : 'fa-cut',
64 icon : 'fa-cut',
63 callback : function () {
65 callback : function () {
64 that.notebook.cut_cell();
66 that.notebook.cut_cell();
65 }
67 }
66 },
68 },
67 {
69 {
68 id : 'copy_b',
70 id : 'copy_b',
69 label : 'Copy Cell',
71 label : 'Copy Cell',
70 icon : 'fa-copy',
72 icon : 'fa-copy',
71 callback : function () {
73 callback : function () {
72 that.notebook.copy_cell();
74 that.notebook.copy_cell();
73 }
75 }
74 },
76 },
75 {
77 {
76 id : 'paste_b',
78 id : 'paste_b',
77 label : 'Paste Cell Below',
79 label : 'Paste Cell Below',
78 icon : 'fa-paste',
80 icon : 'fa-paste',
79 callback : function () {
81 callback : function () {
80 that.notebook.paste_cell_below();
82 that.notebook.paste_cell_below();
81 }
83 }
82 }
84 }
83 ],'cut_copy_paste');
85 ],'cut_copy_paste');
84
86
85 this.add_buttons_group([
87 this.add_buttons_group([
86 {
88 {
87 id : 'move_up_b',
89 id : 'move_up_b',
88 label : 'Move Cell Up',
90 label : 'Move Cell Up',
89 icon : 'fa-arrow-up',
91 icon : 'fa-arrow-up',
90 callback : function () {
92 callback : function () {
91 that.notebook.move_cell_up();
93 that.notebook.move_cell_up();
92 }
94 }
93 },
95 },
94 {
96 {
95 id : 'move_down_b',
97 id : 'move_down_b',
96 label : 'Move Cell Down',
98 label : 'Move Cell Down',
97 icon : 'fa-arrow-down',
99 icon : 'fa-arrow-down',
98 callback : function () {
100 callback : function () {
99 that.notebook.move_cell_down();
101 that.notebook.move_cell_down();
100 }
102 }
101 }
103 }
102 ],'move_up_down');
104 ],'move_up_down');
103
105
104
106
105 this.add_buttons_group([
107 this.add_buttons_group([
106 {
108 {
107 id : 'run_b',
109 id : 'run_b',
108 label : 'Run Cell',
110 label : 'Run Cell',
109 icon : 'fa-play',
111 icon : 'fa-play',
110 callback : function () {
112 callback : function () {
111 // emulate default shift-enter behavior
113 /**
114 * emulate default shift-enter behavior
115 */
112 that.notebook.execute_cell_and_select_below();
116 that.notebook.execute_cell_and_select_below();
113 }
117 }
114 },
118 },
115 {
119 {
116 id : 'interrupt_b',
120 id : 'interrupt_b',
117 label : 'Interrupt',
121 label : 'Interrupt',
118 icon : 'fa-stop',
122 icon : 'fa-stop',
119 callback : function () {
123 callback : function () {
120 that.notebook.kernel.interrupt();
124 that.notebook.kernel.interrupt();
121 }
125 }
122 },
126 },
123 {
127 {
124 id : 'repeat_b',
128 id : 'repeat_b',
125 label : 'Restart Kernel',
129 label : 'Restart Kernel',
126 icon : 'fa-repeat',
130 icon : 'fa-repeat',
127 callback : function () {
131 callback : function () {
128 that.notebook.restart_kernel();
132 that.notebook.restart_kernel();
129 }
133 }
130 }
134 }
131 ],'run_int');
135 ],'run_int');
132 };
136 };
133
137
134 MainToolBar.prototype.add_celltype_list = function () {
138 MainToolBar.prototype.add_celltype_list = function () {
135 this.element
139 this.element
136 .append($('<select/>')
140 .append($('<select/>')
137 .attr('id','cell_type')
141 .attr('id','cell_type')
138 .addClass('form-control select-xs')
142 .addClass('form-control select-xs')
139 .append($('<option/>').attr('value','code').text('Code'))
143 .append($('<option/>').attr('value','code').text('Code'))
140 .append($('<option/>').attr('value','markdown').text('Markdown'))
144 .append($('<option/>').attr('value','markdown').text('Markdown'))
141 .append($('<option/>').attr('value','raw').text('Raw NBConvert'))
145 .append($('<option/>').attr('value','raw').text('Raw NBConvert'))
142 .append($('<option/>').attr('value','heading').text('Heading'))
146 .append($('<option/>').attr('value','heading').text('Heading'))
143 );
147 );
144 };
148 };
145
149
146 MainToolBar.prototype.add_celltoolbar_list = function () {
150 MainToolBar.prototype.add_celltoolbar_list = function () {
147 var label = $('<span/>').addClass("navbar-text").text('Cell Toolbar:');
151 var label = $('<span/>').addClass("navbar-text").text('Cell Toolbar:');
148 var select = $('<select/>')
152 var select = $('<select/>')
149 .attr('id', 'ctb_select')
153 .attr('id', 'ctb_select')
150 .addClass('form-control select-xs')
154 .addClass('form-control select-xs')
151 .append($('<option/>').attr('value', '').text('None'));
155 .append($('<option/>').attr('value', '').text('None'));
152 this.element.append(label).append(select);
156 this.element.append(label).append(select);
153 var that = this;
157 var that = this;
154 select.change(function() {
158 select.change(function() {
155 var val = $(this).val();
159 var val = $(this).val();
156 if (val ==='') {
160 if (val ==='') {
157 celltoolbar.CellToolbar.global_hide();
161 celltoolbar.CellToolbar.global_hide();
158 delete that.notebook.metadata.celltoolbar;
162 delete that.notebook.metadata.celltoolbar;
159 } else {
163 } else {
160 celltoolbar.CellToolbar.global_show();
164 celltoolbar.CellToolbar.global_show();
161 celltoolbar.CellToolbar.activate_preset(val, that.events);
165 celltoolbar.CellToolbar.activate_preset(val, that.events);
162 that.notebook.metadata.celltoolbar = val;
166 that.notebook.metadata.celltoolbar = val;
163 }
167 }
164 });
168 });
165 // Setup the currently registered presets.
169 // Setup the currently registered presets.
166 var presets = celltoolbar.CellToolbar.list_presets();
170 var presets = celltoolbar.CellToolbar.list_presets();
167 for (var i=0; i<presets.length; i++) {
171 for (var i=0; i<presets.length; i++) {
168 var name = presets[i];
172 var name = presets[i];
169 select.append($('<option/>').attr('value', name).text(name));
173 select.append($('<option/>').attr('value', name).text(name));
170 }
174 }
171 // Setup future preset registrations.
175 // Setup future preset registrations.
172 this.events.on('preset_added.CellToolbar', function (event, data) {
176 this.events.on('preset_added.CellToolbar', function (event, data) {
173 var name = data.name;
177 var name = data.name;
174 select.append($('<option/>').attr('value', name).text(name));
178 select.append($('<option/>').attr('value', name).text(name));
175 });
179 });
176 // Update select value when a preset is activated.
180 // Update select value when a preset is activated.
177 this.events.on('preset_activated.CellToolbar', function (event, data) {
181 this.events.on('preset_activated.CellToolbar', function (event, data) {
178 if (select.val() !== data.name)
182 if (select.val() !== data.name)
179 select.val(data.name);
183 select.val(data.name);
180 });
184 });
181 };
185 };
182
186
183 MainToolBar.prototype.bind_events = function () {
187 MainToolBar.prototype.bind_events = function () {
184 var that = this;
188 var that = this;
185
189
186 this.element.find('#cell_type').change(function () {
190 this.element.find('#cell_type').change(function () {
187 var cell_type = $(this).val();
191 var cell_type = $(this).val();
188 switch (cell_type) {
192 switch (cell_type) {
189 case 'code':
193 case 'code':
190 that.notebook.to_code();
194 that.notebook.to_code();
191 break;
195 break;
192 case 'markdown':
196 case 'markdown':
193 that.notebook.to_markdown();
197 that.notebook.to_markdown();
194 break;
198 break;
195 case 'raw':
199 case 'raw':
196 that.notebook.to_raw();
200 that.notebook.to_raw();
197 break;
201 break;
198 case 'heading':
202 case 'heading':
199 that.notebook._warn_heading();
203 that.notebook._warn_heading();
200 that.notebook.to_heading();
204 that.notebook.to_heading();
201 that.element.find('#cell_type').val("markdown");
205 that.element.find('#cell_type').val("markdown");
202 break;
206 break;
203 default:
207 default:
204 console.log("unrecognized cell type:", cell_type);
208 console.log("unrecognized cell type:", cell_type);
205 }
209 }
206 });
210 });
207 this.events.on('selected_cell_type_changed.Notebook', function (event, data) {
211 this.events.on('selected_cell_type_changed.Notebook', function (event, data) {
208 if (data.cell_type === 'heading') {
212 if (data.cell_type === 'heading') {
209 that.element.find('#cell_type').val(data.cell_type+data.level);
213 that.element.find('#cell_type').val(data.cell_type+data.level);
210 } else {
214 } else {
211 that.element.find('#cell_type').val(data.cell_type);
215 that.element.find('#cell_type').val(data.cell_type);
212 }
216 }
213 });
217 });
214 };
218 };
215
219
216 // Backwards compatability.
220 // Backwards compatability.
217 IPython.MainToolBar = MainToolBar;
221 IPython.MainToolBar = MainToolBar;
218
222
219 return {'MainToolBar': MainToolBar};
223 return {'MainToolBar': MainToolBar};
220 });
224 });
@@ -1,383 +1,391 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 'jquery',
5 'jquery',
6 'base/js/namespace',
6 'base/js/namespace',
7 'base/js/dialog',
7 'base/js/dialog',
8 'base/js/utils',
8 'base/js/utils',
9 'notebook/js/tour',
9 'notebook/js/tour',
10 'bootstrap',
10 'bootstrap',
11 'moment',
11 'moment',
12 ], function($, IPython, dialog, utils, tour, bootstrap, moment) {
12 ], function($, IPython, dialog, utils, tour, bootstrap, moment) {
13 "use strict";
13 "use strict";
14
14
15 var MenuBar = function (selector, options) {
15 var MenuBar = function (selector, options) {
16 // Constructor
16 /**
17 //
17 * Constructor
18 // A MenuBar Class to generate the menubar of IPython notebook
18 *
19 //
19 * A MenuBar Class to generate the menubar of IPython notebook
20 // Parameters:
20 *
21 // selector: string
21 * Parameters:
22 // options: dictionary
22 * selector: string
23 // Dictionary of keyword arguments.
23 * options: dictionary
24 // notebook: Notebook instance
24 * Dictionary of keyword arguments.
25 // contents: ContentManager instance
25 * notebook: Notebook instance
26 // layout_manager: LayoutManager instance
26 * contents: ContentManager instance
27 // events: $(Events) instance
27 * layout_manager: LayoutManager instance
28 // save_widget: SaveWidget instance
28 * events: $(Events) instance
29 // quick_help: QuickHelp instance
29 * save_widget: SaveWidget instance
30 // base_url : string
30 * quick_help: QuickHelp instance
31 // notebook_path : string
31 * base_url : string
32 // notebook_name : string
32 * notebook_path : string
33 * notebook_name : string
34 */
33 options = options || {};
35 options = options || {};
34 this.base_url = options.base_url || utils.get_body_data("baseUrl");
36 this.base_url = options.base_url || utils.get_body_data("baseUrl");
35 this.selector = selector;
37 this.selector = selector;
36 this.notebook = options.notebook;
38 this.notebook = options.notebook;
37 this.contents = options.contents;
39 this.contents = options.contents;
38 this.layout_manager = options.layout_manager;
40 this.layout_manager = options.layout_manager;
39 this.events = options.events;
41 this.events = options.events;
40 this.save_widget = options.save_widget;
42 this.save_widget = options.save_widget;
41 this.quick_help = options.quick_help;
43 this.quick_help = options.quick_help;
42
44
43 try {
45 try {
44 this.tour = new tour.Tour(this.notebook, this.events);
46 this.tour = new tour.Tour(this.notebook, this.events);
45 } catch (e) {
47 } catch (e) {
46 this.tour = undefined;
48 this.tour = undefined;
47 console.log("Failed to instantiate Notebook Tour", e);
49 console.log("Failed to instantiate Notebook Tour", e);
48 }
50 }
49
51
50 if (this.selector !== undefined) {
52 if (this.selector !== undefined) {
51 this.element = $(selector);
53 this.element = $(selector);
52 this.style();
54 this.style();
53 this.bind_events();
55 this.bind_events();
54 }
56 }
55 };
57 };
56
58
57 // TODO: This has definitively nothing to do with style ...
59 // TODO: This has definitively nothing to do with style ...
58 MenuBar.prototype.style = function () {
60 MenuBar.prototype.style = function () {
59 var that = this;
61 var that = this;
60 this.element.find("li").click(function (event, ui) {
62 this.element.find("li").click(function (event, ui) {
61 // The selected cell loses focus when the menu is entered, so we
63 // The selected cell loses focus when the menu is entered, so we
62 // re-select it upon selection.
64 // re-select it upon selection.
63 var i = that.notebook.get_selected_index();
65 var i = that.notebook.get_selected_index();
64 that.notebook.select(i);
66 that.notebook.select(i);
65 }
67 }
66 );
68 );
67 };
69 };
68
70
69 MenuBar.prototype._nbconvert = function (format, download) {
71 MenuBar.prototype._nbconvert = function (format, download) {
70 download = download || false;
72 download = download || false;
71 var notebook_path = this.notebook.notebook_path;
73 var notebook_path = this.notebook.notebook_path;
72 var url = utils.url_join_encode(
74 var url = utils.url_join_encode(
73 this.base_url,
75 this.base_url,
74 'nbconvert',
76 'nbconvert',
75 format,
77 format,
76 notebook_path
78 notebook_path
77 ) + "?download=" + download.toString();
79 ) + "?download=" + download.toString();
78
80
79 var w = window.open()
81 var w = window.open()
80 if (this.notebook.dirty) {
82 if (this.notebook.dirty) {
81 this.notebook.save_notebook().then(function() {
83 this.notebook.save_notebook().then(function() {
82 w.location = url;
84 w.location = url;
83 });
85 });
84 } else {
86 } else {
85 w.location = url;
87 w.location = url;
86 }
88 }
87 };
89 };
88
90
89 MenuBar.prototype.bind_events = function () {
91 MenuBar.prototype.bind_events = function () {
90 // File
92 /**
93 * File
94 */
91 var that = this;
95 var that = this;
92 this.element.find('#new_notebook').click(function () {
96 this.element.find('#new_notebook').click(function () {
93 var w = window.open();
97 var w = window.open();
94 // Create a new notebook in the same path as the current
98 // Create a new notebook in the same path as the current
95 // notebook's path.
99 // notebook's path.
96 var parent = utils.url_path_split(that.notebook.notebook_path)[0];
100 var parent = utils.url_path_split(that.notebook.notebook_path)[0];
97 that.contents.new_untitled(parent, {type: "notebook"}).then(
101 that.contents.new_untitled(parent, {type: "notebook"}).then(
98 function (data) {
102 function (data) {
99 w.location = utils.url_join_encode(
103 w.location = utils.url_join_encode(
100 that.base_url, 'notebooks', data.path
104 that.base_url, 'notebooks', data.path
101 );
105 );
102 },
106 },
103 function(error) {
107 function(error) {
104 w.close();
108 w.close();
105 dialog.modal({
109 dialog.modal({
106 title : 'Creating Notebook Failed',
110 title : 'Creating Notebook Failed',
107 body : "The error was: " + error.message,
111 body : "The error was: " + error.message,
108 buttons : {'OK' : {'class' : 'btn-primary'}}
112 buttons : {'OK' : {'class' : 'btn-primary'}}
109 });
113 });
110 }
114 }
111 );
115 );
112 });
116 });
113 this.element.find('#open_notebook').click(function () {
117 this.element.find('#open_notebook').click(function () {
114 var parent = utils.url_path_split(that.notebook.notebook_path)[0];
118 var parent = utils.url_path_split(that.notebook.notebook_path)[0];
115 window.open(utils.url_join_encode(that.base_url, 'tree', parent));
119 window.open(utils.url_join_encode(that.base_url, 'tree', parent));
116 });
120 });
117 this.element.find('#copy_notebook').click(function () {
121 this.element.find('#copy_notebook').click(function () {
118 that.notebook.copy_notebook();
122 that.notebook.copy_notebook();
119 return false;
123 return false;
120 });
124 });
121 this.element.find('#download_ipynb').click(function () {
125 this.element.find('#download_ipynb').click(function () {
122 var base_url = that.notebook.base_url;
126 var base_url = that.notebook.base_url;
123 var notebook_path = that.notebook.notebook_path;
127 var notebook_path = that.notebook.notebook_path;
124 if (that.notebook.dirty) {
128 if (that.notebook.dirty) {
125 that.notebook.save_notebook({async : false});
129 that.notebook.save_notebook({async : false});
126 }
130 }
127
131
128 var url = utils.url_join_encode(base_url, 'files', notebook_path);
132 var url = utils.url_join_encode(base_url, 'files', notebook_path);
129 window.open(url + '?download=1');
133 window.open(url + '?download=1');
130 });
134 });
131
135
132 this.element.find('#print_preview').click(function () {
136 this.element.find('#print_preview').click(function () {
133 that._nbconvert('html', false);
137 that._nbconvert('html', false);
134 });
138 });
135
139
136 this.element.find('#download_html').click(function () {
140 this.element.find('#download_html').click(function () {
137 that._nbconvert('html', true);
141 that._nbconvert('html', true);
138 });
142 });
139
143
140 this.element.find('#download_rst').click(function () {
144 this.element.find('#download_rst').click(function () {
141 that._nbconvert('rst', true);
145 that._nbconvert('rst', true);
142 });
146 });
143
147
144 this.element.find('#download_pdf').click(function () {
148 this.element.find('#download_pdf').click(function () {
145 that._nbconvert('pdf', true);
149 that._nbconvert('pdf', true);
146 });
150 });
147
151
148 this.element.find('#rename_notebook').click(function () {
152 this.element.find('#rename_notebook').click(function () {
149 that.save_widget.rename_notebook({notebook: that.notebook});
153 that.save_widget.rename_notebook({notebook: that.notebook});
150 });
154 });
151 this.element.find('#save_checkpoint').click(function () {
155 this.element.find('#save_checkpoint').click(function () {
152 that.notebook.save_checkpoint();
156 that.notebook.save_checkpoint();
153 });
157 });
154 this.element.find('#restore_checkpoint').click(function () {
158 this.element.find('#restore_checkpoint').click(function () {
155 });
159 });
156 this.element.find('#trust_notebook').click(function () {
160 this.element.find('#trust_notebook').click(function () {
157 that.notebook.trust_notebook();
161 that.notebook.trust_notebook();
158 });
162 });
159 this.events.on('trust_changed.Notebook', function (event, trusted) {
163 this.events.on('trust_changed.Notebook', function (event, trusted) {
160 if (trusted) {
164 if (trusted) {
161 that.element.find('#trust_notebook')
165 that.element.find('#trust_notebook')
162 .addClass("disabled")
166 .addClass("disabled")
163 .find("a").text("Trusted Notebook");
167 .find("a").text("Trusted Notebook");
164 } else {
168 } else {
165 that.element.find('#trust_notebook')
169 that.element.find('#trust_notebook')
166 .removeClass("disabled")
170 .removeClass("disabled")
167 .find("a").text("Trust Notebook");
171 .find("a").text("Trust Notebook");
168 }
172 }
169 });
173 });
170 this.element.find('#kill_and_exit').click(function () {
174 this.element.find('#kill_and_exit').click(function () {
171 var close_window = function () {
175 var close_window = function () {
172 // allow closing of new tabs in Chromium, impossible in FF
176 /**
177 * allow closing of new tabs in Chromium, impossible in FF
178 */
173 window.open('', '_self', '');
179 window.open('', '_self', '');
174 window.close();
180 window.close();
175 };
181 };
176 // finish with close on success or failure
182 // finish with close on success or failure
177 that.notebook.session.delete(close_window, close_window);
183 that.notebook.session.delete(close_window, close_window);
178 });
184 });
179 // Edit
185 // Edit
180 this.element.find('#cut_cell').click(function () {
186 this.element.find('#cut_cell').click(function () {
181 that.notebook.cut_cell();
187 that.notebook.cut_cell();
182 });
188 });
183 this.element.find('#copy_cell').click(function () {
189 this.element.find('#copy_cell').click(function () {
184 that.notebook.copy_cell();
190 that.notebook.copy_cell();
185 });
191 });
186 this.element.find('#delete_cell').click(function () {
192 this.element.find('#delete_cell').click(function () {
187 that.notebook.delete_cell();
193 that.notebook.delete_cell();
188 });
194 });
189 this.element.find('#undelete_cell').click(function () {
195 this.element.find('#undelete_cell').click(function () {
190 that.notebook.undelete_cell();
196 that.notebook.undelete_cell();
191 });
197 });
192 this.element.find('#split_cell').click(function () {
198 this.element.find('#split_cell').click(function () {
193 that.notebook.split_cell();
199 that.notebook.split_cell();
194 });
200 });
195 this.element.find('#merge_cell_above').click(function () {
201 this.element.find('#merge_cell_above').click(function () {
196 that.notebook.merge_cell_above();
202 that.notebook.merge_cell_above();
197 });
203 });
198 this.element.find('#merge_cell_below').click(function () {
204 this.element.find('#merge_cell_below').click(function () {
199 that.notebook.merge_cell_below();
205 that.notebook.merge_cell_below();
200 });
206 });
201 this.element.find('#move_cell_up').click(function () {
207 this.element.find('#move_cell_up').click(function () {
202 that.notebook.move_cell_up();
208 that.notebook.move_cell_up();
203 });
209 });
204 this.element.find('#move_cell_down').click(function () {
210 this.element.find('#move_cell_down').click(function () {
205 that.notebook.move_cell_down();
211 that.notebook.move_cell_down();
206 });
212 });
207 this.element.find('#edit_nb_metadata').click(function () {
213 this.element.find('#edit_nb_metadata').click(function () {
208 that.notebook.edit_metadata({
214 that.notebook.edit_metadata({
209 notebook: that.notebook,
215 notebook: that.notebook,
210 keyboard_manager: that.notebook.keyboard_manager});
216 keyboard_manager: that.notebook.keyboard_manager});
211 });
217 });
212
218
213 // View
219 // View
214 this.element.find('#toggle_header').click(function () {
220 this.element.find('#toggle_header').click(function () {
215 $('div#header').toggle();
221 $('div#header').toggle();
216 that.layout_manager.do_resize();
222 that.layout_manager.do_resize();
217 });
223 });
218 this.element.find('#toggle_toolbar').click(function () {
224 this.element.find('#toggle_toolbar').click(function () {
219 $('div#maintoolbar').toggle();
225 $('div#maintoolbar').toggle();
220 that.layout_manager.do_resize();
226 that.layout_manager.do_resize();
221 });
227 });
222 // Insert
228 // Insert
223 this.element.find('#insert_cell_above').click(function () {
229 this.element.find('#insert_cell_above').click(function () {
224 that.notebook.insert_cell_above('code');
230 that.notebook.insert_cell_above('code');
225 that.notebook.select_prev();
231 that.notebook.select_prev();
226 });
232 });
227 this.element.find('#insert_cell_below').click(function () {
233 this.element.find('#insert_cell_below').click(function () {
228 that.notebook.insert_cell_below('code');
234 that.notebook.insert_cell_below('code');
229 that.notebook.select_next();
235 that.notebook.select_next();
230 });
236 });
231 // Cell
237 // Cell
232 this.element.find('#run_cell').click(function () {
238 this.element.find('#run_cell').click(function () {
233 that.notebook.execute_cell();
239 that.notebook.execute_cell();
234 });
240 });
235 this.element.find('#run_cell_select_below').click(function () {
241 this.element.find('#run_cell_select_below').click(function () {
236 that.notebook.execute_cell_and_select_below();
242 that.notebook.execute_cell_and_select_below();
237 });
243 });
238 this.element.find('#run_cell_insert_below').click(function () {
244 this.element.find('#run_cell_insert_below').click(function () {
239 that.notebook.execute_cell_and_insert_below();
245 that.notebook.execute_cell_and_insert_below();
240 });
246 });
241 this.element.find('#run_all_cells').click(function () {
247 this.element.find('#run_all_cells').click(function () {
242 that.notebook.execute_all_cells();
248 that.notebook.execute_all_cells();
243 });
249 });
244 this.element.find('#run_all_cells_above').click(function () {
250 this.element.find('#run_all_cells_above').click(function () {
245 that.notebook.execute_cells_above();
251 that.notebook.execute_cells_above();
246 });
252 });
247 this.element.find('#run_all_cells_below').click(function () {
253 this.element.find('#run_all_cells_below').click(function () {
248 that.notebook.execute_cells_below();
254 that.notebook.execute_cells_below();
249 });
255 });
250 this.element.find('#to_code').click(function () {
256 this.element.find('#to_code').click(function () {
251 that.notebook.to_code();
257 that.notebook.to_code();
252 });
258 });
253 this.element.find('#to_markdown').click(function () {
259 this.element.find('#to_markdown').click(function () {
254 that.notebook.to_markdown();
260 that.notebook.to_markdown();
255 });
261 });
256 this.element.find('#to_raw').click(function () {
262 this.element.find('#to_raw').click(function () {
257 that.notebook.to_raw();
263 that.notebook.to_raw();
258 });
264 });
259
265
260 this.element.find('#toggle_current_output').click(function () {
266 this.element.find('#toggle_current_output').click(function () {
261 that.notebook.toggle_output();
267 that.notebook.toggle_output();
262 });
268 });
263 this.element.find('#toggle_current_output_scroll').click(function () {
269 this.element.find('#toggle_current_output_scroll').click(function () {
264 that.notebook.toggle_output_scroll();
270 that.notebook.toggle_output_scroll();
265 });
271 });
266 this.element.find('#clear_current_output').click(function () {
272 this.element.find('#clear_current_output').click(function () {
267 that.notebook.clear_output();
273 that.notebook.clear_output();
268 });
274 });
269
275
270 this.element.find('#toggle_all_output').click(function () {
276 this.element.find('#toggle_all_output').click(function () {
271 that.notebook.toggle_all_output();
277 that.notebook.toggle_all_output();
272 });
278 });
273 this.element.find('#toggle_all_output_scroll').click(function () {
279 this.element.find('#toggle_all_output_scroll').click(function () {
274 that.notebook.toggle_all_output_scroll();
280 that.notebook.toggle_all_output_scroll();
275 });
281 });
276 this.element.find('#clear_all_output').click(function () {
282 this.element.find('#clear_all_output').click(function () {
277 that.notebook.clear_all_output();
283 that.notebook.clear_all_output();
278 });
284 });
279
285
280 // Kernel
286 // Kernel
281 this.element.find('#int_kernel').click(function () {
287 this.element.find('#int_kernel').click(function () {
282 that.notebook.kernel.interrupt();
288 that.notebook.kernel.interrupt();
283 });
289 });
284 this.element.find('#restart_kernel').click(function () {
290 this.element.find('#restart_kernel').click(function () {
285 that.notebook.restart_kernel();
291 that.notebook.restart_kernel();
286 });
292 });
287 this.element.find('#reconnect_kernel').click(function () {
293 this.element.find('#reconnect_kernel').click(function () {
288 that.notebook.kernel.reconnect();
294 that.notebook.kernel.reconnect();
289 });
295 });
290 // Help
296 // Help
291 if (this.tour) {
297 if (this.tour) {
292 this.element.find('#notebook_tour').click(function () {
298 this.element.find('#notebook_tour').click(function () {
293 that.tour.start();
299 that.tour.start();
294 });
300 });
295 } else {
301 } else {
296 this.element.find('#notebook_tour').addClass("disabled");
302 this.element.find('#notebook_tour').addClass("disabled");
297 }
303 }
298 this.element.find('#keyboard_shortcuts').click(function () {
304 this.element.find('#keyboard_shortcuts').click(function () {
299 that.quick_help.show_keyboard_shortcuts();
305 that.quick_help.show_keyboard_shortcuts();
300 });
306 });
301
307
302 this.update_restore_checkpoint(null);
308 this.update_restore_checkpoint(null);
303
309
304 this.events.on('checkpoints_listed.Notebook', function (event, data) {
310 this.events.on('checkpoints_listed.Notebook', function (event, data) {
305 that.update_restore_checkpoint(that.notebook.checkpoints);
311 that.update_restore_checkpoint(that.notebook.checkpoints);
306 });
312 });
307
313
308 this.events.on('checkpoint_created.Notebook', function (event, data) {
314 this.events.on('checkpoint_created.Notebook', function (event, data) {
309 that.update_restore_checkpoint(that.notebook.checkpoints);
315 that.update_restore_checkpoint(that.notebook.checkpoints);
310 });
316 });
311
317
312 this.events.on('notebook_loaded.Notebook', function() {
318 this.events.on('notebook_loaded.Notebook', function() {
313 var langinfo = that.notebook.metadata.language_info || {};
319 var langinfo = that.notebook.metadata.language_info || {};
314 that.update_nbconvert_script(langinfo);
320 that.update_nbconvert_script(langinfo);
315 });
321 });
316
322
317 this.events.on('kernel_ready.Kernel', function(event, data) {
323 this.events.on('kernel_ready.Kernel', function(event, data) {
318 var langinfo = data.kernel.info_reply.language_info || {};
324 var langinfo = data.kernel.info_reply.language_info || {};
319 that.update_nbconvert_script(langinfo);
325 that.update_nbconvert_script(langinfo);
320 });
326 });
321 };
327 };
322
328
323 MenuBar.prototype.update_restore_checkpoint = function(checkpoints) {
329 MenuBar.prototype.update_restore_checkpoint = function(checkpoints) {
324 var ul = this.element.find("#restore_checkpoint").find("ul");
330 var ul = this.element.find("#restore_checkpoint").find("ul");
325 ul.empty();
331 ul.empty();
326 if (!checkpoints || checkpoints.length === 0) {
332 if (!checkpoints || checkpoints.length === 0) {
327 ul.append(
333 ul.append(
328 $("<li/>")
334 $("<li/>")
329 .addClass("disabled")
335 .addClass("disabled")
330 .append(
336 .append(
331 $("<a/>")
337 $("<a/>")
332 .text("No checkpoints")
338 .text("No checkpoints")
333 )
339 )
334 );
340 );
335 return;
341 return;
336 }
342 }
337
343
338 var that = this;
344 var that = this;
339 checkpoints.map(function (checkpoint) {
345 checkpoints.map(function (checkpoint) {
340 var d = new Date(checkpoint.last_modified);
346 var d = new Date(checkpoint.last_modified);
341 ul.append(
347 ul.append(
342 $("<li/>").append(
348 $("<li/>").append(
343 $("<a/>")
349 $("<a/>")
344 .attr("href", "#")
350 .attr("href", "#")
345 .text(moment(d).format("LLLL"))
351 .text(moment(d).format("LLLL"))
346 .click(function () {
352 .click(function () {
347 that.notebook.restore_checkpoint_dialog(checkpoint);
353 that.notebook.restore_checkpoint_dialog(checkpoint);
348 })
354 })
349 )
355 )
350 );
356 );
351 });
357 });
352 };
358 };
353
359
354 MenuBar.prototype.update_nbconvert_script = function(langinfo) {
360 MenuBar.prototype.update_nbconvert_script = function(langinfo) {
355 // Set the 'Download as foo' menu option for the relevant language.
361 /**
362 * Set the 'Download as foo' menu option for the relevant language.
363 */
356 var el = this.element.find('#download_script');
364 var el = this.element.find('#download_script');
357 var that = this;
365 var that = this;
358
366
359 // Set menu entry text to e.g. "Python (.py)"
367 // Set menu entry text to e.g. "Python (.py)"
360 var langname = (langinfo.name || 'Script')
368 var langname = (langinfo.name || 'Script')
361 langname = langname.charAt(0).toUpperCase()+langname.substr(1) // Capitalise
369 langname = langname.charAt(0).toUpperCase()+langname.substr(1) // Capitalise
362 el.find('a').text(langname + ' ('+(langinfo.file_extension || 'txt')+')');
370 el.find('a').text(langname + ' ('+(langinfo.file_extension || 'txt')+')');
363
371
364 // Unregister any previously registered handlers
372 // Unregister any previously registered handlers
365 el.off('click');
373 el.off('click');
366 if (langinfo.nbconvert_exporter) {
374 if (langinfo.nbconvert_exporter) {
367 // Metadata specifies a specific exporter, e.g. 'python'
375 // Metadata specifies a specific exporter, e.g. 'python'
368 el.click(function() {
376 el.click(function() {
369 that._nbconvert(langinfo.nbconvert_exporter, true);
377 that._nbconvert(langinfo.nbconvert_exporter, true);
370 });
378 });
371 } else {
379 } else {
372 // Use generic 'script' exporter
380 // Use generic 'script' exporter
373 el.click(function() {
381 el.click(function() {
374 that._nbconvert('script', true);
382 that._nbconvert('script', true);
375 });
383 });
376 }
384 }
377 };
385 };
378
386
379 // Backwards compatability.
387 // Backwards compatability.
380 IPython.MenuBar = MenuBar;
388 IPython.MenuBar = MenuBar;
381
389
382 return {'MenuBar': MenuBar};
390 return {'MenuBar': MenuBar};
383 });
391 });
@@ -1,2508 +1,2518 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/cell',
9 'notebook/js/cell',
10 'notebook/js/textcell',
10 'notebook/js/textcell',
11 'notebook/js/codecell',
11 'notebook/js/codecell',
12 'services/sessions/session',
12 'services/sessions/session',
13 'notebook/js/celltoolbar',
13 'notebook/js/celltoolbar',
14 'components/marked/lib/marked',
14 'components/marked/lib/marked',
15 'codemirror/lib/codemirror',
15 'codemirror/lib/codemirror',
16 'codemirror/addon/runmode/runmode',
16 'codemirror/addon/runmode/runmode',
17 'notebook/js/mathjaxutils',
17 'notebook/js/mathjaxutils',
18 'base/js/keyboard',
18 'base/js/keyboard',
19 'notebook/js/tooltip',
19 'notebook/js/tooltip',
20 'notebook/js/celltoolbarpresets/default',
20 'notebook/js/celltoolbarpresets/default',
21 'notebook/js/celltoolbarpresets/rawcell',
21 'notebook/js/celltoolbarpresets/rawcell',
22 'notebook/js/celltoolbarpresets/slideshow',
22 'notebook/js/celltoolbarpresets/slideshow',
23 'notebook/js/scrollmanager'
23 'notebook/js/scrollmanager'
24 ], function (
24 ], function (
25 IPython,
25 IPython,
26 $,
26 $,
27 utils,
27 utils,
28 dialog,
28 dialog,
29 cellmod,
29 cellmod,
30 textcell,
30 textcell,
31 codecell,
31 codecell,
32 session,
32 session,
33 celltoolbar,
33 celltoolbar,
34 marked,
34 marked,
35 CodeMirror,
35 CodeMirror,
36 runMode,
36 runMode,
37 mathjaxutils,
37 mathjaxutils,
38 keyboard,
38 keyboard,
39 tooltip,
39 tooltip,
40 default_celltoolbar,
40 default_celltoolbar,
41 rawcell_celltoolbar,
41 rawcell_celltoolbar,
42 slideshow_celltoolbar,
42 slideshow_celltoolbar,
43 scrollmanager
43 scrollmanager
44 ) {
44 ) {
45 "use strict";
45 "use strict";
46
46
47 var Notebook = function (selector, options) {
47 var Notebook = function (selector, options) {
48 // Constructor
48 /**
49 //
49 * Constructor
50 // A notebook contains and manages cells.
50 *
51 //
51 * A notebook contains and manages cells.
52 // Parameters:
52 *
53 // selector: string
53 * Parameters:
54 // options: dictionary
54 * selector: string
55 // Dictionary of keyword arguments.
55 * options: dictionary
56 // events: $(Events) instance
56 * Dictionary of keyword arguments.
57 // keyboard_manager: KeyboardManager instance
57 * events: $(Events) instance
58 // contents: Contents instance
58 * keyboard_manager: KeyboardManager instance
59 // save_widget: SaveWidget instance
59 * contents: Contents instance
60 // config: dictionary
60 * save_widget: SaveWidget instance
61 // base_url : string
61 * config: dictionary
62 // notebook_path : string
62 * base_url : string
63 // notebook_name : string
63 * notebook_path : string
64 * notebook_name : string
65 */
64 this.config = utils.mergeopt(Notebook, options.config);
66 this.config = utils.mergeopt(Notebook, options.config);
65 this.base_url = options.base_url;
67 this.base_url = options.base_url;
66 this.notebook_path = options.notebook_path;
68 this.notebook_path = options.notebook_path;
67 this.notebook_name = options.notebook_name;
69 this.notebook_name = options.notebook_name;
68 this.events = options.events;
70 this.events = options.events;
69 this.keyboard_manager = options.keyboard_manager;
71 this.keyboard_manager = options.keyboard_manager;
70 this.contents = options.contents;
72 this.contents = options.contents;
71 this.save_widget = options.save_widget;
73 this.save_widget = options.save_widget;
72 this.tooltip = new tooltip.Tooltip(this.events);
74 this.tooltip = new tooltip.Tooltip(this.events);
73 this.ws_url = options.ws_url;
75 this.ws_url = options.ws_url;
74 this._session_starting = false;
76 this._session_starting = false;
75 this.default_cell_type = this.config.default_cell_type || 'code';
77 this.default_cell_type = this.config.default_cell_type || 'code';
76
78
77 // Create default scroll manager.
79 // Create default scroll manager.
78 this.scroll_manager = new scrollmanager.ScrollManager(this);
80 this.scroll_manager = new scrollmanager.ScrollManager(this);
79
81
80 // TODO: This code smells (and the other `= this` line a couple lines down)
82 // TODO: This code smells (and the other `= this` line a couple lines down)
81 // We need a better way to deal with circular instance references.
83 // We need a better way to deal with circular instance references.
82 this.keyboard_manager.notebook = this;
84 this.keyboard_manager.notebook = this;
83 this.save_widget.notebook = this;
85 this.save_widget.notebook = this;
84
86
85 mathjaxutils.init();
87 mathjaxutils.init();
86
88
87 if (marked) {
89 if (marked) {
88 marked.setOptions({
90 marked.setOptions({
89 gfm : true,
91 gfm : true,
90 tables: true,
92 tables: true,
91 // FIXME: probably want central config for CodeMirror theme when we have js config
93 // FIXME: probably want central config for CodeMirror theme when we have js config
92 langPrefix: "cm-s-ipython language-",
94 langPrefix: "cm-s-ipython language-",
93 highlight: function(code, lang, callback) {
95 highlight: function(code, lang, callback) {
94 if (!lang) {
96 if (!lang) {
95 // no language, no highlight
97 // no language, no highlight
96 if (callback) {
98 if (callback) {
97 callback(null, code);
99 callback(null, code);
98 return;
100 return;
99 } else {
101 } else {
100 return code;
102 return code;
101 }
103 }
102 }
104 }
103 utils.requireCodeMirrorMode(lang, function () {
105 utils.requireCodeMirrorMode(lang, function () {
104 var el = document.createElement("div");
106 var el = document.createElement("div");
105 var mode = CodeMirror.getMode({}, lang);
107 var mode = CodeMirror.getMode({}, lang);
106 if (!mode) {
108 if (!mode) {
107 console.log("No CodeMirror mode: " + lang);
109 console.log("No CodeMirror mode: " + lang);
108 callback(null, code);
110 callback(null, code);
109 return;
111 return;
110 }
112 }
111 try {
113 try {
112 CodeMirror.runMode(code, mode, el);
114 CodeMirror.runMode(code, mode, el);
113 callback(null, el.innerHTML);
115 callback(null, el.innerHTML);
114 } catch (err) {
116 } catch (err) {
115 console.log("Failed to highlight " + lang + " code", err);
117 console.log("Failed to highlight " + lang + " code", err);
116 callback(err, code);
118 callback(err, code);
117 }
119 }
118 }, function (err) {
120 }, function (err) {
119 console.log("No CodeMirror mode: " + lang);
121 console.log("No CodeMirror mode: " + lang);
120 callback(err, code);
122 callback(err, code);
121 });
123 });
122 }
124 }
123 });
125 });
124 }
126 }
125
127
126 this.element = $(selector);
128 this.element = $(selector);
127 this.element.scroll();
129 this.element.scroll();
128 this.element.data("notebook", this);
130 this.element.data("notebook", this);
129 this.next_prompt_number = 1;
131 this.next_prompt_number = 1;
130 this.session = null;
132 this.session = null;
131 this.kernel = null;
133 this.kernel = null;
132 this.clipboard = null;
134 this.clipboard = null;
133 this.undelete_backup = null;
135 this.undelete_backup = null;
134 this.undelete_index = null;
136 this.undelete_index = null;
135 this.undelete_below = false;
137 this.undelete_below = false;
136 this.paste_enabled = false;
138 this.paste_enabled = false;
137 this.writable = false;
139 this.writable = false;
138 // It is important to start out in command mode to match the intial mode
140 // It is important to start out in command mode to match the intial mode
139 // of the KeyboardManager.
141 // of the KeyboardManager.
140 this.mode = 'command';
142 this.mode = 'command';
141 this.set_dirty(false);
143 this.set_dirty(false);
142 this.metadata = {};
144 this.metadata = {};
143 this._checkpoint_after_save = false;
145 this._checkpoint_after_save = false;
144 this.last_checkpoint = null;
146 this.last_checkpoint = null;
145 this.checkpoints = [];
147 this.checkpoints = [];
146 this.autosave_interval = 0;
148 this.autosave_interval = 0;
147 this.autosave_timer = null;
149 this.autosave_timer = null;
148 // autosave *at most* every two minutes
150 // autosave *at most* every two minutes
149 this.minimum_autosave_interval = 120000;
151 this.minimum_autosave_interval = 120000;
150 this.notebook_name_blacklist_re = /[\/\\:]/;
152 this.notebook_name_blacklist_re = /[\/\\:]/;
151 this.nbformat = 4; // Increment this when changing the nbformat
153 this.nbformat = 4; // Increment this when changing the nbformat
152 this.nbformat_minor = this.current_nbformat_minor = 0; // Increment this when changing the nbformat
154 this.nbformat_minor = this.current_nbformat_minor = 0; // Increment this when changing the nbformat
153 this.codemirror_mode = 'ipython';
155 this.codemirror_mode = 'ipython';
154 this.create_elements();
156 this.create_elements();
155 this.bind_events();
157 this.bind_events();
156 this.kernel_selector = null;
158 this.kernel_selector = null;
157 this.dirty = null;
159 this.dirty = null;
158 this.trusted = null;
160 this.trusted = null;
159 this._fully_loaded = false;
161 this._fully_loaded = false;
160
162
161 // Trigger cell toolbar registration.
163 // Trigger cell toolbar registration.
162 default_celltoolbar.register(this);
164 default_celltoolbar.register(this);
163 rawcell_celltoolbar.register(this);
165 rawcell_celltoolbar.register(this);
164 slideshow_celltoolbar.register(this);
166 slideshow_celltoolbar.register(this);
165
167
166 // prevent assign to miss-typed properties.
168 // prevent assign to miss-typed properties.
167 Object.seal(this);
169 Object.seal(this);
168 };
170 };
169
171
170 Notebook.options_default = {
172 Notebook.options_default = {
171 // can be any cell type, or the special values of
173 // can be any cell type, or the special values of
172 // 'above', 'below', or 'selected' to get the value from another cell.
174 // 'above', 'below', or 'selected' to get the value from another cell.
173 Notebook: {
175 Notebook: {
174 default_cell_type: 'code'
176 default_cell_type: 'code'
175 }
177 }
176 };
178 };
177
179
178
180
179 /**
181 /**
180 * Create an HTML and CSS representation of the notebook.
182 * Create an HTML and CSS representation of the notebook.
181 *
183 *
182 * @method create_elements
184 * @method create_elements
183 */
185 */
184 Notebook.prototype.create_elements = function () {
186 Notebook.prototype.create_elements = function () {
185 var that = this;
187 var that = this;
186 this.element.attr('tabindex','-1');
188 this.element.attr('tabindex','-1');
187 this.container = $("<div/>").addClass("container").attr("id", "notebook-container");
189 this.container = $("<div/>").addClass("container").attr("id", "notebook-container");
188 // We add this end_space div to the end of the notebook div to:
190 // We add this end_space div to the end of the notebook div to:
189 // i) provide a margin between the last cell and the end of the notebook
191 // i) provide a margin between the last cell and the end of the notebook
190 // ii) to prevent the div from scrolling up when the last cell is being
192 // ii) to prevent the div from scrolling up when the last cell is being
191 // edited, but is too low on the page, which browsers will do automatically.
193 // edited, but is too low on the page, which browsers will do automatically.
192 var end_space = $('<div/>').addClass('end_space');
194 var end_space = $('<div/>').addClass('end_space');
193 end_space.dblclick(function (e) {
195 end_space.dblclick(function (e) {
194 var ncells = that.ncells();
196 var ncells = that.ncells();
195 that.insert_cell_below('code',ncells-1);
197 that.insert_cell_below('code',ncells-1);
196 });
198 });
197 this.element.append(this.container);
199 this.element.append(this.container);
198 this.container.append(end_space);
200 this.container.append(end_space);
199 };
201 };
200
202
201 /**
203 /**
202 * Bind JavaScript events: key presses and custom IPython events.
204 * Bind JavaScript events: key presses and custom IPython events.
203 *
205 *
204 * @method bind_events
206 * @method bind_events
205 */
207 */
206 Notebook.prototype.bind_events = function () {
208 Notebook.prototype.bind_events = function () {
207 var that = this;
209 var that = this;
208
210
209 this.events.on('set_next_input.Notebook', function (event, data) {
211 this.events.on('set_next_input.Notebook', function (event, data) {
210 var index = that.find_cell_index(data.cell);
212 var index = that.find_cell_index(data.cell);
211 var new_cell = that.insert_cell_below('code',index);
213 var new_cell = that.insert_cell_below('code',index);
212 new_cell.set_text(data.text);
214 new_cell.set_text(data.text);
213 that.dirty = true;
215 that.dirty = true;
214 });
216 });
215
217
216 this.events.on('unrecognized_cell.Cell', function () {
218 this.events.on('unrecognized_cell.Cell', function () {
217 that.warn_nbformat_minor();
219 that.warn_nbformat_minor();
218 });
220 });
219
221
220 this.events.on('unrecognized_output.OutputArea', function () {
222 this.events.on('unrecognized_output.OutputArea', function () {
221 that.warn_nbformat_minor();
223 that.warn_nbformat_minor();
222 });
224 });
223
225
224 this.events.on('set_dirty.Notebook', function (event, data) {
226 this.events.on('set_dirty.Notebook', function (event, data) {
225 that.dirty = data.value;
227 that.dirty = data.value;
226 });
228 });
227
229
228 this.events.on('trust_changed.Notebook', function (event, trusted) {
230 this.events.on('trust_changed.Notebook', function (event, trusted) {
229 that.trusted = trusted;
231 that.trusted = trusted;
230 });
232 });
231
233
232 this.events.on('select.Cell', function (event, data) {
234 this.events.on('select.Cell', function (event, data) {
233 var index = that.find_cell_index(data.cell);
235 var index = that.find_cell_index(data.cell);
234 that.select(index);
236 that.select(index);
235 });
237 });
236
238
237 this.events.on('edit_mode.Cell', function (event, data) {
239 this.events.on('edit_mode.Cell', function (event, data) {
238 that.handle_edit_mode(data.cell);
240 that.handle_edit_mode(data.cell);
239 });
241 });
240
242
241 this.events.on('command_mode.Cell', function (event, data) {
243 this.events.on('command_mode.Cell', function (event, data) {
242 that.handle_command_mode(data.cell);
244 that.handle_command_mode(data.cell);
243 });
245 });
244
246
245 this.events.on('spec_changed.Kernel', function(event, data) {
247 this.events.on('spec_changed.Kernel', function(event, data) {
246 that.metadata.kernelspec =
248 that.metadata.kernelspec =
247 {name: data.name, display_name: data.display_name};
249 {name: data.name, display_name: data.display_name};
248 });
250 });
249
251
250 this.events.on('kernel_ready.Kernel', function(event, data) {
252 this.events.on('kernel_ready.Kernel', function(event, data) {
251 var kinfo = data.kernel.info_reply;
253 var kinfo = data.kernel.info_reply;
252 var langinfo = kinfo.language_info || {};
254 var langinfo = kinfo.language_info || {};
253 that.metadata.language_info = langinfo;
255 that.metadata.language_info = langinfo;
254 // Mode 'null' should be plain, unhighlighted text.
256 // Mode 'null' should be plain, unhighlighted text.
255 var cm_mode = langinfo.codemirror_mode || langinfo.name || 'null';
257 var cm_mode = langinfo.codemirror_mode || langinfo.name || 'null';
256 that.set_codemirror_mode(cm_mode);
258 that.set_codemirror_mode(cm_mode);
257 });
259 });
258
260
259 var collapse_time = function (time) {
261 var collapse_time = function (time) {
260 var app_height = $('#ipython-main-app').height(); // content height
262 var app_height = $('#ipython-main-app').height(); // content height
261 var splitter_height = $('div#pager_splitter').outerHeight(true);
263 var splitter_height = $('div#pager_splitter').outerHeight(true);
262 var new_height = app_height - splitter_height;
264 var new_height = app_height - splitter_height;
263 that.element.animate({height : new_height + 'px'}, time);
265 that.element.animate({height : new_height + 'px'}, time);
264 };
266 };
265
267
266 this.element.bind('collapse_pager', function (event, extrap) {
268 this.element.bind('collapse_pager', function (event, extrap) {
267 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
269 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
268 collapse_time(time);
270 collapse_time(time);
269 });
271 });
270
272
271 var expand_time = function (time) {
273 var expand_time = function (time) {
272 var app_height = $('#ipython-main-app').height(); // content height
274 var app_height = $('#ipython-main-app').height(); // content height
273 var splitter_height = $('div#pager_splitter').outerHeight(true);
275 var splitter_height = $('div#pager_splitter').outerHeight(true);
274 var pager_height = $('div#pager').outerHeight(true);
276 var pager_height = $('div#pager').outerHeight(true);
275 var new_height = app_height - pager_height - splitter_height;
277 var new_height = app_height - pager_height - splitter_height;
276 that.element.animate({height : new_height + 'px'}, time);
278 that.element.animate({height : new_height + 'px'}, time);
277 };
279 };
278
280
279 this.element.bind('expand_pager', function (event, extrap) {
281 this.element.bind('expand_pager', function (event, extrap) {
280 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
282 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
281 expand_time(time);
283 expand_time(time);
282 });
284 });
283
285
284 // Firefox 22 broke $(window).on("beforeunload")
286 // Firefox 22 broke $(window).on("beforeunload")
285 // I'm not sure why or how.
287 // I'm not sure why or how.
286 window.onbeforeunload = function (e) {
288 window.onbeforeunload = function (e) {
287 // TODO: Make killing the kernel configurable.
289 // TODO: Make killing the kernel configurable.
288 var kill_kernel = false;
290 var kill_kernel = false;
289 if (kill_kernel) {
291 if (kill_kernel) {
290 that.session.delete();
292 that.session.delete();
291 }
293 }
292 // if we are autosaving, trigger an autosave on nav-away.
294 // if we are autosaving, trigger an autosave on nav-away.
293 // still warn, because if we don't the autosave may fail.
295 // still warn, because if we don't the autosave may fail.
294 if (that.dirty) {
296 if (that.dirty) {
295 if ( that.autosave_interval ) {
297 if ( that.autosave_interval ) {
296 // schedule autosave in a timeout
298 // schedule autosave in a timeout
297 // this gives you a chance to forcefully discard changes
299 // this gives you a chance to forcefully discard changes
298 // by reloading the page if you *really* want to.
300 // by reloading the page if you *really* want to.
299 // the timer doesn't start until you *dismiss* the dialog.
301 // the timer doesn't start until you *dismiss* the dialog.
300 setTimeout(function () {
302 setTimeout(function () {
301 if (that.dirty) {
303 if (that.dirty) {
302 that.save_notebook();
304 that.save_notebook();
303 }
305 }
304 }, 1000);
306 }, 1000);
305 return "Autosave in progress, latest changes may be lost.";
307 return "Autosave in progress, latest changes may be lost.";
306 } else {
308 } else {
307 return "Unsaved changes will be lost.";
309 return "Unsaved changes will be lost.";
308 }
310 }
309 }
311 }
310 // Null is the *only* return value that will make the browser not
312 // Null is the *only* return value that will make the browser not
311 // pop up the "don't leave" dialog.
313 // pop up the "don't leave" dialog.
312 return null;
314 return null;
313 };
315 };
314 };
316 };
315
317
316 Notebook.prototype.warn_nbformat_minor = function (event) {
318 Notebook.prototype.warn_nbformat_minor = function (event) {
317 // trigger a warning dialog about missing functionality from newer minor versions
319 /**
320 * trigger a warning dialog about missing functionality from newer minor versions
321 */
318 var v = 'v' + this.nbformat + '.';
322 var v = 'v' + this.nbformat + '.';
319 var orig_vs = v + this.nbformat_minor;
323 var orig_vs = v + this.nbformat_minor;
320 var this_vs = v + this.current_nbformat_minor;
324 var this_vs = v + this.current_nbformat_minor;
321 var msg = "This notebook is version " + orig_vs + ", but we only fully support up to " +
325 var msg = "This notebook is version " + orig_vs + ", but we only fully support up to " +
322 this_vs + ". You can still work with this notebook, but cell and output types " +
326 this_vs + ". You can still work with this notebook, but cell and output types " +
323 "introduced in later notebook versions will not be available.";
327 "introduced in later notebook versions will not be available.";
324
328
325 dialog.modal({
329 dialog.modal({
326 notebook: this,
330 notebook: this,
327 keyboard_manager: this.keyboard_manager,
331 keyboard_manager: this.keyboard_manager,
328 title : "Newer Notebook",
332 title : "Newer Notebook",
329 body : msg,
333 body : msg,
330 buttons : {
334 buttons : {
331 OK : {
335 OK : {
332 "class" : "btn-danger"
336 "class" : "btn-danger"
333 }
337 }
334 }
338 }
335 });
339 });
336 }
340 }
337
341
338 /**
342 /**
339 * Set the dirty flag, and trigger the set_dirty.Notebook event
343 * Set the dirty flag, and trigger the set_dirty.Notebook event
340 *
344 *
341 * @method set_dirty
345 * @method set_dirty
342 */
346 */
343 Notebook.prototype.set_dirty = function (value) {
347 Notebook.prototype.set_dirty = function (value) {
344 if (value === undefined) {
348 if (value === undefined) {
345 value = true;
349 value = true;
346 }
350 }
347 if (this.dirty == value) {
351 if (this.dirty == value) {
348 return;
352 return;
349 }
353 }
350 this.events.trigger('set_dirty.Notebook', {value: value});
354 this.events.trigger('set_dirty.Notebook', {value: value});
351 };
355 };
352
356
353 /**
357 /**
354 * Scroll the top of the page to a given cell.
358 * Scroll the top of the page to a given cell.
355 *
359 *
356 * @method scroll_to_cell
360 * @method scroll_to_cell
357 * @param {Number} cell_number An index of the cell to view
361 * @param {Number} cell_number An index of the cell to view
358 * @param {Number} time Animation time in milliseconds
362 * @param {Number} time Animation time in milliseconds
359 * @return {Number} Pixel offset from the top of the container
363 * @return {Number} Pixel offset from the top of the container
360 */
364 */
361 Notebook.prototype.scroll_to_cell = function (cell_number, time) {
365 Notebook.prototype.scroll_to_cell = function (cell_number, time) {
362 var cells = this.get_cells();
366 var cells = this.get_cells();
363 time = time || 0;
367 time = time || 0;
364 cell_number = Math.min(cells.length-1,cell_number);
368 cell_number = Math.min(cells.length-1,cell_number);
365 cell_number = Math.max(0 ,cell_number);
369 cell_number = Math.max(0 ,cell_number);
366 var scroll_value = cells[cell_number].element.position().top-cells[0].element.position().top ;
370 var scroll_value = cells[cell_number].element.position().top-cells[0].element.position().top ;
367 this.element.animate({scrollTop:scroll_value}, time);
371 this.element.animate({scrollTop:scroll_value}, time);
368 return scroll_value;
372 return scroll_value;
369 };
373 };
370
374
371 /**
375 /**
372 * Scroll to the bottom of the page.
376 * Scroll to the bottom of the page.
373 *
377 *
374 * @method scroll_to_bottom
378 * @method scroll_to_bottom
375 */
379 */
376 Notebook.prototype.scroll_to_bottom = function () {
380 Notebook.prototype.scroll_to_bottom = function () {
377 this.element.animate({scrollTop:this.element.get(0).scrollHeight}, 0);
381 this.element.animate({scrollTop:this.element.get(0).scrollHeight}, 0);
378 };
382 };
379
383
380 /**
384 /**
381 * Scroll to the top of the page.
385 * Scroll to the top of the page.
382 *
386 *
383 * @method scroll_to_top
387 * @method scroll_to_top
384 */
388 */
385 Notebook.prototype.scroll_to_top = function () {
389 Notebook.prototype.scroll_to_top = function () {
386 this.element.animate({scrollTop:0}, 0);
390 this.element.animate({scrollTop:0}, 0);
387 };
391 };
388
392
389 // Edit Notebook metadata
393 // Edit Notebook metadata
390
394
391 Notebook.prototype.edit_metadata = function () {
395 Notebook.prototype.edit_metadata = function () {
392 var that = this;
396 var that = this;
393 dialog.edit_metadata({
397 dialog.edit_metadata({
394 md: this.metadata,
398 md: this.metadata,
395 callback: function (md) {
399 callback: function (md) {
396 that.metadata = md;
400 that.metadata = md;
397 },
401 },
398 name: 'Notebook',
402 name: 'Notebook',
399 notebook: this,
403 notebook: this,
400 keyboard_manager: this.keyboard_manager});
404 keyboard_manager: this.keyboard_manager});
401 };
405 };
402
406
403 // Cell indexing, retrieval, etc.
407 // Cell indexing, retrieval, etc.
404
408
405 /**
409 /**
406 * Get all cell elements in the notebook.
410 * Get all cell elements in the notebook.
407 *
411 *
408 * @method get_cell_elements
412 * @method get_cell_elements
409 * @return {jQuery} A selector of all cell elements
413 * @return {jQuery} A selector of all cell elements
410 */
414 */
411 Notebook.prototype.get_cell_elements = function () {
415 Notebook.prototype.get_cell_elements = function () {
412 return this.container.find(".cell").not('.cell .cell');
416 return this.container.find(".cell").not('.cell .cell');
413 };
417 };
414
418
415 /**
419 /**
416 * Get a particular cell element.
420 * Get a particular cell element.
417 *
421 *
418 * @method get_cell_element
422 * @method get_cell_element
419 * @param {Number} index An index of a cell to select
423 * @param {Number} index An index of a cell to select
420 * @return {jQuery} A selector of the given cell.
424 * @return {jQuery} A selector of the given cell.
421 */
425 */
422 Notebook.prototype.get_cell_element = function (index) {
426 Notebook.prototype.get_cell_element = function (index) {
423 var result = null;
427 var result = null;
424 var e = this.get_cell_elements().eq(index);
428 var e = this.get_cell_elements().eq(index);
425 if (e.length !== 0) {
429 if (e.length !== 0) {
426 result = e;
430 result = e;
427 }
431 }
428 return result;
432 return result;
429 };
433 };
430
434
431 /**
435 /**
432 * Try to get a particular cell by msg_id.
436 * Try to get a particular cell by msg_id.
433 *
437 *
434 * @method get_msg_cell
438 * @method get_msg_cell
435 * @param {String} msg_id A message UUID
439 * @param {String} msg_id A message UUID
436 * @return {Cell} Cell or null if no cell was found.
440 * @return {Cell} Cell or null if no cell was found.
437 */
441 */
438 Notebook.prototype.get_msg_cell = function (msg_id) {
442 Notebook.prototype.get_msg_cell = function (msg_id) {
439 return codecell.CodeCell.msg_cells[msg_id] || null;
443 return codecell.CodeCell.msg_cells[msg_id] || null;
440 };
444 };
441
445
442 /**
446 /**
443 * Count the cells in this notebook.
447 * Count the cells in this notebook.
444 *
448 *
445 * @method ncells
449 * @method ncells
446 * @return {Number} The number of cells in this notebook
450 * @return {Number} The number of cells in this notebook
447 */
451 */
448 Notebook.prototype.ncells = function () {
452 Notebook.prototype.ncells = function () {
449 return this.get_cell_elements().length;
453 return this.get_cell_elements().length;
450 };
454 };
451
455
452 /**
456 /**
453 * Get all Cell objects in this notebook.
457 * Get all Cell objects in this notebook.
454 *
458 *
455 * @method get_cells
459 * @method get_cells
456 * @return {Array} This notebook's Cell objects
460 * @return {Array} This notebook's Cell objects
457 */
461 */
458 // TODO: we are often calling cells as cells()[i], which we should optimize
462 // TODO: we are often calling cells as cells()[i], which we should optimize
459 // to cells(i) or a new method.
463 // to cells(i) or a new method.
460 Notebook.prototype.get_cells = function () {
464 Notebook.prototype.get_cells = function () {
461 return this.get_cell_elements().toArray().map(function (e) {
465 return this.get_cell_elements().toArray().map(function (e) {
462 return $(e).data("cell");
466 return $(e).data("cell");
463 });
467 });
464 };
468 };
465
469
466 /**
470 /**
467 * Get a Cell object from this notebook.
471 * Get a Cell object from this notebook.
468 *
472 *
469 * @method get_cell
473 * @method get_cell
470 * @param {Number} index An index of a cell to retrieve
474 * @param {Number} index An index of a cell to retrieve
471 * @return {Cell} Cell or null if no cell was found.
475 * @return {Cell} Cell or null if no cell was found.
472 */
476 */
473 Notebook.prototype.get_cell = function (index) {
477 Notebook.prototype.get_cell = function (index) {
474 var result = null;
478 var result = null;
475 var ce = this.get_cell_element(index);
479 var ce = this.get_cell_element(index);
476 if (ce !== null) {
480 if (ce !== null) {
477 result = ce.data('cell');
481 result = ce.data('cell');
478 }
482 }
479 return result;
483 return result;
480 };
484 };
481
485
482 /**
486 /**
483 * Get the cell below a given cell.
487 * Get the cell below a given cell.
484 *
488 *
485 * @method get_next_cell
489 * @method get_next_cell
486 * @param {Cell} cell The provided cell
490 * @param {Cell} cell The provided cell
487 * @return {Cell} the next cell or null if no cell was found.
491 * @return {Cell} the next cell or null if no cell was found.
488 */
492 */
489 Notebook.prototype.get_next_cell = function (cell) {
493 Notebook.prototype.get_next_cell = function (cell) {
490 var result = null;
494 var result = null;
491 var index = this.find_cell_index(cell);
495 var index = this.find_cell_index(cell);
492 if (this.is_valid_cell_index(index+1)) {
496 if (this.is_valid_cell_index(index+1)) {
493 result = this.get_cell(index+1);
497 result = this.get_cell(index+1);
494 }
498 }
495 return result;
499 return result;
496 };
500 };
497
501
498 /**
502 /**
499 * Get the cell above a given cell.
503 * Get the cell above a given cell.
500 *
504 *
501 * @method get_prev_cell
505 * @method get_prev_cell
502 * @param {Cell} cell The provided cell
506 * @param {Cell} cell The provided cell
503 * @return {Cell} The previous cell or null if no cell was found.
507 * @return {Cell} The previous cell or null if no cell was found.
504 */
508 */
505 Notebook.prototype.get_prev_cell = function (cell) {
509 Notebook.prototype.get_prev_cell = function (cell) {
506 var result = null;
510 var result = null;
507 var index = this.find_cell_index(cell);
511 var index = this.find_cell_index(cell);
508 if (index !== null && index > 0) {
512 if (index !== null && index > 0) {
509 result = this.get_cell(index-1);
513 result = this.get_cell(index-1);
510 }
514 }
511 return result;
515 return result;
512 };
516 };
513
517
514 /**
518 /**
515 * Get the numeric index of a given cell.
519 * Get the numeric index of a given cell.
516 *
520 *
517 * @method find_cell_index
521 * @method find_cell_index
518 * @param {Cell} cell The provided cell
522 * @param {Cell} cell The provided cell
519 * @return {Number} The cell's numeric index or null if no cell was found.
523 * @return {Number} The cell's numeric index or null if no cell was found.
520 */
524 */
521 Notebook.prototype.find_cell_index = function (cell) {
525 Notebook.prototype.find_cell_index = function (cell) {
522 var result = null;
526 var result = null;
523 this.get_cell_elements().filter(function (index) {
527 this.get_cell_elements().filter(function (index) {
524 if ($(this).data("cell") === cell) {
528 if ($(this).data("cell") === cell) {
525 result = index;
529 result = index;
526 }
530 }
527 });
531 });
528 return result;
532 return result;
529 };
533 };
530
534
531 /**
535 /**
532 * Get a given index , or the selected index if none is provided.
536 * Get a given index , or the selected index if none is provided.
533 *
537 *
534 * @method index_or_selected
538 * @method index_or_selected
535 * @param {Number} index A cell's index
539 * @param {Number} index A cell's index
536 * @return {Number} The given index, or selected index if none is provided.
540 * @return {Number} The given index, or selected index if none is provided.
537 */
541 */
538 Notebook.prototype.index_or_selected = function (index) {
542 Notebook.prototype.index_or_selected = function (index) {
539 var i;
543 var i;
540 if (index === undefined || index === null) {
544 if (index === undefined || index === null) {
541 i = this.get_selected_index();
545 i = this.get_selected_index();
542 if (i === null) {
546 if (i === null) {
543 i = 0;
547 i = 0;
544 }
548 }
545 } else {
549 } else {
546 i = index;
550 i = index;
547 }
551 }
548 return i;
552 return i;
549 };
553 };
550
554
551 /**
555 /**
552 * Get the currently selected cell.
556 * Get the currently selected cell.
553 * @method get_selected_cell
557 * @method get_selected_cell
554 * @return {Cell} The selected cell
558 * @return {Cell} The selected cell
555 */
559 */
556 Notebook.prototype.get_selected_cell = function () {
560 Notebook.prototype.get_selected_cell = function () {
557 var index = this.get_selected_index();
561 var index = this.get_selected_index();
558 return this.get_cell(index);
562 return this.get_cell(index);
559 };
563 };
560
564
561 /**
565 /**
562 * Check whether a cell index is valid.
566 * Check whether a cell index is valid.
563 *
567 *
564 * @method is_valid_cell_index
568 * @method is_valid_cell_index
565 * @param {Number} index A cell index
569 * @param {Number} index A cell index
566 * @return True if the index is valid, false otherwise
570 * @return True if the index is valid, false otherwise
567 */
571 */
568 Notebook.prototype.is_valid_cell_index = function (index) {
572 Notebook.prototype.is_valid_cell_index = function (index) {
569 if (index !== null && index >= 0 && index < this.ncells()) {
573 if (index !== null && index >= 0 && index < this.ncells()) {
570 return true;
574 return true;
571 } else {
575 } else {
572 return false;
576 return false;
573 }
577 }
574 };
578 };
575
579
576 /**
580 /**
577 * Get the index of the currently selected cell.
581 * Get the index of the currently selected cell.
578
582
579 * @method get_selected_index
583 * @method get_selected_index
580 * @return {Number} The selected cell's numeric index
584 * @return {Number} The selected cell's numeric index
581 */
585 */
582 Notebook.prototype.get_selected_index = function () {
586 Notebook.prototype.get_selected_index = function () {
583 var result = null;
587 var result = null;
584 this.get_cell_elements().filter(function (index) {
588 this.get_cell_elements().filter(function (index) {
585 if ($(this).data("cell").selected === true) {
589 if ($(this).data("cell").selected === true) {
586 result = index;
590 result = index;
587 }
591 }
588 });
592 });
589 return result;
593 return result;
590 };
594 };
591
595
592
596
593 // Cell selection.
597 // Cell selection.
594
598
595 /**
599 /**
596 * Programmatically select a cell.
600 * Programmatically select a cell.
597 *
601 *
598 * @method select
602 * @method select
599 * @param {Number} index A cell's index
603 * @param {Number} index A cell's index
600 * @return {Notebook} This notebook
604 * @return {Notebook} This notebook
601 */
605 */
602 Notebook.prototype.select = function (index) {
606 Notebook.prototype.select = function (index) {
603 if (this.is_valid_cell_index(index)) {
607 if (this.is_valid_cell_index(index)) {
604 var sindex = this.get_selected_index();
608 var sindex = this.get_selected_index();
605 if (sindex !== null && index !== sindex) {
609 if (sindex !== null && index !== sindex) {
606 // If we are about to select a different cell, make sure we are
610 // If we are about to select a different cell, make sure we are
607 // first in command mode.
611 // first in command mode.
608 if (this.mode !== 'command') {
612 if (this.mode !== 'command') {
609 this.command_mode();
613 this.command_mode();
610 }
614 }
611 this.get_cell(sindex).unselect();
615 this.get_cell(sindex).unselect();
612 }
616 }
613 var cell = this.get_cell(index);
617 var cell = this.get_cell(index);
614 cell.select();
618 cell.select();
615 if (cell.cell_type === 'heading') {
619 if (cell.cell_type === 'heading') {
616 this.events.trigger('selected_cell_type_changed.Notebook',
620 this.events.trigger('selected_cell_type_changed.Notebook',
617 {'cell_type':cell.cell_type,level:cell.level}
621 {'cell_type':cell.cell_type,level:cell.level}
618 );
622 );
619 } else {
623 } else {
620 this.events.trigger('selected_cell_type_changed.Notebook',
624 this.events.trigger('selected_cell_type_changed.Notebook',
621 {'cell_type':cell.cell_type}
625 {'cell_type':cell.cell_type}
622 );
626 );
623 }
627 }
624 }
628 }
625 return this;
629 return this;
626 };
630 };
627
631
628 /**
632 /**
629 * Programmatically select the next cell.
633 * Programmatically select the next cell.
630 *
634 *
631 * @method select_next
635 * @method select_next
632 * @return {Notebook} This notebook
636 * @return {Notebook} This notebook
633 */
637 */
634 Notebook.prototype.select_next = function () {
638 Notebook.prototype.select_next = function () {
635 var index = this.get_selected_index();
639 var index = this.get_selected_index();
636 this.select(index+1);
640 this.select(index+1);
637 return this;
641 return this;
638 };
642 };
639
643
640 /**
644 /**
641 * Programmatically select the previous cell.
645 * Programmatically select the previous cell.
642 *
646 *
643 * @method select_prev
647 * @method select_prev
644 * @return {Notebook} This notebook
648 * @return {Notebook} This notebook
645 */
649 */
646 Notebook.prototype.select_prev = function () {
650 Notebook.prototype.select_prev = function () {
647 var index = this.get_selected_index();
651 var index = this.get_selected_index();
648 this.select(index-1);
652 this.select(index-1);
649 return this;
653 return this;
650 };
654 };
651
655
652
656
653 // Edit/Command mode
657 // Edit/Command mode
654
658
655 /**
659 /**
656 * Gets the index of the cell that is in edit mode.
660 * Gets the index of the cell that is in edit mode.
657 *
661 *
658 * @method get_edit_index
662 * @method get_edit_index
659 *
663 *
660 * @return index {int}
664 * @return index {int}
661 **/
665 **/
662 Notebook.prototype.get_edit_index = function () {
666 Notebook.prototype.get_edit_index = function () {
663 var result = null;
667 var result = null;
664 this.get_cell_elements().filter(function (index) {
668 this.get_cell_elements().filter(function (index) {
665 if ($(this).data("cell").mode === 'edit') {
669 if ($(this).data("cell").mode === 'edit') {
666 result = index;
670 result = index;
667 }
671 }
668 });
672 });
669 return result;
673 return result;
670 };
674 };
671
675
672 /**
676 /**
673 * Handle when a a cell blurs and the notebook should enter command mode.
677 * Handle when a a cell blurs and the notebook should enter command mode.
674 *
678 *
675 * @method handle_command_mode
679 * @method handle_command_mode
676 * @param [cell] {Cell} Cell to enter command mode on.
680 * @param [cell] {Cell} Cell to enter command mode on.
677 **/
681 **/
678 Notebook.prototype.handle_command_mode = function (cell) {
682 Notebook.prototype.handle_command_mode = function (cell) {
679 if (this.mode !== 'command') {
683 if (this.mode !== 'command') {
680 cell.command_mode();
684 cell.command_mode();
681 this.mode = 'command';
685 this.mode = 'command';
682 this.events.trigger('command_mode.Notebook');
686 this.events.trigger('command_mode.Notebook');
683 this.keyboard_manager.command_mode();
687 this.keyboard_manager.command_mode();
684 }
688 }
685 };
689 };
686
690
687 /**
691 /**
688 * Make the notebook enter command mode.
692 * Make the notebook enter command mode.
689 *
693 *
690 * @method command_mode
694 * @method command_mode
691 **/
695 **/
692 Notebook.prototype.command_mode = function () {
696 Notebook.prototype.command_mode = function () {
693 var cell = this.get_cell(this.get_edit_index());
697 var cell = this.get_cell(this.get_edit_index());
694 if (cell && this.mode !== 'command') {
698 if (cell && this.mode !== 'command') {
695 // We don't call cell.command_mode, but rather call cell.focus_cell()
699 // We don't call cell.command_mode, but rather call cell.focus_cell()
696 // which will blur and CM editor and trigger the call to
700 // which will blur and CM editor and trigger the call to
697 // handle_command_mode.
701 // handle_command_mode.
698 cell.focus_cell();
702 cell.focus_cell();
699 }
703 }
700 };
704 };
701
705
702 /**
706 /**
703 * Handle when a cell fires it's edit_mode event.
707 * Handle when a cell fires it's edit_mode event.
704 *
708 *
705 * @method handle_edit_mode
709 * @method handle_edit_mode
706 * @param [cell] {Cell} Cell to enter edit mode on.
710 * @param [cell] {Cell} Cell to enter edit mode on.
707 **/
711 **/
708 Notebook.prototype.handle_edit_mode = function (cell) {
712 Notebook.prototype.handle_edit_mode = function (cell) {
709 if (cell && this.mode !== 'edit') {
713 if (cell && this.mode !== 'edit') {
710 cell.edit_mode();
714 cell.edit_mode();
711 this.mode = 'edit';
715 this.mode = 'edit';
712 this.events.trigger('edit_mode.Notebook');
716 this.events.trigger('edit_mode.Notebook');
713 this.keyboard_manager.edit_mode();
717 this.keyboard_manager.edit_mode();
714 }
718 }
715 };
719 };
716
720
717 /**
721 /**
718 * Make a cell enter edit mode.
722 * Make a cell enter edit mode.
719 *
723 *
720 * @method edit_mode
724 * @method edit_mode
721 **/
725 **/
722 Notebook.prototype.edit_mode = function () {
726 Notebook.prototype.edit_mode = function () {
723 var cell = this.get_selected_cell();
727 var cell = this.get_selected_cell();
724 if (cell && this.mode !== 'edit') {
728 if (cell && this.mode !== 'edit') {
725 cell.unrender();
729 cell.unrender();
726 cell.focus_editor();
730 cell.focus_editor();
727 }
731 }
728 };
732 };
729
733
730 /**
734 /**
731 * Focus the currently selected cell.
735 * Focus the currently selected cell.
732 *
736 *
733 * @method focus_cell
737 * @method focus_cell
734 **/
738 **/
735 Notebook.prototype.focus_cell = function () {
739 Notebook.prototype.focus_cell = function () {
736 var cell = this.get_selected_cell();
740 var cell = this.get_selected_cell();
737 if (cell === null) {return;} // No cell is selected
741 if (cell === null) {return;} // No cell is selected
738 cell.focus_cell();
742 cell.focus_cell();
739 };
743 };
740
744
741 // Cell movement
745 // Cell movement
742
746
743 /**
747 /**
744 * Move given (or selected) cell up and select it.
748 * Move given (or selected) cell up and select it.
745 *
749 *
746 * @method move_cell_up
750 * @method move_cell_up
747 * @param [index] {integer} cell index
751 * @param [index] {integer} cell index
748 * @return {Notebook} This notebook
752 * @return {Notebook} This notebook
749 **/
753 **/
750 Notebook.prototype.move_cell_up = function (index) {
754 Notebook.prototype.move_cell_up = function (index) {
751 var i = this.index_or_selected(index);
755 var i = this.index_or_selected(index);
752 if (this.is_valid_cell_index(i) && i > 0) {
756 if (this.is_valid_cell_index(i) && i > 0) {
753 var pivot = this.get_cell_element(i-1);
757 var pivot = this.get_cell_element(i-1);
754 var tomove = this.get_cell_element(i);
758 var tomove = this.get_cell_element(i);
755 if (pivot !== null && tomove !== null) {
759 if (pivot !== null && tomove !== null) {
756 tomove.detach();
760 tomove.detach();
757 pivot.before(tomove);
761 pivot.before(tomove);
758 this.select(i-1);
762 this.select(i-1);
759 var cell = this.get_selected_cell();
763 var cell = this.get_selected_cell();
760 cell.focus_cell();
764 cell.focus_cell();
761 }
765 }
762 this.set_dirty(true);
766 this.set_dirty(true);
763 }
767 }
764 return this;
768 return this;
765 };
769 };
766
770
767
771
768 /**
772 /**
769 * Move given (or selected) cell down and select it
773 * Move given (or selected) cell down and select it
770 *
774 *
771 * @method move_cell_down
775 * @method move_cell_down
772 * @param [index] {integer} cell index
776 * @param [index] {integer} cell index
773 * @return {Notebook} This notebook
777 * @return {Notebook} This notebook
774 **/
778 **/
775 Notebook.prototype.move_cell_down = function (index) {
779 Notebook.prototype.move_cell_down = function (index) {
776 var i = this.index_or_selected(index);
780 var i = this.index_or_selected(index);
777 if (this.is_valid_cell_index(i) && this.is_valid_cell_index(i+1)) {
781 if (this.is_valid_cell_index(i) && this.is_valid_cell_index(i+1)) {
778 var pivot = this.get_cell_element(i+1);
782 var pivot = this.get_cell_element(i+1);
779 var tomove = this.get_cell_element(i);
783 var tomove = this.get_cell_element(i);
780 if (pivot !== null && tomove !== null) {
784 if (pivot !== null && tomove !== null) {
781 tomove.detach();
785 tomove.detach();
782 pivot.after(tomove);
786 pivot.after(tomove);
783 this.select(i+1);
787 this.select(i+1);
784 var cell = this.get_selected_cell();
788 var cell = this.get_selected_cell();
785 cell.focus_cell();
789 cell.focus_cell();
786 }
790 }
787 }
791 }
788 this.set_dirty();
792 this.set_dirty();
789 return this;
793 return this;
790 };
794 };
791
795
792
796
793 // Insertion, deletion.
797 // Insertion, deletion.
794
798
795 /**
799 /**
796 * Delete a cell from the notebook.
800 * Delete a cell from the notebook.
797 *
801 *
798 * @method delete_cell
802 * @method delete_cell
799 * @param [index] A cell's numeric index
803 * @param [index] A cell's numeric index
800 * @return {Notebook} This notebook
804 * @return {Notebook} This notebook
801 */
805 */
802 Notebook.prototype.delete_cell = function (index) {
806 Notebook.prototype.delete_cell = function (index) {
803 var i = this.index_or_selected(index);
807 var i = this.index_or_selected(index);
804 var cell = this.get_cell(i);
808 var cell = this.get_cell(i);
805 if (!cell.is_deletable()) {
809 if (!cell.is_deletable()) {
806 return this;
810 return this;
807 }
811 }
808
812
809 this.undelete_backup = cell.toJSON();
813 this.undelete_backup = cell.toJSON();
810 $('#undelete_cell').removeClass('disabled');
814 $('#undelete_cell').removeClass('disabled');
811 if (this.is_valid_cell_index(i)) {
815 if (this.is_valid_cell_index(i)) {
812 var old_ncells = this.ncells();
816 var old_ncells = this.ncells();
813 var ce = this.get_cell_element(i);
817 var ce = this.get_cell_element(i);
814 ce.remove();
818 ce.remove();
815 if (i === 0) {
819 if (i === 0) {
816 // Always make sure we have at least one cell.
820 // Always make sure we have at least one cell.
817 if (old_ncells === 1) {
821 if (old_ncells === 1) {
818 this.insert_cell_below('code');
822 this.insert_cell_below('code');
819 }
823 }
820 this.select(0);
824 this.select(0);
821 this.undelete_index = 0;
825 this.undelete_index = 0;
822 this.undelete_below = false;
826 this.undelete_below = false;
823 } else if (i === old_ncells-1 && i !== 0) {
827 } else if (i === old_ncells-1 && i !== 0) {
824 this.select(i-1);
828 this.select(i-1);
825 this.undelete_index = i - 1;
829 this.undelete_index = i - 1;
826 this.undelete_below = true;
830 this.undelete_below = true;
827 } else {
831 } else {
828 this.select(i);
832 this.select(i);
829 this.undelete_index = i;
833 this.undelete_index = i;
830 this.undelete_below = false;
834 this.undelete_below = false;
831 }
835 }
832 this.events.trigger('delete.Cell', {'cell': cell, 'index': i});
836 this.events.trigger('delete.Cell', {'cell': cell, 'index': i});
833 this.set_dirty(true);
837 this.set_dirty(true);
834 }
838 }
835 return this;
839 return this;
836 };
840 };
837
841
838 /**
842 /**
839 * Restore the most recently deleted cell.
843 * Restore the most recently deleted cell.
840 *
844 *
841 * @method undelete
845 * @method undelete
842 */
846 */
843 Notebook.prototype.undelete_cell = function() {
847 Notebook.prototype.undelete_cell = function() {
844 if (this.undelete_backup !== null && this.undelete_index !== null) {
848 if (this.undelete_backup !== null && this.undelete_index !== null) {
845 var current_index = this.get_selected_index();
849 var current_index = this.get_selected_index();
846 if (this.undelete_index < current_index) {
850 if (this.undelete_index < current_index) {
847 current_index = current_index + 1;
851 current_index = current_index + 1;
848 }
852 }
849 if (this.undelete_index >= this.ncells()) {
853 if (this.undelete_index >= this.ncells()) {
850 this.select(this.ncells() - 1);
854 this.select(this.ncells() - 1);
851 }
855 }
852 else {
856 else {
853 this.select(this.undelete_index);
857 this.select(this.undelete_index);
854 }
858 }
855 var cell_data = this.undelete_backup;
859 var cell_data = this.undelete_backup;
856 var new_cell = null;
860 var new_cell = null;
857 if (this.undelete_below) {
861 if (this.undelete_below) {
858 new_cell = this.insert_cell_below(cell_data.cell_type);
862 new_cell = this.insert_cell_below(cell_data.cell_type);
859 } else {
863 } else {
860 new_cell = this.insert_cell_above(cell_data.cell_type);
864 new_cell = this.insert_cell_above(cell_data.cell_type);
861 }
865 }
862 new_cell.fromJSON(cell_data);
866 new_cell.fromJSON(cell_data);
863 if (this.undelete_below) {
867 if (this.undelete_below) {
864 this.select(current_index+1);
868 this.select(current_index+1);
865 } else {
869 } else {
866 this.select(current_index);
870 this.select(current_index);
867 }
871 }
868 this.undelete_backup = null;
872 this.undelete_backup = null;
869 this.undelete_index = null;
873 this.undelete_index = null;
870 }
874 }
871 $('#undelete_cell').addClass('disabled');
875 $('#undelete_cell').addClass('disabled');
872 };
876 };
873
877
874 /**
878 /**
875 * Insert a cell so that after insertion the cell is at given index.
879 * Insert a cell so that after insertion the cell is at given index.
876 *
880 *
877 * If cell type is not provided, it will default to the type of the
881 * If cell type is not provided, it will default to the type of the
878 * currently active cell.
882 * currently active cell.
879 *
883 *
880 * Similar to insert_above, but index parameter is mandatory
884 * Similar to insert_above, but index parameter is mandatory
881 *
885 *
882 * Index will be brought back into the accessible range [0,n]
886 * Index will be brought back into the accessible range [0,n]
883 *
887 *
884 * @method insert_cell_at_index
888 * @method insert_cell_at_index
885 * @param [type] {string} in ['code','markdown', 'raw'], defaults to 'code'
889 * @param [type] {string} in ['code','markdown', 'raw'], defaults to 'code'
886 * @param [index] {int} a valid index where to insert cell
890 * @param [index] {int} a valid index where to insert cell
887 *
891 *
888 * @return cell {cell|null} created cell or null
892 * @return cell {cell|null} created cell or null
889 **/
893 **/
890 Notebook.prototype.insert_cell_at_index = function(type, index){
894 Notebook.prototype.insert_cell_at_index = function(type, index){
891
895
892 var ncells = this.ncells();
896 var ncells = this.ncells();
893 index = Math.min(index, ncells);
897 index = Math.min(index, ncells);
894 index = Math.max(index, 0);
898 index = Math.max(index, 0);
895 var cell = null;
899 var cell = null;
896 type = type || this.default_cell_type;
900 type = type || this.default_cell_type;
897 if (type === 'above') {
901 if (type === 'above') {
898 if (index > 0) {
902 if (index > 0) {
899 type = this.get_cell(index-1).cell_type;
903 type = this.get_cell(index-1).cell_type;
900 } else {
904 } else {
901 type = 'code';
905 type = 'code';
902 }
906 }
903 } else if (type === 'below') {
907 } else if (type === 'below') {
904 if (index < ncells) {
908 if (index < ncells) {
905 type = this.get_cell(index).cell_type;
909 type = this.get_cell(index).cell_type;
906 } else {
910 } else {
907 type = 'code';
911 type = 'code';
908 }
912 }
909 } else if (type === 'selected') {
913 } else if (type === 'selected') {
910 type = this.get_selected_cell().cell_type;
914 type = this.get_selected_cell().cell_type;
911 }
915 }
912
916
913 if (ncells === 0 || this.is_valid_cell_index(index) || index === ncells) {
917 if (ncells === 0 || this.is_valid_cell_index(index) || index === ncells) {
914 var cell_options = {
918 var cell_options = {
915 events: this.events,
919 events: this.events,
916 config: this.config,
920 config: this.config,
917 keyboard_manager: this.keyboard_manager,
921 keyboard_manager: this.keyboard_manager,
918 notebook: this,
922 notebook: this,
919 tooltip: this.tooltip
923 tooltip: this.tooltip
920 };
924 };
921 switch(type) {
925 switch(type) {
922 case 'code':
926 case 'code':
923 cell = new codecell.CodeCell(this.kernel, cell_options);
927 cell = new codecell.CodeCell(this.kernel, cell_options);
924 cell.set_input_prompt();
928 cell.set_input_prompt();
925 break;
929 break;
926 case 'markdown':
930 case 'markdown':
927 cell = new textcell.MarkdownCell(cell_options);
931 cell = new textcell.MarkdownCell(cell_options);
928 break;
932 break;
929 case 'raw':
933 case 'raw':
930 cell = new textcell.RawCell(cell_options);
934 cell = new textcell.RawCell(cell_options);
931 break;
935 break;
932 default:
936 default:
933 console.log("Unrecognized cell type: ", type, cellmod);
937 console.log("Unrecognized cell type: ", type, cellmod);
934 cell = new cellmod.UnrecognizedCell(cell_options);
938 cell = new cellmod.UnrecognizedCell(cell_options);
935 }
939 }
936
940
937 if(this._insert_element_at_index(cell.element,index)) {
941 if(this._insert_element_at_index(cell.element,index)) {
938 cell.render();
942 cell.render();
939 this.events.trigger('create.Cell', {'cell': cell, 'index': index});
943 this.events.trigger('create.Cell', {'cell': cell, 'index': index});
940 cell.refresh();
944 cell.refresh();
941 // We used to select the cell after we refresh it, but there
945 // We used to select the cell after we refresh it, but there
942 // are now cases were this method is called where select is
946 // are now cases were this method is called where select is
943 // not appropriate. The selection logic should be handled by the
947 // not appropriate. The selection logic should be handled by the
944 // caller of the the top level insert_cell methods.
948 // caller of the the top level insert_cell methods.
945 this.set_dirty(true);
949 this.set_dirty(true);
946 }
950 }
947 }
951 }
948 return cell;
952 return cell;
949
953
950 };
954 };
951
955
952 /**
956 /**
953 * Insert an element at given cell index.
957 * Insert an element at given cell index.
954 *
958 *
955 * @method _insert_element_at_index
959 * @method _insert_element_at_index
956 * @param element {dom_element} a cell element
960 * @param element {dom_element} a cell element
957 * @param [index] {int} a valid index where to inser cell
961 * @param [index] {int} a valid index where to inser cell
958 * @private
962 * @private
959 *
963 *
960 * return true if everything whent fine.
964 * return true if everything whent fine.
961 **/
965 **/
962 Notebook.prototype._insert_element_at_index = function(element, index){
966 Notebook.prototype._insert_element_at_index = function(element, index){
963 if (element === undefined){
967 if (element === undefined){
964 return false;
968 return false;
965 }
969 }
966
970
967 var ncells = this.ncells();
971 var ncells = this.ncells();
968
972
969 if (ncells === 0) {
973 if (ncells === 0) {
970 // special case append if empty
974 // special case append if empty
971 this.element.find('div.end_space').before(element);
975 this.element.find('div.end_space').before(element);
972 } else if ( ncells === index ) {
976 } else if ( ncells === index ) {
973 // special case append it the end, but not empty
977 // special case append it the end, but not empty
974 this.get_cell_element(index-1).after(element);
978 this.get_cell_element(index-1).after(element);
975 } else if (this.is_valid_cell_index(index)) {
979 } else if (this.is_valid_cell_index(index)) {
976 // otherwise always somewhere to append to
980 // otherwise always somewhere to append to
977 this.get_cell_element(index).before(element);
981 this.get_cell_element(index).before(element);
978 } else {
982 } else {
979 return false;
983 return false;
980 }
984 }
981
985
982 if (this.undelete_index !== null && index <= this.undelete_index) {
986 if (this.undelete_index !== null && index <= this.undelete_index) {
983 this.undelete_index = this.undelete_index + 1;
987 this.undelete_index = this.undelete_index + 1;
984 this.set_dirty(true);
988 this.set_dirty(true);
985 }
989 }
986 return true;
990 return true;
987 };
991 };
988
992
989 /**
993 /**
990 * Insert a cell of given type above given index, or at top
994 * Insert a cell of given type above given index, or at top
991 * of notebook if index smaller than 0.
995 * of notebook if index smaller than 0.
992 *
996 *
993 * default index value is the one of currently selected cell
997 * default index value is the one of currently selected cell
994 *
998 *
995 * @method insert_cell_above
999 * @method insert_cell_above
996 * @param [type] {string} cell type
1000 * @param [type] {string} cell type
997 * @param [index] {integer}
1001 * @param [index] {integer}
998 *
1002 *
999 * @return handle to created cell or null
1003 * @return handle to created cell or null
1000 **/
1004 **/
1001 Notebook.prototype.insert_cell_above = function (type, index) {
1005 Notebook.prototype.insert_cell_above = function (type, index) {
1002 index = this.index_or_selected(index);
1006 index = this.index_or_selected(index);
1003 return this.insert_cell_at_index(type, index);
1007 return this.insert_cell_at_index(type, index);
1004 };
1008 };
1005
1009
1006 /**
1010 /**
1007 * Insert a cell of given type below given index, or at bottom
1011 * Insert a cell of given type below given index, or at bottom
1008 * of notebook if index greater than number of cells
1012 * of notebook if index greater than number of cells
1009 *
1013 *
1010 * default index value is the one of currently selected cell
1014 * default index value is the one of currently selected cell
1011 *
1015 *
1012 * @method insert_cell_below
1016 * @method insert_cell_below
1013 * @param [type] {string} cell type
1017 * @param [type] {string} cell type
1014 * @param [index] {integer}
1018 * @param [index] {integer}
1015 *
1019 *
1016 * @return handle to created cell or null
1020 * @return handle to created cell or null
1017 *
1021 *
1018 **/
1022 **/
1019 Notebook.prototype.insert_cell_below = function (type, index) {
1023 Notebook.prototype.insert_cell_below = function (type, index) {
1020 index = this.index_or_selected(index);
1024 index = this.index_or_selected(index);
1021 return this.insert_cell_at_index(type, index+1);
1025 return this.insert_cell_at_index(type, index+1);
1022 };
1026 };
1023
1027
1024
1028
1025 /**
1029 /**
1026 * Insert cell at end of notebook
1030 * Insert cell at end of notebook
1027 *
1031 *
1028 * @method insert_cell_at_bottom
1032 * @method insert_cell_at_bottom
1029 * @param {String} type cell type
1033 * @param {String} type cell type
1030 *
1034 *
1031 * @return the added cell; or null
1035 * @return the added cell; or null
1032 **/
1036 **/
1033 Notebook.prototype.insert_cell_at_bottom = function (type){
1037 Notebook.prototype.insert_cell_at_bottom = function (type){
1034 var len = this.ncells();
1038 var len = this.ncells();
1035 return this.insert_cell_below(type,len-1);
1039 return this.insert_cell_below(type,len-1);
1036 };
1040 };
1037
1041
1038 /**
1042 /**
1039 * Turn a cell into a code cell.
1043 * Turn a cell into a code cell.
1040 *
1044 *
1041 * @method to_code
1045 * @method to_code
1042 * @param {Number} [index] A cell's index
1046 * @param {Number} [index] A cell's index
1043 */
1047 */
1044 Notebook.prototype.to_code = function (index) {
1048 Notebook.prototype.to_code = function (index) {
1045 var i = this.index_or_selected(index);
1049 var i = this.index_or_selected(index);
1046 if (this.is_valid_cell_index(i)) {
1050 if (this.is_valid_cell_index(i)) {
1047 var source_cell = this.get_cell(i);
1051 var source_cell = this.get_cell(i);
1048 if (!(source_cell instanceof codecell.CodeCell)) {
1052 if (!(source_cell instanceof codecell.CodeCell)) {
1049 var target_cell = this.insert_cell_below('code',i);
1053 var target_cell = this.insert_cell_below('code',i);
1050 var text = source_cell.get_text();
1054 var text = source_cell.get_text();
1051 if (text === source_cell.placeholder) {
1055 if (text === source_cell.placeholder) {
1052 text = '';
1056 text = '';
1053 }
1057 }
1054 //metadata
1058 //metadata
1055 target_cell.metadata = source_cell.metadata;
1059 target_cell.metadata = source_cell.metadata;
1056
1060
1057 target_cell.set_text(text);
1061 target_cell.set_text(text);
1058 // make this value the starting point, so that we can only undo
1062 // make this value the starting point, so that we can only undo
1059 // to this state, instead of a blank cell
1063 // to this state, instead of a blank cell
1060 target_cell.code_mirror.clearHistory();
1064 target_cell.code_mirror.clearHistory();
1061 source_cell.element.remove();
1065 source_cell.element.remove();
1062 this.select(i);
1066 this.select(i);
1063 var cursor = source_cell.code_mirror.getCursor();
1067 var cursor = source_cell.code_mirror.getCursor();
1064 target_cell.code_mirror.setCursor(cursor);
1068 target_cell.code_mirror.setCursor(cursor);
1065 this.set_dirty(true);
1069 this.set_dirty(true);
1066 }
1070 }
1067 }
1071 }
1068 };
1072 };
1069
1073
1070 /**
1074 /**
1071 * Turn a cell into a Markdown cell.
1075 * Turn a cell into a Markdown cell.
1072 *
1076 *
1073 * @method to_markdown
1077 * @method to_markdown
1074 * @param {Number} [index] A cell's index
1078 * @param {Number} [index] A cell's index
1075 */
1079 */
1076 Notebook.prototype.to_markdown = function (index) {
1080 Notebook.prototype.to_markdown = function (index) {
1077 var i = this.index_or_selected(index);
1081 var i = this.index_or_selected(index);
1078 if (this.is_valid_cell_index(i)) {
1082 if (this.is_valid_cell_index(i)) {
1079 var source_cell = this.get_cell(i);
1083 var source_cell = this.get_cell(i);
1080
1084
1081 if (!(source_cell instanceof textcell.MarkdownCell)) {
1085 if (!(source_cell instanceof textcell.MarkdownCell)) {
1082 var target_cell = this.insert_cell_below('markdown',i);
1086 var target_cell = this.insert_cell_below('markdown',i);
1083 var text = source_cell.get_text();
1087 var text = source_cell.get_text();
1084
1088
1085 if (text === source_cell.placeholder) {
1089 if (text === source_cell.placeholder) {
1086 text = '';
1090 text = '';
1087 }
1091 }
1088 // metadata
1092 // metadata
1089 target_cell.metadata = source_cell.metadata;
1093 target_cell.metadata = source_cell.metadata;
1090 // We must show the editor before setting its contents
1094 // We must show the editor before setting its contents
1091 target_cell.unrender();
1095 target_cell.unrender();
1092 target_cell.set_text(text);
1096 target_cell.set_text(text);
1093 // make this value the starting point, so that we can only undo
1097 // make this value the starting point, so that we can only undo
1094 // to this state, instead of a blank cell
1098 // to this state, instead of a blank cell
1095 target_cell.code_mirror.clearHistory();
1099 target_cell.code_mirror.clearHistory();
1096 source_cell.element.remove();
1100 source_cell.element.remove();
1097 this.select(i);
1101 this.select(i);
1098 if ((source_cell instanceof textcell.TextCell) && source_cell.rendered) {
1102 if ((source_cell instanceof textcell.TextCell) && source_cell.rendered) {
1099 target_cell.render();
1103 target_cell.render();
1100 }
1104 }
1101 var cursor = source_cell.code_mirror.getCursor();
1105 var cursor = source_cell.code_mirror.getCursor();
1102 target_cell.code_mirror.setCursor(cursor);
1106 target_cell.code_mirror.setCursor(cursor);
1103 this.set_dirty(true);
1107 this.set_dirty(true);
1104 }
1108 }
1105 }
1109 }
1106 };
1110 };
1107
1111
1108 /**
1112 /**
1109 * Turn a cell into a raw text cell.
1113 * Turn a cell into a raw text cell.
1110 *
1114 *
1111 * @method to_raw
1115 * @method to_raw
1112 * @param {Number} [index] A cell's index
1116 * @param {Number} [index] A cell's index
1113 */
1117 */
1114 Notebook.prototype.to_raw = function (index) {
1118 Notebook.prototype.to_raw = function (index) {
1115 var i = this.index_or_selected(index);
1119 var i = this.index_or_selected(index);
1116 if (this.is_valid_cell_index(i)) {
1120 if (this.is_valid_cell_index(i)) {
1117 var target_cell = null;
1121 var target_cell = null;
1118 var source_cell = this.get_cell(i);
1122 var source_cell = this.get_cell(i);
1119
1123
1120 if (!(source_cell instanceof textcell.RawCell)) {
1124 if (!(source_cell instanceof textcell.RawCell)) {
1121 target_cell = this.insert_cell_below('raw',i);
1125 target_cell = this.insert_cell_below('raw',i);
1122 var text = source_cell.get_text();
1126 var text = source_cell.get_text();
1123 if (text === source_cell.placeholder) {
1127 if (text === source_cell.placeholder) {
1124 text = '';
1128 text = '';
1125 }
1129 }
1126 //metadata
1130 //metadata
1127 target_cell.metadata = source_cell.metadata;
1131 target_cell.metadata = source_cell.metadata;
1128 // We must show the editor before setting its contents
1132 // We must show the editor before setting its contents
1129 target_cell.unrender();
1133 target_cell.unrender();
1130 target_cell.set_text(text);
1134 target_cell.set_text(text);
1131 // make this value the starting point, so that we can only undo
1135 // make this value the starting point, so that we can only undo
1132 // to this state, instead of a blank cell
1136 // to this state, instead of a blank cell
1133 target_cell.code_mirror.clearHistory();
1137 target_cell.code_mirror.clearHistory();
1134 source_cell.element.remove();
1138 source_cell.element.remove();
1135 this.select(i);
1139 this.select(i);
1136 var cursor = source_cell.code_mirror.getCursor();
1140 var cursor = source_cell.code_mirror.getCursor();
1137 target_cell.code_mirror.setCursor(cursor);
1141 target_cell.code_mirror.setCursor(cursor);
1138 this.set_dirty(true);
1142 this.set_dirty(true);
1139 }
1143 }
1140 }
1144 }
1141 };
1145 };
1142
1146
1143 Notebook.prototype._warn_heading = function () {
1147 Notebook.prototype._warn_heading = function () {
1144 // warn about heading cells being removed
1148 /**
1149 * warn about heading cells being removed
1150 */
1145 dialog.modal({
1151 dialog.modal({
1146 notebook: this,
1152 notebook: this,
1147 keyboard_manager: this.keyboard_manager,
1153 keyboard_manager: this.keyboard_manager,
1148 title : "Use markdown headings",
1154 title : "Use markdown headings",
1149 body : $("<p/>").text(
1155 body : $("<p/>").text(
1150 'IPython no longer uses special heading cells. ' +
1156 'IPython no longer uses special heading cells. ' +
1151 'Instead, write your headings in Markdown cells using # characters:'
1157 'Instead, write your headings in Markdown cells using # characters:'
1152 ).append($('<pre/>').text(
1158 ).append($('<pre/>').text(
1153 '## This is a level 2 heading'
1159 '## This is a level 2 heading'
1154 )),
1160 )),
1155 buttons : {
1161 buttons : {
1156 "OK" : {}
1162 "OK" : {}
1157 }
1163 }
1158 });
1164 });
1159 };
1165 };
1160
1166
1161 /**
1167 /**
1162 * Turn a cell into a markdown cell with a heading.
1168 * Turn a cell into a markdown cell with a heading.
1163 *
1169 *
1164 * @method to_heading
1170 * @method to_heading
1165 * @param {Number} [index] A cell's index
1171 * @param {Number} [index] A cell's index
1166 * @param {Number} [level] A heading level (e.g., 1 for h1)
1172 * @param {Number} [level] A heading level (e.g., 1 for h1)
1167 */
1173 */
1168 Notebook.prototype.to_heading = function (index, level) {
1174 Notebook.prototype.to_heading = function (index, level) {
1169 this.to_markdown(index);
1175 this.to_markdown(index);
1170 level = level || 1;
1176 level = level || 1;
1171 var i = this.index_or_selected(index);
1177 var i = this.index_or_selected(index);
1172 if (this.is_valid_cell_index(i)) {
1178 if (this.is_valid_cell_index(i)) {
1173 var cell = this.get_cell(i);
1179 var cell = this.get_cell(i);
1174 cell.set_heading_level(level);
1180 cell.set_heading_level(level);
1175 this.set_dirty(true);
1181 this.set_dirty(true);
1176 }
1182 }
1177 };
1183 };
1178
1184
1179
1185
1180 // Cut/Copy/Paste
1186 // Cut/Copy/Paste
1181
1187
1182 /**
1188 /**
1183 * Enable UI elements for pasting cells.
1189 * Enable UI elements for pasting cells.
1184 *
1190 *
1185 * @method enable_paste
1191 * @method enable_paste
1186 */
1192 */
1187 Notebook.prototype.enable_paste = function () {
1193 Notebook.prototype.enable_paste = function () {
1188 var that = this;
1194 var that = this;
1189 if (!this.paste_enabled) {
1195 if (!this.paste_enabled) {
1190 $('#paste_cell_replace').removeClass('disabled')
1196 $('#paste_cell_replace').removeClass('disabled')
1191 .on('click', function () {that.paste_cell_replace();});
1197 .on('click', function () {that.paste_cell_replace();});
1192 $('#paste_cell_above').removeClass('disabled')
1198 $('#paste_cell_above').removeClass('disabled')
1193 .on('click', function () {that.paste_cell_above();});
1199 .on('click', function () {that.paste_cell_above();});
1194 $('#paste_cell_below').removeClass('disabled')
1200 $('#paste_cell_below').removeClass('disabled')
1195 .on('click', function () {that.paste_cell_below();});
1201 .on('click', function () {that.paste_cell_below();});
1196 this.paste_enabled = true;
1202 this.paste_enabled = true;
1197 }
1203 }
1198 };
1204 };
1199
1205
1200 /**
1206 /**
1201 * Disable UI elements for pasting cells.
1207 * Disable UI elements for pasting cells.
1202 *
1208 *
1203 * @method disable_paste
1209 * @method disable_paste
1204 */
1210 */
1205 Notebook.prototype.disable_paste = function () {
1211 Notebook.prototype.disable_paste = function () {
1206 if (this.paste_enabled) {
1212 if (this.paste_enabled) {
1207 $('#paste_cell_replace').addClass('disabled').off('click');
1213 $('#paste_cell_replace').addClass('disabled').off('click');
1208 $('#paste_cell_above').addClass('disabled').off('click');
1214 $('#paste_cell_above').addClass('disabled').off('click');
1209 $('#paste_cell_below').addClass('disabled').off('click');
1215 $('#paste_cell_below').addClass('disabled').off('click');
1210 this.paste_enabled = false;
1216 this.paste_enabled = false;
1211 }
1217 }
1212 };
1218 };
1213
1219
1214 /**
1220 /**
1215 * Cut a cell.
1221 * Cut a cell.
1216 *
1222 *
1217 * @method cut_cell
1223 * @method cut_cell
1218 */
1224 */
1219 Notebook.prototype.cut_cell = function () {
1225 Notebook.prototype.cut_cell = function () {
1220 this.copy_cell();
1226 this.copy_cell();
1221 this.delete_cell();
1227 this.delete_cell();
1222 };
1228 };
1223
1229
1224 /**
1230 /**
1225 * Copy a cell.
1231 * Copy a cell.
1226 *
1232 *
1227 * @method copy_cell
1233 * @method copy_cell
1228 */
1234 */
1229 Notebook.prototype.copy_cell = function () {
1235 Notebook.prototype.copy_cell = function () {
1230 var cell = this.get_selected_cell();
1236 var cell = this.get_selected_cell();
1231 this.clipboard = cell.toJSON();
1237 this.clipboard = cell.toJSON();
1232 // remove undeletable status from the copied cell
1238 // remove undeletable status from the copied cell
1233 if (this.clipboard.metadata.deletable !== undefined) {
1239 if (this.clipboard.metadata.deletable !== undefined) {
1234 delete this.clipboard.metadata.deletable;
1240 delete this.clipboard.metadata.deletable;
1235 }
1241 }
1236 this.enable_paste();
1242 this.enable_paste();
1237 };
1243 };
1238
1244
1239 /**
1245 /**
1240 * Replace the selected cell with a cell in the clipboard.
1246 * Replace the selected cell with a cell in the clipboard.
1241 *
1247 *
1242 * @method paste_cell_replace
1248 * @method paste_cell_replace
1243 */
1249 */
1244 Notebook.prototype.paste_cell_replace = function () {
1250 Notebook.prototype.paste_cell_replace = function () {
1245 if (this.clipboard !== null && this.paste_enabled) {
1251 if (this.clipboard !== null && this.paste_enabled) {
1246 var cell_data = this.clipboard;
1252 var cell_data = this.clipboard;
1247 var new_cell = this.insert_cell_above(cell_data.cell_type);
1253 var new_cell = this.insert_cell_above(cell_data.cell_type);
1248 new_cell.fromJSON(cell_data);
1254 new_cell.fromJSON(cell_data);
1249 var old_cell = this.get_next_cell(new_cell);
1255 var old_cell = this.get_next_cell(new_cell);
1250 this.delete_cell(this.find_cell_index(old_cell));
1256 this.delete_cell(this.find_cell_index(old_cell));
1251 this.select(this.find_cell_index(new_cell));
1257 this.select(this.find_cell_index(new_cell));
1252 }
1258 }
1253 };
1259 };
1254
1260
1255 /**
1261 /**
1256 * Paste a cell from the clipboard above the selected cell.
1262 * Paste a cell from the clipboard above the selected cell.
1257 *
1263 *
1258 * @method paste_cell_above
1264 * @method paste_cell_above
1259 */
1265 */
1260 Notebook.prototype.paste_cell_above = function () {
1266 Notebook.prototype.paste_cell_above = function () {
1261 if (this.clipboard !== null && this.paste_enabled) {
1267 if (this.clipboard !== null && this.paste_enabled) {
1262 var cell_data = this.clipboard;
1268 var cell_data = this.clipboard;
1263 var new_cell = this.insert_cell_above(cell_data.cell_type);
1269 var new_cell = this.insert_cell_above(cell_data.cell_type);
1264 new_cell.fromJSON(cell_data);
1270 new_cell.fromJSON(cell_data);
1265 new_cell.focus_cell();
1271 new_cell.focus_cell();
1266 }
1272 }
1267 };
1273 };
1268
1274
1269 /**
1275 /**
1270 * Paste a cell from the clipboard below the selected cell.
1276 * Paste a cell from the clipboard below the selected cell.
1271 *
1277 *
1272 * @method paste_cell_below
1278 * @method paste_cell_below
1273 */
1279 */
1274 Notebook.prototype.paste_cell_below = function () {
1280 Notebook.prototype.paste_cell_below = function () {
1275 if (this.clipboard !== null && this.paste_enabled) {
1281 if (this.clipboard !== null && this.paste_enabled) {
1276 var cell_data = this.clipboard;
1282 var cell_data = this.clipboard;
1277 var new_cell = this.insert_cell_below(cell_data.cell_type);
1283 var new_cell = this.insert_cell_below(cell_data.cell_type);
1278 new_cell.fromJSON(cell_data);
1284 new_cell.fromJSON(cell_data);
1279 new_cell.focus_cell();
1285 new_cell.focus_cell();
1280 }
1286 }
1281 };
1287 };
1282
1288
1283 // Split/merge
1289 // Split/merge
1284
1290
1285 /**
1291 /**
1286 * Split the selected cell into two, at the cursor.
1292 * Split the selected cell into two, at the cursor.
1287 *
1293 *
1288 * @method split_cell
1294 * @method split_cell
1289 */
1295 */
1290 Notebook.prototype.split_cell = function () {
1296 Notebook.prototype.split_cell = function () {
1291 var cell = this.get_selected_cell();
1297 var cell = this.get_selected_cell();
1292 if (cell.is_splittable()) {
1298 if (cell.is_splittable()) {
1293 var texta = cell.get_pre_cursor();
1299 var texta = cell.get_pre_cursor();
1294 var textb = cell.get_post_cursor();
1300 var textb = cell.get_post_cursor();
1295 cell.set_text(textb);
1301 cell.set_text(textb);
1296 var new_cell = this.insert_cell_above(cell.cell_type);
1302 var new_cell = this.insert_cell_above(cell.cell_type);
1297 // Unrender the new cell so we can call set_text.
1303 // Unrender the new cell so we can call set_text.
1298 new_cell.unrender();
1304 new_cell.unrender();
1299 new_cell.set_text(texta);
1305 new_cell.set_text(texta);
1300 }
1306 }
1301 };
1307 };
1302
1308
1303 /**
1309 /**
1304 * Combine the selected cell into the cell above it.
1310 * Combine the selected cell into the cell above it.
1305 *
1311 *
1306 * @method merge_cell_above
1312 * @method merge_cell_above
1307 */
1313 */
1308 Notebook.prototype.merge_cell_above = function () {
1314 Notebook.prototype.merge_cell_above = function () {
1309 var index = this.get_selected_index();
1315 var index = this.get_selected_index();
1310 var cell = this.get_cell(index);
1316 var cell = this.get_cell(index);
1311 var render = cell.rendered;
1317 var render = cell.rendered;
1312 if (!cell.is_mergeable()) {
1318 if (!cell.is_mergeable()) {
1313 return;
1319 return;
1314 }
1320 }
1315 if (index > 0) {
1321 if (index > 0) {
1316 var upper_cell = this.get_cell(index-1);
1322 var upper_cell = this.get_cell(index-1);
1317 if (!upper_cell.is_mergeable()) {
1323 if (!upper_cell.is_mergeable()) {
1318 return;
1324 return;
1319 }
1325 }
1320 var upper_text = upper_cell.get_text();
1326 var upper_text = upper_cell.get_text();
1321 var text = cell.get_text();
1327 var text = cell.get_text();
1322 if (cell instanceof codecell.CodeCell) {
1328 if (cell instanceof codecell.CodeCell) {
1323 cell.set_text(upper_text+'\n'+text);
1329 cell.set_text(upper_text+'\n'+text);
1324 } else {
1330 } else {
1325 cell.unrender(); // Must unrender before we set_text.
1331 cell.unrender(); // Must unrender before we set_text.
1326 cell.set_text(upper_text+'\n\n'+text);
1332 cell.set_text(upper_text+'\n\n'+text);
1327 if (render) {
1333 if (render) {
1328 // The rendered state of the final cell should match
1334 // The rendered state of the final cell should match
1329 // that of the original selected cell;
1335 // that of the original selected cell;
1330 cell.render();
1336 cell.render();
1331 }
1337 }
1332 }
1338 }
1333 this.delete_cell(index-1);
1339 this.delete_cell(index-1);
1334 this.select(this.find_cell_index(cell));
1340 this.select(this.find_cell_index(cell));
1335 }
1341 }
1336 };
1342 };
1337
1343
1338 /**
1344 /**
1339 * Combine the selected cell into the cell below it.
1345 * Combine the selected cell into the cell below it.
1340 *
1346 *
1341 * @method merge_cell_below
1347 * @method merge_cell_below
1342 */
1348 */
1343 Notebook.prototype.merge_cell_below = function () {
1349 Notebook.prototype.merge_cell_below = function () {
1344 var index = this.get_selected_index();
1350 var index = this.get_selected_index();
1345 var cell = this.get_cell(index);
1351 var cell = this.get_cell(index);
1346 var render = cell.rendered;
1352 var render = cell.rendered;
1347 if (!cell.is_mergeable()) {
1353 if (!cell.is_mergeable()) {
1348 return;
1354 return;
1349 }
1355 }
1350 if (index < this.ncells()-1) {
1356 if (index < this.ncells()-1) {
1351 var lower_cell = this.get_cell(index+1);
1357 var lower_cell = this.get_cell(index+1);
1352 if (!lower_cell.is_mergeable()) {
1358 if (!lower_cell.is_mergeable()) {
1353 return;
1359 return;
1354 }
1360 }
1355 var lower_text = lower_cell.get_text();
1361 var lower_text = lower_cell.get_text();
1356 var text = cell.get_text();
1362 var text = cell.get_text();
1357 if (cell instanceof codecell.CodeCell) {
1363 if (cell instanceof codecell.CodeCell) {
1358 cell.set_text(text+'\n'+lower_text);
1364 cell.set_text(text+'\n'+lower_text);
1359 } else {
1365 } else {
1360 cell.unrender(); // Must unrender before we set_text.
1366 cell.unrender(); // Must unrender before we set_text.
1361 cell.set_text(text+'\n\n'+lower_text);
1367 cell.set_text(text+'\n\n'+lower_text);
1362 if (render) {
1368 if (render) {
1363 // The rendered state of the final cell should match
1369 // The rendered state of the final cell should match
1364 // that of the original selected cell;
1370 // that of the original selected cell;
1365 cell.render();
1371 cell.render();
1366 }
1372 }
1367 }
1373 }
1368 this.delete_cell(index+1);
1374 this.delete_cell(index+1);
1369 this.select(this.find_cell_index(cell));
1375 this.select(this.find_cell_index(cell));
1370 }
1376 }
1371 };
1377 };
1372
1378
1373
1379
1374 // Cell collapsing and output clearing
1380 // Cell collapsing and output clearing
1375
1381
1376 /**
1382 /**
1377 * Hide a cell's output.
1383 * Hide a cell's output.
1378 *
1384 *
1379 * @method collapse_output
1385 * @method collapse_output
1380 * @param {Number} index A cell's numeric index
1386 * @param {Number} index A cell's numeric index
1381 */
1387 */
1382 Notebook.prototype.collapse_output = function (index) {
1388 Notebook.prototype.collapse_output = function (index) {
1383 var i = this.index_or_selected(index);
1389 var i = this.index_or_selected(index);
1384 var cell = this.get_cell(i);
1390 var cell = this.get_cell(i);
1385 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1391 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1386 cell.collapse_output();
1392 cell.collapse_output();
1387 this.set_dirty(true);
1393 this.set_dirty(true);
1388 }
1394 }
1389 };
1395 };
1390
1396
1391 /**
1397 /**
1392 * Hide each code cell's output area.
1398 * Hide each code cell's output area.
1393 *
1399 *
1394 * @method collapse_all_output
1400 * @method collapse_all_output
1395 */
1401 */
1396 Notebook.prototype.collapse_all_output = function () {
1402 Notebook.prototype.collapse_all_output = function () {
1397 this.get_cells().map(function (cell, i) {
1403 this.get_cells().map(function (cell, i) {
1398 if (cell instanceof codecell.CodeCell) {
1404 if (cell instanceof codecell.CodeCell) {
1399 cell.collapse_output();
1405 cell.collapse_output();
1400 }
1406 }
1401 });
1407 });
1402 // this should not be set if the `collapse` key is removed from nbformat
1408 // this should not be set if the `collapse` key is removed from nbformat
1403 this.set_dirty(true);
1409 this.set_dirty(true);
1404 };
1410 };
1405
1411
1406 /**
1412 /**
1407 * Show a cell's output.
1413 * Show a cell's output.
1408 *
1414 *
1409 * @method expand_output
1415 * @method expand_output
1410 * @param {Number} index A cell's numeric index
1416 * @param {Number} index A cell's numeric index
1411 */
1417 */
1412 Notebook.prototype.expand_output = function (index) {
1418 Notebook.prototype.expand_output = function (index) {
1413 var i = this.index_or_selected(index);
1419 var i = this.index_or_selected(index);
1414 var cell = this.get_cell(i);
1420 var cell = this.get_cell(i);
1415 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1421 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1416 cell.expand_output();
1422 cell.expand_output();
1417 this.set_dirty(true);
1423 this.set_dirty(true);
1418 }
1424 }
1419 };
1425 };
1420
1426
1421 /**
1427 /**
1422 * Expand each code cell's output area, and remove scrollbars.
1428 * Expand each code cell's output area, and remove scrollbars.
1423 *
1429 *
1424 * @method expand_all_output
1430 * @method expand_all_output
1425 */
1431 */
1426 Notebook.prototype.expand_all_output = function () {
1432 Notebook.prototype.expand_all_output = function () {
1427 this.get_cells().map(function (cell, i) {
1433 this.get_cells().map(function (cell, i) {
1428 if (cell instanceof codecell.CodeCell) {
1434 if (cell instanceof codecell.CodeCell) {
1429 cell.expand_output();
1435 cell.expand_output();
1430 }
1436 }
1431 });
1437 });
1432 // this should not be set if the `collapse` key is removed from nbformat
1438 // this should not be set if the `collapse` key is removed from nbformat
1433 this.set_dirty(true);
1439 this.set_dirty(true);
1434 };
1440 };
1435
1441
1436 /**
1442 /**
1437 * Clear the selected CodeCell's output area.
1443 * Clear the selected CodeCell's output area.
1438 *
1444 *
1439 * @method clear_output
1445 * @method clear_output
1440 * @param {Number} index A cell's numeric index
1446 * @param {Number} index A cell's numeric index
1441 */
1447 */
1442 Notebook.prototype.clear_output = function (index) {
1448 Notebook.prototype.clear_output = function (index) {
1443 var i = this.index_or_selected(index);
1449 var i = this.index_or_selected(index);
1444 var cell = this.get_cell(i);
1450 var cell = this.get_cell(i);
1445 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1451 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1446 cell.clear_output();
1452 cell.clear_output();
1447 this.set_dirty(true);
1453 this.set_dirty(true);
1448 }
1454 }
1449 };
1455 };
1450
1456
1451 /**
1457 /**
1452 * Clear each code cell's output area.
1458 * Clear each code cell's output area.
1453 *
1459 *
1454 * @method clear_all_output
1460 * @method clear_all_output
1455 */
1461 */
1456 Notebook.prototype.clear_all_output = function () {
1462 Notebook.prototype.clear_all_output = function () {
1457 this.get_cells().map(function (cell, i) {
1463 this.get_cells().map(function (cell, i) {
1458 if (cell instanceof codecell.CodeCell) {
1464 if (cell instanceof codecell.CodeCell) {
1459 cell.clear_output();
1465 cell.clear_output();
1460 }
1466 }
1461 });
1467 });
1462 this.set_dirty(true);
1468 this.set_dirty(true);
1463 };
1469 };
1464
1470
1465 /**
1471 /**
1466 * Scroll the selected CodeCell's output area.
1472 * Scroll the selected CodeCell's output area.
1467 *
1473 *
1468 * @method scroll_output
1474 * @method scroll_output
1469 * @param {Number} index A cell's numeric index
1475 * @param {Number} index A cell's numeric index
1470 */
1476 */
1471 Notebook.prototype.scroll_output = function (index) {
1477 Notebook.prototype.scroll_output = function (index) {
1472 var i = this.index_or_selected(index);
1478 var i = this.index_or_selected(index);
1473 var cell = this.get_cell(i);
1479 var cell = this.get_cell(i);
1474 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1480 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1475 cell.scroll_output();
1481 cell.scroll_output();
1476 this.set_dirty(true);
1482 this.set_dirty(true);
1477 }
1483 }
1478 };
1484 };
1479
1485
1480 /**
1486 /**
1481 * Expand each code cell's output area, and add a scrollbar for long output.
1487 * Expand each code cell's output area, and add a scrollbar for long output.
1482 *
1488 *
1483 * @method scroll_all_output
1489 * @method scroll_all_output
1484 */
1490 */
1485 Notebook.prototype.scroll_all_output = function () {
1491 Notebook.prototype.scroll_all_output = function () {
1486 this.get_cells().map(function (cell, i) {
1492 this.get_cells().map(function (cell, i) {
1487 if (cell instanceof codecell.CodeCell) {
1493 if (cell instanceof codecell.CodeCell) {
1488 cell.scroll_output();
1494 cell.scroll_output();
1489 }
1495 }
1490 });
1496 });
1491 // this should not be set if the `collapse` key is removed from nbformat
1497 // this should not be set if the `collapse` key is removed from nbformat
1492 this.set_dirty(true);
1498 this.set_dirty(true);
1493 };
1499 };
1494
1500
1495 /** Toggle whether a cell's output is collapsed or expanded.
1501 /** Toggle whether a cell's output is collapsed or expanded.
1496 *
1502 *
1497 * @method toggle_output
1503 * @method toggle_output
1498 * @param {Number} index A cell's numeric index
1504 * @param {Number} index A cell's numeric index
1499 */
1505 */
1500 Notebook.prototype.toggle_output = function (index) {
1506 Notebook.prototype.toggle_output = function (index) {
1501 var i = this.index_or_selected(index);
1507 var i = this.index_or_selected(index);
1502 var cell = this.get_cell(i);
1508 var cell = this.get_cell(i);
1503 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1509 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1504 cell.toggle_output();
1510 cell.toggle_output();
1505 this.set_dirty(true);
1511 this.set_dirty(true);
1506 }
1512 }
1507 };
1513 };
1508
1514
1509 /**
1515 /**
1510 * Hide/show the output of all cells.
1516 * Hide/show the output of all cells.
1511 *
1517 *
1512 * @method toggle_all_output
1518 * @method toggle_all_output
1513 */
1519 */
1514 Notebook.prototype.toggle_all_output = function () {
1520 Notebook.prototype.toggle_all_output = function () {
1515 this.get_cells().map(function (cell, i) {
1521 this.get_cells().map(function (cell, i) {
1516 if (cell instanceof codecell.CodeCell) {
1522 if (cell instanceof codecell.CodeCell) {
1517 cell.toggle_output();
1523 cell.toggle_output();
1518 }
1524 }
1519 });
1525 });
1520 // this should not be set if the `collapse` key is removed from nbformat
1526 // this should not be set if the `collapse` key is removed from nbformat
1521 this.set_dirty(true);
1527 this.set_dirty(true);
1522 };
1528 };
1523
1529
1524 /**
1530 /**
1525 * Toggle a scrollbar for long cell outputs.
1531 * Toggle a scrollbar for long cell outputs.
1526 *
1532 *
1527 * @method toggle_output_scroll
1533 * @method toggle_output_scroll
1528 * @param {Number} index A cell's numeric index
1534 * @param {Number} index A cell's numeric index
1529 */
1535 */
1530 Notebook.prototype.toggle_output_scroll = function (index) {
1536 Notebook.prototype.toggle_output_scroll = function (index) {
1531 var i = this.index_or_selected(index);
1537 var i = this.index_or_selected(index);
1532 var cell = this.get_cell(i);
1538 var cell = this.get_cell(i);
1533 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1539 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1534 cell.toggle_output_scroll();
1540 cell.toggle_output_scroll();
1535 this.set_dirty(true);
1541 this.set_dirty(true);
1536 }
1542 }
1537 };
1543 };
1538
1544
1539 /**
1545 /**
1540 * Toggle the scrolling of long output on all cells.
1546 * Toggle the scrolling of long output on all cells.
1541 *
1547 *
1542 * @method toggle_all_output_scrolling
1548 * @method toggle_all_output_scrolling
1543 */
1549 */
1544 Notebook.prototype.toggle_all_output_scroll = function () {
1550 Notebook.prototype.toggle_all_output_scroll = function () {
1545 this.get_cells().map(function (cell, i) {
1551 this.get_cells().map(function (cell, i) {
1546 if (cell instanceof codecell.CodeCell) {
1552 if (cell instanceof codecell.CodeCell) {
1547 cell.toggle_output_scroll();
1553 cell.toggle_output_scroll();
1548 }
1554 }
1549 });
1555 });
1550 // this should not be set if the `collapse` key is removed from nbformat
1556 // this should not be set if the `collapse` key is removed from nbformat
1551 this.set_dirty(true);
1557 this.set_dirty(true);
1552 };
1558 };
1553
1559
1554 // Other cell functions: line numbers, ...
1560 // Other cell functions: line numbers, ...
1555
1561
1556 /**
1562 /**
1557 * Toggle line numbers in the selected cell's input area.
1563 * Toggle line numbers in the selected cell's input area.
1558 *
1564 *
1559 * @method cell_toggle_line_numbers
1565 * @method cell_toggle_line_numbers
1560 */
1566 */
1561 Notebook.prototype.cell_toggle_line_numbers = function() {
1567 Notebook.prototype.cell_toggle_line_numbers = function() {
1562 this.get_selected_cell().toggle_line_numbers();
1568 this.get_selected_cell().toggle_line_numbers();
1563 };
1569 };
1564
1570
1565 /**
1571 /**
1566 * Set the codemirror mode for all code cells, including the default for
1572 * Set the codemirror mode for all code cells, including the default for
1567 * new code cells.
1573 * new code cells.
1568 *
1574 *
1569 * @method set_codemirror_mode
1575 * @method set_codemirror_mode
1570 */
1576 */
1571 Notebook.prototype.set_codemirror_mode = function(newmode){
1577 Notebook.prototype.set_codemirror_mode = function(newmode){
1572 if (newmode === this.codemirror_mode) {
1578 if (newmode === this.codemirror_mode) {
1573 return;
1579 return;
1574 }
1580 }
1575 this.codemirror_mode = newmode;
1581 this.codemirror_mode = newmode;
1576 codecell.CodeCell.options_default.cm_config.mode = newmode;
1582 codecell.CodeCell.options_default.cm_config.mode = newmode;
1577 var modename = newmode.mode || newmode.name || newmode;
1583 var modename = newmode.mode || newmode.name || newmode;
1578
1584
1579 var that = this;
1585 var that = this;
1580 utils.requireCodeMirrorMode(modename, function () {
1586 utils.requireCodeMirrorMode(modename, function () {
1581 that.get_cells().map(function(cell, i) {
1587 that.get_cells().map(function(cell, i) {
1582 if (cell.cell_type === 'code'){
1588 if (cell.cell_type === 'code'){
1583 cell.code_mirror.setOption('mode', newmode);
1589 cell.code_mirror.setOption('mode', newmode);
1584 // This is currently redundant, because cm_config ends up as
1590 // This is currently redundant, because cm_config ends up as
1585 // codemirror's own .options object, but I don't want to
1591 // codemirror's own .options object, but I don't want to
1586 // rely on that.
1592 // rely on that.
1587 cell.cm_config.mode = newmode;
1593 cell.cm_config.mode = newmode;
1588 }
1594 }
1589 });
1595 });
1590 });
1596 });
1591 };
1597 };
1592
1598
1593 // Session related things
1599 // Session related things
1594
1600
1595 /**
1601 /**
1596 * Start a new session and set it on each code cell.
1602 * Start a new session and set it on each code cell.
1597 *
1603 *
1598 * @method start_session
1604 * @method start_session
1599 */
1605 */
1600 Notebook.prototype.start_session = function (kernel_name) {
1606 Notebook.prototype.start_session = function (kernel_name) {
1601 if (this._session_starting) {
1607 if (this._session_starting) {
1602 throw new session.SessionAlreadyStarting();
1608 throw new session.SessionAlreadyStarting();
1603 }
1609 }
1604 this._session_starting = true;
1610 this._session_starting = true;
1605
1611
1606 var options = {
1612 var options = {
1607 base_url: this.base_url,
1613 base_url: this.base_url,
1608 ws_url: this.ws_url,
1614 ws_url: this.ws_url,
1609 notebook_path: this.notebook_path,
1615 notebook_path: this.notebook_path,
1610 notebook_name: this.notebook_name,
1616 notebook_name: this.notebook_name,
1611 kernel_name: kernel_name,
1617 kernel_name: kernel_name,
1612 notebook: this
1618 notebook: this
1613 };
1619 };
1614
1620
1615 var success = $.proxy(this._session_started, this);
1621 var success = $.proxy(this._session_started, this);
1616 var failure = $.proxy(this._session_start_failed, this);
1622 var failure = $.proxy(this._session_start_failed, this);
1617
1623
1618 if (this.session !== null) {
1624 if (this.session !== null) {
1619 this.session.restart(options, success, failure);
1625 this.session.restart(options, success, failure);
1620 } else {
1626 } else {
1621 this.session = new session.Session(options);
1627 this.session = new session.Session(options);
1622 this.session.start(success, failure);
1628 this.session.start(success, failure);
1623 }
1629 }
1624 };
1630 };
1625
1631
1626
1632
1627 /**
1633 /**
1628 * Once a session is started, link the code cells to the kernel and pass the
1634 * Once a session is started, link the code cells to the kernel and pass the
1629 * comm manager to the widget manager
1635 * comm manager to the widget manager
1630 *
1636 *
1631 */
1637 */
1632 Notebook.prototype._session_started = function (){
1638 Notebook.prototype._session_started = function (){
1633 this._session_starting = false;
1639 this._session_starting = false;
1634 this.kernel = this.session.kernel;
1640 this.kernel = this.session.kernel;
1635 var ncells = this.ncells();
1641 var ncells = this.ncells();
1636 for (var i=0; i<ncells; i++) {
1642 for (var i=0; i<ncells; i++) {
1637 var cell = this.get_cell(i);
1643 var cell = this.get_cell(i);
1638 if (cell instanceof codecell.CodeCell) {
1644 if (cell instanceof codecell.CodeCell) {
1639 cell.set_kernel(this.session.kernel);
1645 cell.set_kernel(this.session.kernel);
1640 }
1646 }
1641 }
1647 }
1642 };
1648 };
1643 Notebook.prototype._session_start_failed = function (jqxhr, status, error){
1649 Notebook.prototype._session_start_failed = function (jqxhr, status, error){
1644 this._session_starting = false;
1650 this._session_starting = false;
1645 utils.log_ajax_error(jqxhr, status, error);
1651 utils.log_ajax_error(jqxhr, status, error);
1646 };
1652 };
1647
1653
1648 /**
1654 /**
1649 * Prompt the user to restart the IPython kernel.
1655 * Prompt the user to restart the IPython kernel.
1650 *
1656 *
1651 * @method restart_kernel
1657 * @method restart_kernel
1652 */
1658 */
1653 Notebook.prototype.restart_kernel = function () {
1659 Notebook.prototype.restart_kernel = function () {
1654 var that = this;
1660 var that = this;
1655 dialog.modal({
1661 dialog.modal({
1656 notebook: this,
1662 notebook: this,
1657 keyboard_manager: this.keyboard_manager,
1663 keyboard_manager: this.keyboard_manager,
1658 title : "Restart kernel or continue running?",
1664 title : "Restart kernel or continue running?",
1659 body : $("<p/>").text(
1665 body : $("<p/>").text(
1660 'Do you want to restart the current kernel? You will lose all variables defined in it.'
1666 'Do you want to restart the current kernel? You will lose all variables defined in it.'
1661 ),
1667 ),
1662 buttons : {
1668 buttons : {
1663 "Continue running" : {},
1669 "Continue running" : {},
1664 "Restart" : {
1670 "Restart" : {
1665 "class" : "btn-danger",
1671 "class" : "btn-danger",
1666 "click" : function() {
1672 "click" : function() {
1667 that.kernel.restart();
1673 that.kernel.restart();
1668 }
1674 }
1669 }
1675 }
1670 }
1676 }
1671 });
1677 });
1672 };
1678 };
1673
1679
1674 /**
1680 /**
1675 * Execute or render cell outputs and go into command mode.
1681 * Execute or render cell outputs and go into command mode.
1676 *
1682 *
1677 * @method execute_cell
1683 * @method execute_cell
1678 */
1684 */
1679 Notebook.prototype.execute_cell = function () {
1685 Notebook.prototype.execute_cell = function () {
1680 // mode = shift, ctrl, alt
1686 /**
1687 * mode = shift, ctrl, alt
1688 */
1681 var cell = this.get_selected_cell();
1689 var cell = this.get_selected_cell();
1682
1690
1683 cell.execute();
1691 cell.execute();
1684 this.command_mode();
1692 this.command_mode();
1685 this.set_dirty(true);
1693 this.set_dirty(true);
1686 };
1694 };
1687
1695
1688 /**
1696 /**
1689 * Execute or render cell outputs and insert a new cell below.
1697 * Execute or render cell outputs and insert a new cell below.
1690 *
1698 *
1691 * @method execute_cell_and_insert_below
1699 * @method execute_cell_and_insert_below
1692 */
1700 */
1693 Notebook.prototype.execute_cell_and_insert_below = function () {
1701 Notebook.prototype.execute_cell_and_insert_below = function () {
1694 var cell = this.get_selected_cell();
1702 var cell = this.get_selected_cell();
1695 var cell_index = this.find_cell_index(cell);
1703 var cell_index = this.find_cell_index(cell);
1696
1704
1697 cell.execute();
1705 cell.execute();
1698
1706
1699 // If we are at the end always insert a new cell and return
1707 // If we are at the end always insert a new cell and return
1700 if (cell_index === (this.ncells()-1)) {
1708 if (cell_index === (this.ncells()-1)) {
1701 this.command_mode();
1709 this.command_mode();
1702 this.insert_cell_below();
1710 this.insert_cell_below();
1703 this.select(cell_index+1);
1711 this.select(cell_index+1);
1704 this.edit_mode();
1712 this.edit_mode();
1705 this.scroll_to_bottom();
1713 this.scroll_to_bottom();
1706 this.set_dirty(true);
1714 this.set_dirty(true);
1707 return;
1715 return;
1708 }
1716 }
1709
1717
1710 this.command_mode();
1718 this.command_mode();
1711 this.insert_cell_below();
1719 this.insert_cell_below();
1712 this.select(cell_index+1);
1720 this.select(cell_index+1);
1713 this.edit_mode();
1721 this.edit_mode();
1714 this.set_dirty(true);
1722 this.set_dirty(true);
1715 };
1723 };
1716
1724
1717 /**
1725 /**
1718 * Execute or render cell outputs and select the next cell.
1726 * Execute or render cell outputs and select the next cell.
1719 *
1727 *
1720 * @method execute_cell_and_select_below
1728 * @method execute_cell_and_select_below
1721 */
1729 */
1722 Notebook.prototype.execute_cell_and_select_below = function () {
1730 Notebook.prototype.execute_cell_and_select_below = function () {
1723
1731
1724 var cell = this.get_selected_cell();
1732 var cell = this.get_selected_cell();
1725 var cell_index = this.find_cell_index(cell);
1733 var cell_index = this.find_cell_index(cell);
1726
1734
1727 cell.execute();
1735 cell.execute();
1728
1736
1729 // If we are at the end always insert a new cell and return
1737 // If we are at the end always insert a new cell and return
1730 if (cell_index === (this.ncells()-1)) {
1738 if (cell_index === (this.ncells()-1)) {
1731 this.command_mode();
1739 this.command_mode();
1732 this.insert_cell_below();
1740 this.insert_cell_below();
1733 this.select(cell_index+1);
1741 this.select(cell_index+1);
1734 this.edit_mode();
1742 this.edit_mode();
1735 this.scroll_to_bottom();
1743 this.scroll_to_bottom();
1736 this.set_dirty(true);
1744 this.set_dirty(true);
1737 return;
1745 return;
1738 }
1746 }
1739
1747
1740 this.command_mode();
1748 this.command_mode();
1741 this.select(cell_index+1);
1749 this.select(cell_index+1);
1742 this.focus_cell();
1750 this.focus_cell();
1743 this.set_dirty(true);
1751 this.set_dirty(true);
1744 };
1752 };
1745
1753
1746 /**
1754 /**
1747 * Execute all cells below the selected cell.
1755 * Execute all cells below the selected cell.
1748 *
1756 *
1749 * @method execute_cells_below
1757 * @method execute_cells_below
1750 */
1758 */
1751 Notebook.prototype.execute_cells_below = function () {
1759 Notebook.prototype.execute_cells_below = function () {
1752 this.execute_cell_range(this.get_selected_index(), this.ncells());
1760 this.execute_cell_range(this.get_selected_index(), this.ncells());
1753 this.scroll_to_bottom();
1761 this.scroll_to_bottom();
1754 };
1762 };
1755
1763
1756 /**
1764 /**
1757 * Execute all cells above the selected cell.
1765 * Execute all cells above the selected cell.
1758 *
1766 *
1759 * @method execute_cells_above
1767 * @method execute_cells_above
1760 */
1768 */
1761 Notebook.prototype.execute_cells_above = function () {
1769 Notebook.prototype.execute_cells_above = function () {
1762 this.execute_cell_range(0, this.get_selected_index());
1770 this.execute_cell_range(0, this.get_selected_index());
1763 };
1771 };
1764
1772
1765 /**
1773 /**
1766 * Execute all cells.
1774 * Execute all cells.
1767 *
1775 *
1768 * @method execute_all_cells
1776 * @method execute_all_cells
1769 */
1777 */
1770 Notebook.prototype.execute_all_cells = function () {
1778 Notebook.prototype.execute_all_cells = function () {
1771 this.execute_cell_range(0, this.ncells());
1779 this.execute_cell_range(0, this.ncells());
1772 this.scroll_to_bottom();
1780 this.scroll_to_bottom();
1773 };
1781 };
1774
1782
1775 /**
1783 /**
1776 * Execute a contiguous range of cells.
1784 * Execute a contiguous range of cells.
1777 *
1785 *
1778 * @method execute_cell_range
1786 * @method execute_cell_range
1779 * @param {Number} start Index of the first cell to execute (inclusive)
1787 * @param {Number} start Index of the first cell to execute (inclusive)
1780 * @param {Number} end Index of the last cell to execute (exclusive)
1788 * @param {Number} end Index of the last cell to execute (exclusive)
1781 */
1789 */
1782 Notebook.prototype.execute_cell_range = function (start, end) {
1790 Notebook.prototype.execute_cell_range = function (start, end) {
1783 this.command_mode();
1791 this.command_mode();
1784 for (var i=start; i<end; i++) {
1792 for (var i=start; i<end; i++) {
1785 this.select(i);
1793 this.select(i);
1786 this.execute_cell();
1794 this.execute_cell();
1787 }
1795 }
1788 };
1796 };
1789
1797
1790 // Persistance and loading
1798 // Persistance and loading
1791
1799
1792 /**
1800 /**
1793 * Getter method for this notebook's name.
1801 * Getter method for this notebook's name.
1794 *
1802 *
1795 * @method get_notebook_name
1803 * @method get_notebook_name
1796 * @return {String} This notebook's name (excluding file extension)
1804 * @return {String} This notebook's name (excluding file extension)
1797 */
1805 */
1798 Notebook.prototype.get_notebook_name = function () {
1806 Notebook.prototype.get_notebook_name = function () {
1799 var nbname = this.notebook_name.substring(0,this.notebook_name.length-6);
1807 var nbname = this.notebook_name.substring(0,this.notebook_name.length-6);
1800 return nbname;
1808 return nbname;
1801 };
1809 };
1802
1810
1803 /**
1811 /**
1804 * Setter method for this notebook's name.
1812 * Setter method for this notebook's name.
1805 *
1813 *
1806 * @method set_notebook_name
1814 * @method set_notebook_name
1807 * @param {String} name A new name for this notebook
1815 * @param {String} name A new name for this notebook
1808 */
1816 */
1809 Notebook.prototype.set_notebook_name = function (name) {
1817 Notebook.prototype.set_notebook_name = function (name) {
1810 var parent = utils.url_path_split(this.notebook_path)[0];
1818 var parent = utils.url_path_split(this.notebook_path)[0];
1811 this.notebook_name = name;
1819 this.notebook_name = name;
1812 this.notebook_path = utils.url_path_join(parent, name);
1820 this.notebook_path = utils.url_path_join(parent, name);
1813 };
1821 };
1814
1822
1815 /**
1823 /**
1816 * Check that a notebook's name is valid.
1824 * Check that a notebook's name is valid.
1817 *
1825 *
1818 * @method test_notebook_name
1826 * @method test_notebook_name
1819 * @param {String} nbname A name for this notebook
1827 * @param {String} nbname A name for this notebook
1820 * @return {Boolean} True if the name is valid, false if invalid
1828 * @return {Boolean} True if the name is valid, false if invalid
1821 */
1829 */
1822 Notebook.prototype.test_notebook_name = function (nbname) {
1830 Notebook.prototype.test_notebook_name = function (nbname) {
1823 nbname = nbname || '';
1831 nbname = nbname || '';
1824 if (nbname.length>0 && !this.notebook_name_blacklist_re.test(nbname)) {
1832 if (nbname.length>0 && !this.notebook_name_blacklist_re.test(nbname)) {
1825 return true;
1833 return true;
1826 } else {
1834 } else {
1827 return false;
1835 return false;
1828 }
1836 }
1829 };
1837 };
1830
1838
1831 /**
1839 /**
1832 * Load a notebook from JSON (.ipynb).
1840 * Load a notebook from JSON (.ipynb).
1833 *
1841 *
1834 * @method fromJSON
1842 * @method fromJSON
1835 * @param {Object} data JSON representation of a notebook
1843 * @param {Object} data JSON representation of a notebook
1836 */
1844 */
1837 Notebook.prototype.fromJSON = function (data) {
1845 Notebook.prototype.fromJSON = function (data) {
1838
1846
1839 var content = data.content;
1847 var content = data.content;
1840 var ncells = this.ncells();
1848 var ncells = this.ncells();
1841 var i;
1849 var i;
1842 for (i=0; i<ncells; i++) {
1850 for (i=0; i<ncells; i++) {
1843 // Always delete cell 0 as they get renumbered as they are deleted.
1851 // Always delete cell 0 as they get renumbered as they are deleted.
1844 this.delete_cell(0);
1852 this.delete_cell(0);
1845 }
1853 }
1846 // Save the metadata and name.
1854 // Save the metadata and name.
1847 this.metadata = content.metadata;
1855 this.metadata = content.metadata;
1848 this.notebook_name = data.name;
1856 this.notebook_name = data.name;
1849 this.notebook_path = data.path;
1857 this.notebook_path = data.path;
1850 var trusted = true;
1858 var trusted = true;
1851
1859
1852 // Trigger an event changing the kernel spec - this will set the default
1860 // Trigger an event changing the kernel spec - this will set the default
1853 // codemirror mode
1861 // codemirror mode
1854 if (this.metadata.kernelspec !== undefined) {
1862 if (this.metadata.kernelspec !== undefined) {
1855 this.events.trigger('spec_changed.Kernel', this.metadata.kernelspec);
1863 this.events.trigger('spec_changed.Kernel', this.metadata.kernelspec);
1856 }
1864 }
1857
1865
1858 // Set the codemirror mode from language_info metadata
1866 // Set the codemirror mode from language_info metadata
1859 if (this.metadata.language_info !== undefined) {
1867 if (this.metadata.language_info !== undefined) {
1860 var langinfo = this.metadata.language_info;
1868 var langinfo = this.metadata.language_info;
1861 // Mode 'null' should be plain, unhighlighted text.
1869 // Mode 'null' should be plain, unhighlighted text.
1862 var cm_mode = langinfo.codemirror_mode || langinfo.name || 'null';
1870 var cm_mode = langinfo.codemirror_mode || langinfo.name || 'null';
1863 this.set_codemirror_mode(cm_mode);
1871 this.set_codemirror_mode(cm_mode);
1864 }
1872 }
1865
1873
1866 var new_cells = content.cells;
1874 var new_cells = content.cells;
1867 ncells = new_cells.length;
1875 ncells = new_cells.length;
1868 var cell_data = null;
1876 var cell_data = null;
1869 var new_cell = null;
1877 var new_cell = null;
1870 for (i=0; i<ncells; i++) {
1878 for (i=0; i<ncells; i++) {
1871 cell_data = new_cells[i];
1879 cell_data = new_cells[i];
1872 new_cell = this.insert_cell_at_index(cell_data.cell_type, i);
1880 new_cell = this.insert_cell_at_index(cell_data.cell_type, i);
1873 new_cell.fromJSON(cell_data);
1881 new_cell.fromJSON(cell_data);
1874 if (new_cell.cell_type == 'code' && !new_cell.output_area.trusted) {
1882 if (new_cell.cell_type == 'code' && !new_cell.output_area.trusted) {
1875 trusted = false;
1883 trusted = false;
1876 }
1884 }
1877 }
1885 }
1878 if (trusted !== this.trusted) {
1886 if (trusted !== this.trusted) {
1879 this.trusted = trusted;
1887 this.trusted = trusted;
1880 this.events.trigger("trust_changed.Notebook", trusted);
1888 this.events.trigger("trust_changed.Notebook", trusted);
1881 }
1889 }
1882 };
1890 };
1883
1891
1884 /**
1892 /**
1885 * Dump this notebook into a JSON-friendly object.
1893 * Dump this notebook into a JSON-friendly object.
1886 *
1894 *
1887 * @method toJSON
1895 * @method toJSON
1888 * @return {Object} A JSON-friendly representation of this notebook.
1896 * @return {Object} A JSON-friendly representation of this notebook.
1889 */
1897 */
1890 Notebook.prototype.toJSON = function () {
1898 Notebook.prototype.toJSON = function () {
1891 // remove the conversion indicator, which only belongs in-memory
1899 /**
1900 * remove the conversion indicator, which only belongs in-memory
1901 */
1892 delete this.metadata.orig_nbformat;
1902 delete this.metadata.orig_nbformat;
1893 delete this.metadata.orig_nbformat_minor;
1903 delete this.metadata.orig_nbformat_minor;
1894
1904
1895 var cells = this.get_cells();
1905 var cells = this.get_cells();
1896 var ncells = cells.length;
1906 var ncells = cells.length;
1897 var cell_array = new Array(ncells);
1907 var cell_array = new Array(ncells);
1898 var trusted = true;
1908 var trusted = true;
1899 for (var i=0; i<ncells; i++) {
1909 for (var i=0; i<ncells; i++) {
1900 var cell = cells[i];
1910 var cell = cells[i];
1901 if (cell.cell_type == 'code' && !cell.output_area.trusted) {
1911 if (cell.cell_type == 'code' && !cell.output_area.trusted) {
1902 trusted = false;
1912 trusted = false;
1903 }
1913 }
1904 cell_array[i] = cell.toJSON();
1914 cell_array[i] = cell.toJSON();
1905 }
1915 }
1906 var data = {
1916 var data = {
1907 cells: cell_array,
1917 cells: cell_array,
1908 metadata: this.metadata,
1918 metadata: this.metadata,
1909 nbformat: this.nbformat,
1919 nbformat: this.nbformat,
1910 nbformat_minor: this.nbformat_minor
1920 nbformat_minor: this.nbformat_minor
1911 };
1921 };
1912 if (trusted != this.trusted) {
1922 if (trusted != this.trusted) {
1913 this.trusted = trusted;
1923 this.trusted = trusted;
1914 this.events.trigger("trust_changed.Notebook", trusted);
1924 this.events.trigger("trust_changed.Notebook", trusted);
1915 }
1925 }
1916 return data;
1926 return data;
1917 };
1927 };
1918
1928
1919 /**
1929 /**
1920 * Start an autosave timer, for periodically saving the notebook.
1930 * Start an autosave timer, for periodically saving the notebook.
1921 *
1931 *
1922 * @method set_autosave_interval
1932 * @method set_autosave_interval
1923 * @param {Integer} interval the autosave interval in milliseconds
1933 * @param {Integer} interval the autosave interval in milliseconds
1924 */
1934 */
1925 Notebook.prototype.set_autosave_interval = function (interval) {
1935 Notebook.prototype.set_autosave_interval = function (interval) {
1926 var that = this;
1936 var that = this;
1927 // clear previous interval, so we don't get simultaneous timers
1937 // clear previous interval, so we don't get simultaneous timers
1928 if (this.autosave_timer) {
1938 if (this.autosave_timer) {
1929 clearInterval(this.autosave_timer);
1939 clearInterval(this.autosave_timer);
1930 }
1940 }
1931 if (!this.writable) {
1941 if (!this.writable) {
1932 // disable autosave if not writable
1942 // disable autosave if not writable
1933 interval = 0;
1943 interval = 0;
1934 }
1944 }
1935
1945
1936 this.autosave_interval = this.minimum_autosave_interval = interval;
1946 this.autosave_interval = this.minimum_autosave_interval = interval;
1937 if (interval) {
1947 if (interval) {
1938 this.autosave_timer = setInterval(function() {
1948 this.autosave_timer = setInterval(function() {
1939 if (that.dirty) {
1949 if (that.dirty) {
1940 that.save_notebook();
1950 that.save_notebook();
1941 }
1951 }
1942 }, interval);
1952 }, interval);
1943 this.events.trigger("autosave_enabled.Notebook", interval);
1953 this.events.trigger("autosave_enabled.Notebook", interval);
1944 } else {
1954 } else {
1945 this.autosave_timer = null;
1955 this.autosave_timer = null;
1946 this.events.trigger("autosave_disabled.Notebook");
1956 this.events.trigger("autosave_disabled.Notebook");
1947 }
1957 }
1948 };
1958 };
1949
1959
1950 /**
1960 /**
1951 * Save this notebook on the server. This becomes a notebook instance's
1961 * Save this notebook on the server. This becomes a notebook instance's
1952 * .save_notebook method *after* the entire notebook has been loaded.
1962 * .save_notebook method *after* the entire notebook has been loaded.
1953 *
1963 *
1954 * @method save_notebook
1964 * @method save_notebook
1955 */
1965 */
1956 Notebook.prototype.save_notebook = function () {
1966 Notebook.prototype.save_notebook = function () {
1957 if (!this._fully_loaded) {
1967 if (!this._fully_loaded) {
1958 this.events.trigger('notebook_save_failed.Notebook',
1968 this.events.trigger('notebook_save_failed.Notebook',
1959 new Error("Load failed, save is disabled")
1969 new Error("Load failed, save is disabled")
1960 );
1970 );
1961 return;
1971 return;
1962 } else if (!this.writable) {
1972 } else if (!this.writable) {
1963 this.events.trigger('notebook_save_failed.Notebook',
1973 this.events.trigger('notebook_save_failed.Notebook',
1964 new Error("Notebook is read-only")
1974 new Error("Notebook is read-only")
1965 );
1975 );
1966 return;
1976 return;
1967 }
1977 }
1968
1978
1969 // Create a JSON model to be sent to the server.
1979 // Create a JSON model to be sent to the server.
1970 var model = {
1980 var model = {
1971 type : "notebook",
1981 type : "notebook",
1972 content : this.toJSON()
1982 content : this.toJSON()
1973 };
1983 };
1974 // time the ajax call for autosave tuning purposes.
1984 // time the ajax call for autosave tuning purposes.
1975 var start = new Date().getTime();
1985 var start = new Date().getTime();
1976
1986
1977 var that = this;
1987 var that = this;
1978 return this.contents.save(this.notebook_path, model).then(
1988 return this.contents.save(this.notebook_path, model).then(
1979 $.proxy(this.save_notebook_success, this, start),
1989 $.proxy(this.save_notebook_success, this, start),
1980 function (error) {
1990 function (error) {
1981 that.events.trigger('notebook_save_failed.Notebook', error);
1991 that.events.trigger('notebook_save_failed.Notebook', error);
1982 }
1992 }
1983 );
1993 );
1984 };
1994 };
1985
1995
1986 /**
1996 /**
1987 * Success callback for saving a notebook.
1997 * Success callback for saving a notebook.
1988 *
1998 *
1989 * @method save_notebook_success
1999 * @method save_notebook_success
1990 * @param {Integer} start Time when the save request start
2000 * @param {Integer} start Time when the save request start
1991 * @param {Object} data JSON representation of a notebook
2001 * @param {Object} data JSON representation of a notebook
1992 */
2002 */
1993 Notebook.prototype.save_notebook_success = function (start, data) {
2003 Notebook.prototype.save_notebook_success = function (start, data) {
1994 this.set_dirty(false);
2004 this.set_dirty(false);
1995 if (data.message) {
2005 if (data.message) {
1996 // save succeeded, but validation failed.
2006 // save succeeded, but validation failed.
1997 var body = $("<div>");
2007 var body = $("<div>");
1998 var title = "Notebook validation failed";
2008 var title = "Notebook validation failed";
1999
2009
2000 body.append($("<p>").text(
2010 body.append($("<p>").text(
2001 "The save operation succeeded," +
2011 "The save operation succeeded," +
2002 " but the notebook does not appear to be valid." +
2012 " but the notebook does not appear to be valid." +
2003 " The validation error was:"
2013 " The validation error was:"
2004 )).append($("<div>").addClass("validation-error").append(
2014 )).append($("<div>").addClass("validation-error").append(
2005 $("<pre>").text(data.message)
2015 $("<pre>").text(data.message)
2006 ));
2016 ));
2007 dialog.modal({
2017 dialog.modal({
2008 notebook: this,
2018 notebook: this,
2009 keyboard_manager: this.keyboard_manager,
2019 keyboard_manager: this.keyboard_manager,
2010 title: title,
2020 title: title,
2011 body: body,
2021 body: body,
2012 buttons : {
2022 buttons : {
2013 OK : {
2023 OK : {
2014 "class" : "btn-primary"
2024 "class" : "btn-primary"
2015 }
2025 }
2016 }
2026 }
2017 });
2027 });
2018 }
2028 }
2019 this.events.trigger('notebook_saved.Notebook');
2029 this.events.trigger('notebook_saved.Notebook');
2020 this._update_autosave_interval(start);
2030 this._update_autosave_interval(start);
2021 if (this._checkpoint_after_save) {
2031 if (this._checkpoint_after_save) {
2022 this.create_checkpoint();
2032 this.create_checkpoint();
2023 this._checkpoint_after_save = false;
2033 this._checkpoint_after_save = false;
2024 }
2034 }
2025 };
2035 };
2026
2036
2027 /**
2037 /**
2028 * update the autosave interval based on how long the last save took
2038 * update the autosave interval based on how long the last save took
2029 *
2039 *
2030 * @method _update_autosave_interval
2040 * @method _update_autosave_interval
2031 * @param {Integer} timestamp when the save request started
2041 * @param {Integer} timestamp when the save request started
2032 */
2042 */
2033 Notebook.prototype._update_autosave_interval = function (start) {
2043 Notebook.prototype._update_autosave_interval = function (start) {
2034 var duration = (new Date().getTime() - start);
2044 var duration = (new Date().getTime() - start);
2035 if (this.autosave_interval) {
2045 if (this.autosave_interval) {
2036 // new save interval: higher of 10x save duration or parameter (default 30 seconds)
2046 // new save interval: higher of 10x save duration or parameter (default 30 seconds)
2037 var interval = Math.max(10 * duration, this.minimum_autosave_interval);
2047 var interval = Math.max(10 * duration, this.minimum_autosave_interval);
2038 // round to 10 seconds, otherwise we will be setting a new interval too often
2048 // round to 10 seconds, otherwise we will be setting a new interval too often
2039 interval = 10000 * Math.round(interval / 10000);
2049 interval = 10000 * Math.round(interval / 10000);
2040 // set new interval, if it's changed
2050 // set new interval, if it's changed
2041 if (interval != this.autosave_interval) {
2051 if (interval != this.autosave_interval) {
2042 this.set_autosave_interval(interval);
2052 this.set_autosave_interval(interval);
2043 }
2053 }
2044 }
2054 }
2045 };
2055 };
2046
2056
2047 /**
2057 /**
2048 * Explicitly trust the output of this notebook.
2058 * Explicitly trust the output of this notebook.
2049 *
2059 *
2050 * @method trust_notebook
2060 * @method trust_notebook
2051 */
2061 */
2052 Notebook.prototype.trust_notebook = function () {
2062 Notebook.prototype.trust_notebook = function () {
2053 var body = $("<div>").append($("<p>")
2063 var body = $("<div>").append($("<p>")
2054 .text("A trusted IPython notebook may execute hidden malicious code ")
2064 .text("A trusted IPython notebook may execute hidden malicious code ")
2055 .append($("<strong>")
2065 .append($("<strong>")
2056 .append(
2066 .append(
2057 $("<em>").text("when you open it")
2067 $("<em>").text("when you open it")
2058 )
2068 )
2059 ).append(".").append(
2069 ).append(".").append(
2060 " Selecting trust will immediately reload this notebook in a trusted state."
2070 " Selecting trust will immediately reload this notebook in a trusted state."
2061 ).append(
2071 ).append(
2062 " For more information, see the "
2072 " For more information, see the "
2063 ).append($("<a>").attr("href", "http://ipython.org/ipython-doc/2/notebook/security.html")
2073 ).append($("<a>").attr("href", "http://ipython.org/ipython-doc/2/notebook/security.html")
2064 .text("IPython security documentation")
2074 .text("IPython security documentation")
2065 ).append(".")
2075 ).append(".")
2066 );
2076 );
2067
2077
2068 var nb = this;
2078 var nb = this;
2069 dialog.modal({
2079 dialog.modal({
2070 notebook: this,
2080 notebook: this,
2071 keyboard_manager: this.keyboard_manager,
2081 keyboard_manager: this.keyboard_manager,
2072 title: "Trust this notebook?",
2082 title: "Trust this notebook?",
2073 body: body,
2083 body: body,
2074
2084
2075 buttons: {
2085 buttons: {
2076 Cancel : {},
2086 Cancel : {},
2077 Trust : {
2087 Trust : {
2078 class : "btn-danger",
2088 class : "btn-danger",
2079 click : function () {
2089 click : function () {
2080 var cells = nb.get_cells();
2090 var cells = nb.get_cells();
2081 for (var i = 0; i < cells.length; i++) {
2091 for (var i = 0; i < cells.length; i++) {
2082 var cell = cells[i];
2092 var cell = cells[i];
2083 if (cell.cell_type == 'code') {
2093 if (cell.cell_type == 'code') {
2084 cell.output_area.trusted = true;
2094 cell.output_area.trusted = true;
2085 }
2095 }
2086 }
2096 }
2087 nb.events.on('notebook_saved.Notebook', function () {
2097 nb.events.on('notebook_saved.Notebook', function () {
2088 window.location.reload();
2098 window.location.reload();
2089 });
2099 });
2090 nb.save_notebook();
2100 nb.save_notebook();
2091 }
2101 }
2092 }
2102 }
2093 }
2103 }
2094 });
2104 });
2095 };
2105 };
2096
2106
2097 Notebook.prototype.copy_notebook = function () {
2107 Notebook.prototype.copy_notebook = function () {
2098 var that = this;
2108 var that = this;
2099 var base_url = this.base_url;
2109 var base_url = this.base_url;
2100 var w = window.open();
2110 var w = window.open();
2101 var parent = utils.url_path_split(this.notebook_path)[0];
2111 var parent = utils.url_path_split(this.notebook_path)[0];
2102 this.contents.copy(this.notebook_path, parent).then(
2112 this.contents.copy(this.notebook_path, parent).then(
2103 function (data) {
2113 function (data) {
2104 w.location = utils.url_join_encode(
2114 w.location = utils.url_join_encode(
2105 base_url, 'notebooks', data.path
2115 base_url, 'notebooks', data.path
2106 );
2116 );
2107 },
2117 },
2108 function(error) {
2118 function(error) {
2109 w.close();
2119 w.close();
2110 that.events.trigger('notebook_copy_failed', error);
2120 that.events.trigger('notebook_copy_failed', error);
2111 }
2121 }
2112 );
2122 );
2113 };
2123 };
2114
2124
2115 Notebook.prototype.rename = function (new_name) {
2125 Notebook.prototype.rename = function (new_name) {
2116 if (!new_name.match(/\.ipynb$/)) {
2126 if (!new_name.match(/\.ipynb$/)) {
2117 new_name = new_name + ".ipynb";
2127 new_name = new_name + ".ipynb";
2118 }
2128 }
2119
2129
2120 var that = this;
2130 var that = this;
2121 var parent = utils.url_path_split(this.notebook_path)[0];
2131 var parent = utils.url_path_split(this.notebook_path)[0];
2122 var new_path = utils.url_path_join(parent, new_name);
2132 var new_path = utils.url_path_join(parent, new_name);
2123 return this.contents.rename(this.notebook_path, new_path).then(
2133 return this.contents.rename(this.notebook_path, new_path).then(
2124 function (json) {
2134 function (json) {
2125 that.notebook_name = json.name;
2135 that.notebook_name = json.name;
2126 that.notebook_path = json.path;
2136 that.notebook_path = json.path;
2127 that.session.rename_notebook(json.path);
2137 that.session.rename_notebook(json.path);
2128 that.events.trigger('notebook_renamed.Notebook', json);
2138 that.events.trigger('notebook_renamed.Notebook', json);
2129 }
2139 }
2130 );
2140 );
2131 };
2141 };
2132
2142
2133 Notebook.prototype.delete = function () {
2143 Notebook.prototype.delete = function () {
2134 this.contents.delete(this.notebook_path);
2144 this.contents.delete(this.notebook_path);
2135 };
2145 };
2136
2146
2137 /**
2147 /**
2138 * Request a notebook's data from the server.
2148 * Request a notebook's data from the server.
2139 *
2149 *
2140 * @method load_notebook
2150 * @method load_notebook
2141 * @param {String} notebook_path A notebook to load
2151 * @param {String} notebook_path A notebook to load
2142 */
2152 */
2143 Notebook.prototype.load_notebook = function (notebook_path) {
2153 Notebook.prototype.load_notebook = function (notebook_path) {
2144 this.notebook_path = notebook_path;
2154 this.notebook_path = notebook_path;
2145 this.notebook_name = utils.url_path_split(this.notebook_path)[1];
2155 this.notebook_name = utils.url_path_split(this.notebook_path)[1];
2146 this.events.trigger('notebook_loading.Notebook');
2156 this.events.trigger('notebook_loading.Notebook');
2147 this.contents.get(notebook_path, {type: 'notebook'}).then(
2157 this.contents.get(notebook_path, {type: 'notebook'}).then(
2148 $.proxy(this.load_notebook_success, this),
2158 $.proxy(this.load_notebook_success, this),
2149 $.proxy(this.load_notebook_error, this)
2159 $.proxy(this.load_notebook_error, this)
2150 );
2160 );
2151 };
2161 };
2152
2162
2153 /**
2163 /**
2154 * Success callback for loading a notebook from the server.
2164 * Success callback for loading a notebook from the server.
2155 *
2165 *
2156 * Load notebook data from the JSON response.
2166 * Load notebook data from the JSON response.
2157 *
2167 *
2158 * @method load_notebook_success
2168 * @method load_notebook_success
2159 * @param {Object} data JSON representation of a notebook
2169 * @param {Object} data JSON representation of a notebook
2160 */
2170 */
2161 Notebook.prototype.load_notebook_success = function (data) {
2171 Notebook.prototype.load_notebook_success = function (data) {
2162 var failed, msg;
2172 var failed, msg;
2163 try {
2173 try {
2164 this.fromJSON(data);
2174 this.fromJSON(data);
2165 } catch (e) {
2175 } catch (e) {
2166 failed = e;
2176 failed = e;
2167 console.log("Notebook failed to load from JSON:", e);
2177 console.log("Notebook failed to load from JSON:", e);
2168 }
2178 }
2169 if (failed || data.message) {
2179 if (failed || data.message) {
2170 // *either* fromJSON failed or validation failed
2180 // *either* fromJSON failed or validation failed
2171 var body = $("<div>");
2181 var body = $("<div>");
2172 var title;
2182 var title;
2173 if (failed) {
2183 if (failed) {
2174 title = "Notebook failed to load";
2184 title = "Notebook failed to load";
2175 body.append($("<p>").text(
2185 body.append($("<p>").text(
2176 "The error was: "
2186 "The error was: "
2177 )).append($("<div>").addClass("js-error").text(
2187 )).append($("<div>").addClass("js-error").text(
2178 failed.toString()
2188 failed.toString()
2179 )).append($("<p>").text(
2189 )).append($("<p>").text(
2180 "See the error console for details."
2190 "See the error console for details."
2181 ));
2191 ));
2182 } else {
2192 } else {
2183 title = "Notebook validation failed";
2193 title = "Notebook validation failed";
2184 }
2194 }
2185
2195
2186 if (data.message) {
2196 if (data.message) {
2187 if (failed) {
2197 if (failed) {
2188 msg = "The notebook also failed validation:";
2198 msg = "The notebook also failed validation:";
2189 } else {
2199 } else {
2190 msg = "An invalid notebook may not function properly." +
2200 msg = "An invalid notebook may not function properly." +
2191 " The validation error was:";
2201 " The validation error was:";
2192 }
2202 }
2193 body.append($("<p>").text(
2203 body.append($("<p>").text(
2194 msg
2204 msg
2195 )).append($("<div>").addClass("validation-error").append(
2205 )).append($("<div>").addClass("validation-error").append(
2196 $("<pre>").text(data.message)
2206 $("<pre>").text(data.message)
2197 ));
2207 ));
2198 }
2208 }
2199
2209
2200 dialog.modal({
2210 dialog.modal({
2201 notebook: this,
2211 notebook: this,
2202 keyboard_manager: this.keyboard_manager,
2212 keyboard_manager: this.keyboard_manager,
2203 title: title,
2213 title: title,
2204 body: body,
2214 body: body,
2205 buttons : {
2215 buttons : {
2206 OK : {
2216 OK : {
2207 "class" : "btn-primary"
2217 "class" : "btn-primary"
2208 }
2218 }
2209 }
2219 }
2210 });
2220 });
2211 }
2221 }
2212 if (this.ncells() === 0) {
2222 if (this.ncells() === 0) {
2213 this.insert_cell_below('code');
2223 this.insert_cell_below('code');
2214 this.edit_mode(0);
2224 this.edit_mode(0);
2215 } else {
2225 } else {
2216 this.select(0);
2226 this.select(0);
2217 this.handle_command_mode(this.get_cell(0));
2227 this.handle_command_mode(this.get_cell(0));
2218 }
2228 }
2219 this.set_dirty(false);
2229 this.set_dirty(false);
2220 this.scroll_to_top();
2230 this.scroll_to_top();
2221 this.writable = data.writable || false;
2231 this.writable = data.writable || false;
2222 var nbmodel = data.content;
2232 var nbmodel = data.content;
2223 var orig_nbformat = nbmodel.metadata.orig_nbformat;
2233 var orig_nbformat = nbmodel.metadata.orig_nbformat;
2224 var orig_nbformat_minor = nbmodel.metadata.orig_nbformat_minor;
2234 var orig_nbformat_minor = nbmodel.metadata.orig_nbformat_minor;
2225 if (orig_nbformat !== undefined && nbmodel.nbformat !== orig_nbformat) {
2235 if (orig_nbformat !== undefined && nbmodel.nbformat !== orig_nbformat) {
2226 var src;
2236 var src;
2227 if (nbmodel.nbformat > orig_nbformat) {
2237 if (nbmodel.nbformat > orig_nbformat) {
2228 src = " an older notebook format ";
2238 src = " an older notebook format ";
2229 } else {
2239 } else {
2230 src = " a newer notebook format ";
2240 src = " a newer notebook format ";
2231 }
2241 }
2232
2242
2233 msg = "This notebook has been converted from" + src +
2243 msg = "This notebook has been converted from" + src +
2234 "(v"+orig_nbformat+") to the current notebook " +
2244 "(v"+orig_nbformat+") to the current notebook " +
2235 "format (v"+nbmodel.nbformat+"). The next time you save this notebook, the " +
2245 "format (v"+nbmodel.nbformat+"). The next time you save this notebook, the " +
2236 "current notebook format will be used.";
2246 "current notebook format will be used.";
2237
2247
2238 if (nbmodel.nbformat > orig_nbformat) {
2248 if (nbmodel.nbformat > orig_nbformat) {
2239 msg += " Older versions of IPython may not be able to read the new format.";
2249 msg += " Older versions of IPython may not be able to read the new format.";
2240 } else {
2250 } else {
2241 msg += " Some features of the original notebook may not be available.";
2251 msg += " Some features of the original notebook may not be available.";
2242 }
2252 }
2243 msg += " To preserve the original version, close the " +
2253 msg += " To preserve the original version, close the " +
2244 "notebook without saving it.";
2254 "notebook without saving it.";
2245 dialog.modal({
2255 dialog.modal({
2246 notebook: this,
2256 notebook: this,
2247 keyboard_manager: this.keyboard_manager,
2257 keyboard_manager: this.keyboard_manager,
2248 title : "Notebook converted",
2258 title : "Notebook converted",
2249 body : msg,
2259 body : msg,
2250 buttons : {
2260 buttons : {
2251 OK : {
2261 OK : {
2252 class : "btn-primary"
2262 class : "btn-primary"
2253 }
2263 }
2254 }
2264 }
2255 });
2265 });
2256 } else if (this.nbformat_minor < nbmodel.nbformat_minor) {
2266 } else if (this.nbformat_minor < nbmodel.nbformat_minor) {
2257 this.nbformat_minor = nbmodel.nbformat_minor;
2267 this.nbformat_minor = nbmodel.nbformat_minor;
2258 }
2268 }
2259
2269
2260 // Create the session after the notebook is completely loaded to prevent
2270 // Create the session after the notebook is completely loaded to prevent
2261 // code execution upon loading, which is a security risk.
2271 // code execution upon loading, which is a security risk.
2262 if (this.session === null) {
2272 if (this.session === null) {
2263 var kernelspec = this.metadata.kernelspec || {};
2273 var kernelspec = this.metadata.kernelspec || {};
2264 var kernel_name = kernelspec.name;
2274 var kernel_name = kernelspec.name;
2265
2275
2266 this.start_session(kernel_name);
2276 this.start_session(kernel_name);
2267 }
2277 }
2268 // load our checkpoint list
2278 // load our checkpoint list
2269 this.list_checkpoints();
2279 this.list_checkpoints();
2270
2280
2271 // load toolbar state
2281 // load toolbar state
2272 if (this.metadata.celltoolbar) {
2282 if (this.metadata.celltoolbar) {
2273 celltoolbar.CellToolbar.global_show();
2283 celltoolbar.CellToolbar.global_show();
2274 celltoolbar.CellToolbar.activate_preset(this.metadata.celltoolbar);
2284 celltoolbar.CellToolbar.activate_preset(this.metadata.celltoolbar);
2275 } else {
2285 } else {
2276 celltoolbar.CellToolbar.global_hide();
2286 celltoolbar.CellToolbar.global_hide();
2277 }
2287 }
2278
2288
2279 if (!this.writable) {
2289 if (!this.writable) {
2280 this.set_autosave_interval(0);
2290 this.set_autosave_interval(0);
2281 this.events.trigger('notebook_read_only.Notebook');
2291 this.events.trigger('notebook_read_only.Notebook');
2282 }
2292 }
2283
2293
2284 // now that we're fully loaded, it is safe to restore save functionality
2294 // now that we're fully loaded, it is safe to restore save functionality
2285 this._fully_loaded = true;
2295 this._fully_loaded = true;
2286 this.events.trigger('notebook_loaded.Notebook');
2296 this.events.trigger('notebook_loaded.Notebook');
2287 };
2297 };
2288
2298
2289 /**
2299 /**
2290 * Failure callback for loading a notebook from the server.
2300 * Failure callback for loading a notebook from the server.
2291 *
2301 *
2292 * @method load_notebook_error
2302 * @method load_notebook_error
2293 * @param {Error} error
2303 * @param {Error} error
2294 */
2304 */
2295 Notebook.prototype.load_notebook_error = function (error) {
2305 Notebook.prototype.load_notebook_error = function (error) {
2296 this.events.trigger('notebook_load_failed.Notebook', error);
2306 this.events.trigger('notebook_load_failed.Notebook', error);
2297 var msg;
2307 var msg;
2298 if (error.name === utils.XHR_ERROR && error.xhr.status === 500) {
2308 if (error.name === utils.XHR_ERROR && error.xhr.status === 500) {
2299 utils.log_ajax_error(error.xhr, error.xhr_status, error.xhr_error);
2309 utils.log_ajax_error(error.xhr, error.xhr_status, error.xhr_error);
2300 msg = "An unknown error occurred while loading this notebook. " +
2310 msg = "An unknown error occurred while loading this notebook. " +
2301 "This version can load notebook formats " +
2311 "This version can load notebook formats " +
2302 "v" + this.nbformat + " or earlier. See the server log for details.";
2312 "v" + this.nbformat + " or earlier. See the server log for details.";
2303 } else {
2313 } else {
2304 msg = error.message;
2314 msg = error.message;
2305 }
2315 }
2306 dialog.modal({
2316 dialog.modal({
2307 notebook: this,
2317 notebook: this,
2308 keyboard_manager: this.keyboard_manager,
2318 keyboard_manager: this.keyboard_manager,
2309 title: "Error loading notebook",
2319 title: "Error loading notebook",
2310 body : msg,
2320 body : msg,
2311 buttons : {
2321 buttons : {
2312 "OK": {}
2322 "OK": {}
2313 }
2323 }
2314 });
2324 });
2315 };
2325 };
2316
2326
2317 /********************* checkpoint-related *********************/
2327 /********************* checkpoint-related *********************/
2318
2328
2319 /**
2329 /**
2320 * Save the notebook then immediately create a checkpoint.
2330 * Save the notebook then immediately create a checkpoint.
2321 *
2331 *
2322 * @method save_checkpoint
2332 * @method save_checkpoint
2323 */
2333 */
2324 Notebook.prototype.save_checkpoint = function () {
2334 Notebook.prototype.save_checkpoint = function () {
2325 this._checkpoint_after_save = true;
2335 this._checkpoint_after_save = true;
2326 this.save_notebook();
2336 this.save_notebook();
2327 };
2337 };
2328
2338
2329 /**
2339 /**
2330 * Add a checkpoint for this notebook.
2340 * Add a checkpoint for this notebook.
2331 * for use as a callback from checkpoint creation.
2341 * for use as a callback from checkpoint creation.
2332 *
2342 *
2333 * @method add_checkpoint
2343 * @method add_checkpoint
2334 */
2344 */
2335 Notebook.prototype.add_checkpoint = function (checkpoint) {
2345 Notebook.prototype.add_checkpoint = function (checkpoint) {
2336 var found = false;
2346 var found = false;
2337 for (var i = 0; i < this.checkpoints.length; i++) {
2347 for (var i = 0; i < this.checkpoints.length; i++) {
2338 var existing = this.checkpoints[i];
2348 var existing = this.checkpoints[i];
2339 if (existing.id == checkpoint.id) {
2349 if (existing.id == checkpoint.id) {
2340 found = true;
2350 found = true;
2341 this.checkpoints[i] = checkpoint;
2351 this.checkpoints[i] = checkpoint;
2342 break;
2352 break;
2343 }
2353 }
2344 }
2354 }
2345 if (!found) {
2355 if (!found) {
2346 this.checkpoints.push(checkpoint);
2356 this.checkpoints.push(checkpoint);
2347 }
2357 }
2348 this.last_checkpoint = this.checkpoints[this.checkpoints.length - 1];
2358 this.last_checkpoint = this.checkpoints[this.checkpoints.length - 1];
2349 };
2359 };
2350
2360
2351 /**
2361 /**
2352 * List checkpoints for this notebook.
2362 * List checkpoints for this notebook.
2353 *
2363 *
2354 * @method list_checkpoints
2364 * @method list_checkpoints
2355 */
2365 */
2356 Notebook.prototype.list_checkpoints = function () {
2366 Notebook.prototype.list_checkpoints = function () {
2357 var that = this;
2367 var that = this;
2358 this.contents.list_checkpoints(this.notebook_path).then(
2368 this.contents.list_checkpoints(this.notebook_path).then(
2359 $.proxy(this.list_checkpoints_success, this),
2369 $.proxy(this.list_checkpoints_success, this),
2360 function(error) {
2370 function(error) {
2361 that.events.trigger('list_checkpoints_failed.Notebook', error);
2371 that.events.trigger('list_checkpoints_failed.Notebook', error);
2362 }
2372 }
2363 );
2373 );
2364 };
2374 };
2365
2375
2366 /**
2376 /**
2367 * Success callback for listing checkpoints.
2377 * Success callback for listing checkpoints.
2368 *
2378 *
2369 * @method list_checkpoint_success
2379 * @method list_checkpoint_success
2370 * @param {Object} data JSON representation of a checkpoint
2380 * @param {Object} data JSON representation of a checkpoint
2371 */
2381 */
2372 Notebook.prototype.list_checkpoints_success = function (data) {
2382 Notebook.prototype.list_checkpoints_success = function (data) {
2373 this.checkpoints = data;
2383 this.checkpoints = data;
2374 if (data.length) {
2384 if (data.length) {
2375 this.last_checkpoint = data[data.length - 1];
2385 this.last_checkpoint = data[data.length - 1];
2376 } else {
2386 } else {
2377 this.last_checkpoint = null;
2387 this.last_checkpoint = null;
2378 }
2388 }
2379 this.events.trigger('checkpoints_listed.Notebook', [data]);
2389 this.events.trigger('checkpoints_listed.Notebook', [data]);
2380 };
2390 };
2381
2391
2382 /**
2392 /**
2383 * Create a checkpoint of this notebook on the server from the most recent save.
2393 * Create a checkpoint of this notebook on the server from the most recent save.
2384 *
2394 *
2385 * @method create_checkpoint
2395 * @method create_checkpoint
2386 */
2396 */
2387 Notebook.prototype.create_checkpoint = function () {
2397 Notebook.prototype.create_checkpoint = function () {
2388 var that = this;
2398 var that = this;
2389 this.contents.create_checkpoint(this.notebook_path).then(
2399 this.contents.create_checkpoint(this.notebook_path).then(
2390 $.proxy(this.create_checkpoint_success, this),
2400 $.proxy(this.create_checkpoint_success, this),
2391 function (error) {
2401 function (error) {
2392 that.events.trigger('checkpoint_failed.Notebook', error);
2402 that.events.trigger('checkpoint_failed.Notebook', error);
2393 }
2403 }
2394 );
2404 );
2395 };
2405 };
2396
2406
2397 /**
2407 /**
2398 * Success callback for creating a checkpoint.
2408 * Success callback for creating a checkpoint.
2399 *
2409 *
2400 * @method create_checkpoint_success
2410 * @method create_checkpoint_success
2401 * @param {Object} data JSON representation of a checkpoint
2411 * @param {Object} data JSON representation of a checkpoint
2402 */
2412 */
2403 Notebook.prototype.create_checkpoint_success = function (data) {
2413 Notebook.prototype.create_checkpoint_success = function (data) {
2404 this.add_checkpoint(data);
2414 this.add_checkpoint(data);
2405 this.events.trigger('checkpoint_created.Notebook', data);
2415 this.events.trigger('checkpoint_created.Notebook', data);
2406 };
2416 };
2407
2417
2408 Notebook.prototype.restore_checkpoint_dialog = function (checkpoint) {
2418 Notebook.prototype.restore_checkpoint_dialog = function (checkpoint) {
2409 var that = this;
2419 var that = this;
2410 checkpoint = checkpoint || this.last_checkpoint;
2420 checkpoint = checkpoint || this.last_checkpoint;
2411 if ( ! checkpoint ) {
2421 if ( ! checkpoint ) {
2412 console.log("restore dialog, but no checkpoint to restore to!");
2422 console.log("restore dialog, but no checkpoint to restore to!");
2413 return;
2423 return;
2414 }
2424 }
2415 var body = $('<div/>').append(
2425 var body = $('<div/>').append(
2416 $('<p/>').addClass("p-space").text(
2426 $('<p/>').addClass("p-space").text(
2417 "Are you sure you want to revert the notebook to " +
2427 "Are you sure you want to revert the notebook to " +
2418 "the latest checkpoint?"
2428 "the latest checkpoint?"
2419 ).append(
2429 ).append(
2420 $("<strong/>").text(
2430 $("<strong/>").text(
2421 " This cannot be undone."
2431 " This cannot be undone."
2422 )
2432 )
2423 )
2433 )
2424 ).append(
2434 ).append(
2425 $('<p/>').addClass("p-space").text("The checkpoint was last updated at:")
2435 $('<p/>').addClass("p-space").text("The checkpoint was last updated at:")
2426 ).append(
2436 ).append(
2427 $('<p/>').addClass("p-space").text(
2437 $('<p/>').addClass("p-space").text(
2428 Date(checkpoint.last_modified)
2438 Date(checkpoint.last_modified)
2429 ).css("text-align", "center")
2439 ).css("text-align", "center")
2430 );
2440 );
2431
2441
2432 dialog.modal({
2442 dialog.modal({
2433 notebook: this,
2443 notebook: this,
2434 keyboard_manager: this.keyboard_manager,
2444 keyboard_manager: this.keyboard_manager,
2435 title : "Revert notebook to checkpoint",
2445 title : "Revert notebook to checkpoint",
2436 body : body,
2446 body : body,
2437 buttons : {
2447 buttons : {
2438 Revert : {
2448 Revert : {
2439 class : "btn-danger",
2449 class : "btn-danger",
2440 click : function () {
2450 click : function () {
2441 that.restore_checkpoint(checkpoint.id);
2451 that.restore_checkpoint(checkpoint.id);
2442 }
2452 }
2443 },
2453 },
2444 Cancel : {}
2454 Cancel : {}
2445 }
2455 }
2446 });
2456 });
2447 };
2457 };
2448
2458
2449 /**
2459 /**
2450 * Restore the notebook to a checkpoint state.
2460 * Restore the notebook to a checkpoint state.
2451 *
2461 *
2452 * @method restore_checkpoint
2462 * @method restore_checkpoint
2453 * @param {String} checkpoint ID
2463 * @param {String} checkpoint ID
2454 */
2464 */
2455 Notebook.prototype.restore_checkpoint = function (checkpoint) {
2465 Notebook.prototype.restore_checkpoint = function (checkpoint) {
2456 this.events.trigger('notebook_restoring.Notebook', checkpoint);
2466 this.events.trigger('notebook_restoring.Notebook', checkpoint);
2457 var that = this;
2467 var that = this;
2458 this.contents.restore_checkpoint(this.notebook_path, checkpoint).then(
2468 this.contents.restore_checkpoint(this.notebook_path, checkpoint).then(
2459 $.proxy(this.restore_checkpoint_success, this),
2469 $.proxy(this.restore_checkpoint_success, this),
2460 function (error) {
2470 function (error) {
2461 that.events.trigger('checkpoint_restore_failed.Notebook', error);
2471 that.events.trigger('checkpoint_restore_failed.Notebook', error);
2462 }
2472 }
2463 );
2473 );
2464 };
2474 };
2465
2475
2466 /**
2476 /**
2467 * Success callback for restoring a notebook to a checkpoint.
2477 * Success callback for restoring a notebook to a checkpoint.
2468 *
2478 *
2469 * @method restore_checkpoint_success
2479 * @method restore_checkpoint_success
2470 */
2480 */
2471 Notebook.prototype.restore_checkpoint_success = function () {
2481 Notebook.prototype.restore_checkpoint_success = function () {
2472 this.events.trigger('checkpoint_restored.Notebook');
2482 this.events.trigger('checkpoint_restored.Notebook');
2473 this.load_notebook(this.notebook_path);
2483 this.load_notebook(this.notebook_path);
2474 };
2484 };
2475
2485
2476 /**
2486 /**
2477 * Delete a notebook checkpoint.
2487 * Delete a notebook checkpoint.
2478 *
2488 *
2479 * @method delete_checkpoint
2489 * @method delete_checkpoint
2480 * @param {String} checkpoint ID
2490 * @param {String} checkpoint ID
2481 */
2491 */
2482 Notebook.prototype.delete_checkpoint = function (checkpoint) {
2492 Notebook.prototype.delete_checkpoint = function (checkpoint) {
2483 this.events.trigger('notebook_restoring.Notebook', checkpoint);
2493 this.events.trigger('notebook_restoring.Notebook', checkpoint);
2484 var that = this;
2494 var that = this;
2485 this.contents.delete_checkpoint(this.notebook_path, checkpoint).then(
2495 this.contents.delete_checkpoint(this.notebook_path, checkpoint).then(
2486 $.proxy(this.delete_checkpoint_success, this),
2496 $.proxy(this.delete_checkpoint_success, this),
2487 function (error) {
2497 function (error) {
2488 that.events.trigger('checkpoint_delete_failed.Notebook', error);
2498 that.events.trigger('checkpoint_delete_failed.Notebook', error);
2489 }
2499 }
2490 );
2500 );
2491 };
2501 };
2492
2502
2493 /**
2503 /**
2494 * Success callback for deleting a notebook checkpoint
2504 * Success callback for deleting a notebook checkpoint
2495 *
2505 *
2496 * @method delete_checkpoint_success
2506 * @method delete_checkpoint_success
2497 */
2507 */
2498 Notebook.prototype.delete_checkpoint_success = function () {
2508 Notebook.prototype.delete_checkpoint_success = function () {
2499 this.events.trigger('checkpoint_deleted.Notebook');
2509 this.events.trigger('checkpoint_deleted.Notebook');
2500 this.load_notebook(this.notebook_path);
2510 this.load_notebook(this.notebook_path);
2501 };
2511 };
2502
2512
2503
2513
2504 // For backwards compatability.
2514 // For backwards compatability.
2505 IPython.Notebook = Notebook;
2515 IPython.Notebook = Notebook;
2506
2516
2507 return {'Notebook': Notebook};
2517 return {'Notebook': Notebook};
2508 });
2518 });
@@ -1,962 +1,980 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 'jqueryui',
6 'jqueryui',
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 'components/marked/lib/marked',
11 'components/marked/lib/marked',
12 ], function(IPython, $, utils, security, keyboard, mathjaxutils, marked) {
12 ], function(IPython, $, utils, security, keyboard, mathjaxutils, marked) {
13 "use strict";
13 "use strict";
14
14
15 /**
15 /**
16 * @class OutputArea
16 * @class OutputArea
17 *
17 *
18 * @constructor
18 * @constructor
19 */
19 */
20
20
21 var OutputArea = function (options) {
21 var OutputArea = function (options) {
22 this.selector = options.selector;
22 this.selector = options.selector;
23 this.events = options.events;
23 this.events = options.events;
24 this.keyboard_manager = options.keyboard_manager;
24 this.keyboard_manager = options.keyboard_manager;
25 this.wrapper = $(options.selector);
25 this.wrapper = $(options.selector);
26 this.outputs = [];
26 this.outputs = [];
27 this.collapsed = false;
27 this.collapsed = false;
28 this.scrolled = false;
28 this.scrolled = false;
29 this.trusted = true;
29 this.trusted = true;
30 this.clear_queued = null;
30 this.clear_queued = null;
31 if (options.prompt_area === undefined) {
31 if (options.prompt_area === undefined) {
32 this.prompt_area = true;
32 this.prompt_area = true;
33 } else {
33 } else {
34 this.prompt_area = options.prompt_area;
34 this.prompt_area = options.prompt_area;
35 }
35 }
36 this.create_elements();
36 this.create_elements();
37 this.style();
37 this.style();
38 this.bind_events();
38 this.bind_events();
39 };
39 };
40
40
41
41
42 /**
42 /**
43 * Class prototypes
43 * Class prototypes
44 **/
44 **/
45
45
46 OutputArea.prototype.create_elements = function () {
46 OutputArea.prototype.create_elements = function () {
47 this.element = $("<div/>");
47 this.element = $("<div/>");
48 this.collapse_button = $("<div/>");
48 this.collapse_button = $("<div/>");
49 this.prompt_overlay = $("<div/>");
49 this.prompt_overlay = $("<div/>");
50 this.wrapper.append(this.prompt_overlay);
50 this.wrapper.append(this.prompt_overlay);
51 this.wrapper.append(this.element);
51 this.wrapper.append(this.element);
52 this.wrapper.append(this.collapse_button);
52 this.wrapper.append(this.collapse_button);
53 };
53 };
54
54
55
55
56 OutputArea.prototype.style = function () {
56 OutputArea.prototype.style = function () {
57 this.collapse_button.hide();
57 this.collapse_button.hide();
58 this.prompt_overlay.hide();
58 this.prompt_overlay.hide();
59
59
60 this.wrapper.addClass('output_wrapper');
60 this.wrapper.addClass('output_wrapper');
61 this.element.addClass('output');
61 this.element.addClass('output');
62
62
63 this.collapse_button.addClass("btn btn-default output_collapsed");
63 this.collapse_button.addClass("btn btn-default output_collapsed");
64 this.collapse_button.attr('title', 'click to expand output');
64 this.collapse_button.attr('title', 'click to expand output');
65 this.collapse_button.text('. . .');
65 this.collapse_button.text('. . .');
66
66
67 this.prompt_overlay.addClass('out_prompt_overlay prompt');
67 this.prompt_overlay.addClass('out_prompt_overlay prompt');
68 this.prompt_overlay.attr('title', 'click to expand output; double click to hide output');
68 this.prompt_overlay.attr('title', 'click to expand output; double click to hide output');
69
69
70 this.collapse();
70 this.collapse();
71 };
71 };
72
72
73 /**
73 /**
74 * Should the OutputArea scroll?
74 * Should the OutputArea scroll?
75 * Returns whether the height (in lines) exceeds a threshold.
75 * Returns whether the height (in lines) exceeds a threshold.
76 *
76 *
77 * @private
77 * @private
78 * @method _should_scroll
78 * @method _should_scroll
79 * @param [lines=100]{Integer}
79 * @param [lines=100]{Integer}
80 * @return {Bool}
80 * @return {Bool}
81 *
81 *
82 */
82 */
83 OutputArea.prototype._should_scroll = function (lines) {
83 OutputArea.prototype._should_scroll = function (lines) {
84 if (lines <=0 ){ return; }
84 if (lines <=0 ){ return; }
85 if (!lines) {
85 if (!lines) {
86 lines = 100;
86 lines = 100;
87 }
87 }
88 // line-height from http://stackoverflow.com/questions/1185151
88 // line-height from http://stackoverflow.com/questions/1185151
89 var fontSize = this.element.css('font-size');
89 var fontSize = this.element.css('font-size');
90 var lineHeight = Math.floor(parseInt(fontSize.replace('px','')) * 1.5);
90 var lineHeight = Math.floor(parseInt(fontSize.replace('px','')) * 1.5);
91
91
92 return (this.element.height() > lines * lineHeight);
92 return (this.element.height() > lines * lineHeight);
93 };
93 };
94
94
95
95
96 OutputArea.prototype.bind_events = function () {
96 OutputArea.prototype.bind_events = function () {
97 var that = this;
97 var that = this;
98 this.prompt_overlay.dblclick(function () { that.toggle_output(); });
98 this.prompt_overlay.dblclick(function () { that.toggle_output(); });
99 this.prompt_overlay.click(function () { that.toggle_scroll(); });
99 this.prompt_overlay.click(function () { that.toggle_scroll(); });
100
100
101 this.element.resize(function () {
101 this.element.resize(function () {
102 // FIXME: Firefox on Linux misbehaves, so automatic scrolling is disabled
102 // FIXME: Firefox on Linux misbehaves, so automatic scrolling is disabled
103 if ( utils.browser[0] === "Firefox" ) {
103 if ( utils.browser[0] === "Firefox" ) {
104 return;
104 return;
105 }
105 }
106 // maybe scroll output,
106 // maybe scroll output,
107 // if it's grown large enough and hasn't already been scrolled.
107 // if it's grown large enough and hasn't already been scrolled.
108 if ( !that.scrolled && that._should_scroll(OutputArea.auto_scroll_threshold)) {
108 if ( !that.scrolled && that._should_scroll(OutputArea.auto_scroll_threshold)) {
109 that.scroll_area();
109 that.scroll_area();
110 }
110 }
111 });
111 });
112 this.collapse_button.click(function () {
112 this.collapse_button.click(function () {
113 that.expand();
113 that.expand();
114 });
114 });
115 };
115 };
116
116
117
117
118 OutputArea.prototype.collapse = function () {
118 OutputArea.prototype.collapse = function () {
119 if (!this.collapsed) {
119 if (!this.collapsed) {
120 this.element.hide();
120 this.element.hide();
121 this.prompt_overlay.hide();
121 this.prompt_overlay.hide();
122 if (this.element.html()){
122 if (this.element.html()){
123 this.collapse_button.show();
123 this.collapse_button.show();
124 }
124 }
125 this.collapsed = true;
125 this.collapsed = true;
126 }
126 }
127 };
127 };
128
128
129
129
130 OutputArea.prototype.expand = function () {
130 OutputArea.prototype.expand = function () {
131 if (this.collapsed) {
131 if (this.collapsed) {
132 this.collapse_button.hide();
132 this.collapse_button.hide();
133 this.element.show();
133 this.element.show();
134 this.prompt_overlay.show();
134 this.prompt_overlay.show();
135 this.collapsed = false;
135 this.collapsed = false;
136 }
136 }
137 };
137 };
138
138
139
139
140 OutputArea.prototype.toggle_output = function () {
140 OutputArea.prototype.toggle_output = function () {
141 if (this.collapsed) {
141 if (this.collapsed) {
142 this.expand();
142 this.expand();
143 } else {
143 } else {
144 this.collapse();
144 this.collapse();
145 }
145 }
146 };
146 };
147
147
148
148
149 OutputArea.prototype.scroll_area = function () {
149 OutputArea.prototype.scroll_area = function () {
150 this.element.addClass('output_scroll');
150 this.element.addClass('output_scroll');
151 this.prompt_overlay.attr('title', 'click to unscroll output; double click to hide');
151 this.prompt_overlay.attr('title', 'click to unscroll output; double click to hide');
152 this.scrolled = true;
152 this.scrolled = true;
153 };
153 };
154
154
155
155
156 OutputArea.prototype.unscroll_area = function () {
156 OutputArea.prototype.unscroll_area = function () {
157 this.element.removeClass('output_scroll');
157 this.element.removeClass('output_scroll');
158 this.prompt_overlay.attr('title', 'click to scroll output; double click to hide');
158 this.prompt_overlay.attr('title', 'click to scroll output; double click to hide');
159 this.scrolled = false;
159 this.scrolled = false;
160 };
160 };
161
161
162 /**
162 /**
163 *
163 *
164 * Scroll OutputArea if height supperior than a threshold (in lines).
164 * Scroll OutputArea if height supperior than a threshold (in lines).
165 *
165 *
166 * Threshold is a maximum number of lines. If unspecified, defaults to
166 * Threshold is a maximum number of lines. If unspecified, defaults to
167 * OutputArea.minimum_scroll_threshold.
167 * OutputArea.minimum_scroll_threshold.
168 *
168 *
169 * Negative threshold will prevent the OutputArea from ever scrolling.
169 * Negative threshold will prevent the OutputArea from ever scrolling.
170 *
170 *
171 * @method scroll_if_long
171 * @method scroll_if_long
172 *
172 *
173 * @param [lines=20]{Number} Default to 20 if not set,
173 * @param [lines=20]{Number} Default to 20 if not set,
174 * behavior undefined for value of `0`.
174 * behavior undefined for value of `0`.
175 *
175 *
176 **/
176 **/
177 OutputArea.prototype.scroll_if_long = function (lines) {
177 OutputArea.prototype.scroll_if_long = function (lines) {
178 var n = lines | OutputArea.minimum_scroll_threshold;
178 var n = lines | OutputArea.minimum_scroll_threshold;
179 if(n <= 0){
179 if(n <= 0){
180 return;
180 return;
181 }
181 }
182
182
183 if (this._should_scroll(n)) {
183 if (this._should_scroll(n)) {
184 // only allow scrolling long-enough output
184 // only allow scrolling long-enough output
185 this.scroll_area();
185 this.scroll_area();
186 }
186 }
187 };
187 };
188
188
189
189
190 OutputArea.prototype.toggle_scroll = function () {
190 OutputArea.prototype.toggle_scroll = function () {
191 if (this.scrolled) {
191 if (this.scrolled) {
192 this.unscroll_area();
192 this.unscroll_area();
193 } else {
193 } else {
194 // only allow scrolling long-enough output
194 // only allow scrolling long-enough output
195 this.scroll_if_long();
195 this.scroll_if_long();
196 }
196 }
197 };
197 };
198
198
199
199
200 // typeset with MathJax if MathJax is available
200 // typeset with MathJax if MathJax is available
201 OutputArea.prototype.typeset = function () {
201 OutputArea.prototype.typeset = function () {
202 if (window.MathJax){
202 if (window.MathJax){
203 MathJax.Hub.Queue(["Typeset",MathJax.Hub]);
203 MathJax.Hub.Queue(["Typeset",MathJax.Hub]);
204 }
204 }
205 };
205 };
206
206
207
207
208 OutputArea.prototype.handle_output = function (msg) {
208 OutputArea.prototype.handle_output = function (msg) {
209 var json = {};
209 var json = {};
210 var msg_type = json.output_type = msg.header.msg_type;
210 var msg_type = json.output_type = msg.header.msg_type;
211 var content = msg.content;
211 var content = msg.content;
212 if (msg_type === "stream") {
212 if (msg_type === "stream") {
213 json.text = content.text;
213 json.text = content.text;
214 json.name = content.name;
214 json.name = content.name;
215 } else if (msg_type === "display_data") {
215 } else if (msg_type === "display_data") {
216 json.data = content.data;
216 json.data = content.data;
217 json.output_type = msg_type;
217 json.output_type = msg_type;
218 json.metadata = content.metadata;
218 json.metadata = content.metadata;
219 } else if (msg_type === "execute_result") {
219 } else if (msg_type === "execute_result") {
220 json.data = content.data;
220 json.data = content.data;
221 json.output_type = msg_type;
221 json.output_type = msg_type;
222 json.metadata = content.metadata;
222 json.metadata = content.metadata;
223 json.execution_count = content.execution_count;
223 json.execution_count = content.execution_count;
224 } else if (msg_type === "error") {
224 } else if (msg_type === "error") {
225 json.ename = content.ename;
225 json.ename = content.ename;
226 json.evalue = content.evalue;
226 json.evalue = content.evalue;
227 json.traceback = content.traceback;
227 json.traceback = content.traceback;
228 } else {
228 } else {
229 console.log("unhandled output message", msg);
229 console.log("unhandled output message", msg);
230 return;
230 return;
231 }
231 }
232 this.append_output(json);
232 this.append_output(json);
233 };
233 };
234
234
235
235
236 OutputArea.output_types = [
236 OutputArea.output_types = [
237 'application/javascript',
237 'application/javascript',
238 'text/html',
238 'text/html',
239 'text/markdown',
239 'text/markdown',
240 'text/latex',
240 'text/latex',
241 'image/svg+xml',
241 'image/svg+xml',
242 'image/png',
242 'image/png',
243 'image/jpeg',
243 'image/jpeg',
244 'application/pdf',
244 'application/pdf',
245 'text/plain'
245 'text/plain'
246 ];
246 ];
247
247
248 OutputArea.prototype.validate_mimebundle = function (json) {
248 OutputArea.prototype.validate_mimebundle = function (json) {
249 // scrub invalid outputs
249 /**
250 * scrub invalid outputs
251 */
250 var data = json.data;
252 var data = json.data;
251 $.map(OutputArea.output_types, function(key){
253 $.map(OutputArea.output_types, function(key){
252 if (key !== 'application/json' &&
254 if (key !== 'application/json' &&
253 data[key] !== undefined &&
255 data[key] !== undefined &&
254 typeof data[key] !== 'string'
256 typeof data[key] !== 'string'
255 ) {
257 ) {
256 console.log("Invalid type for " + key, data[key]);
258 console.log("Invalid type for " + key, data[key]);
257 delete data[key];
259 delete data[key];
258 }
260 }
259 });
261 });
260 return json;
262 return json;
261 };
263 };
262
264
263 OutputArea.prototype.append_output = function (json) {
265 OutputArea.prototype.append_output = function (json) {
264 this.expand();
266 this.expand();
265
267
266 // Clear the output if clear is queued.
268 // Clear the output if clear is queued.
267 var needs_height_reset = false;
269 var needs_height_reset = false;
268 if (this.clear_queued) {
270 if (this.clear_queued) {
269 this.clear_output(false);
271 this.clear_output(false);
270 needs_height_reset = true;
272 needs_height_reset = true;
271 }
273 }
272
274
273 var record_output = true;
275 var record_output = true;
274 switch(json.output_type) {
276 switch(json.output_type) {
275 case 'execute_result':
277 case 'execute_result':
276 json = this.validate_mimebundle(json);
278 json = this.validate_mimebundle(json);
277 this.append_execute_result(json);
279 this.append_execute_result(json);
278 break;
280 break;
279 case 'stream':
281 case 'stream':
280 // append_stream might have merged the output with earlier stream output
282 // append_stream might have merged the output with earlier stream output
281 record_output = this.append_stream(json);
283 record_output = this.append_stream(json);
282 break;
284 break;
283 case 'error':
285 case 'error':
284 this.append_error(json);
286 this.append_error(json);
285 break;
287 break;
286 case 'display_data':
288 case 'display_data':
287 // append handled below
289 // append handled below
288 json = this.validate_mimebundle(json);
290 json = this.validate_mimebundle(json);
289 break;
291 break;
290 default:
292 default:
291 console.log("unrecognized output type: " + json.output_type);
293 console.log("unrecognized output type: " + json.output_type);
292 this.append_unrecognized(json);
294 this.append_unrecognized(json);
293 }
295 }
294
296
295 // We must release the animation fixed height in a callback since Gecko
297 // We must release the animation fixed height in a callback since Gecko
296 // (FireFox) doesn't render the image immediately as the data is
298 // (FireFox) doesn't render the image immediately as the data is
297 // available.
299 // available.
298 var that = this;
300 var that = this;
299 var handle_appended = function ($el) {
301 var handle_appended = function ($el) {
300 // Only reset the height to automatic if the height is currently
302 /**
301 // fixed (done by wait=True flag on clear_output).
303 * Only reset the height to automatic if the height is currently
304 * fixed (done by wait=True flag on clear_output).
305 */
302 if (needs_height_reset) {
306 if (needs_height_reset) {
303 that.element.height('');
307 that.element.height('');
304 }
308 }
305 that.element.trigger('resize');
309 that.element.trigger('resize');
306 };
310 };
307 if (json.output_type === 'display_data') {
311 if (json.output_type === 'display_data') {
308 this.append_display_data(json, handle_appended);
312 this.append_display_data(json, handle_appended);
309 } else {
313 } else {
310 handle_appended();
314 handle_appended();
311 }
315 }
312
316
313 if (record_output) {
317 if (record_output) {
314 this.outputs.push(json);
318 this.outputs.push(json);
315 }
319 }
316 };
320 };
317
321
318
322
319 OutputArea.prototype.create_output_area = function () {
323 OutputArea.prototype.create_output_area = function () {
320 var oa = $("<div/>").addClass("output_area");
324 var oa = $("<div/>").addClass("output_area");
321 if (this.prompt_area) {
325 if (this.prompt_area) {
322 oa.append($('<div/>').addClass('prompt'));
326 oa.append($('<div/>').addClass('prompt'));
323 }
327 }
324 return oa;
328 return oa;
325 };
329 };
326
330
327
331
328 function _get_metadata_key(metadata, key, mime) {
332 function _get_metadata_key(metadata, key, mime) {
329 var mime_md = metadata[mime];
333 var mime_md = metadata[mime];
330 // mime-specific higher priority
334 // mime-specific higher priority
331 if (mime_md && mime_md[key] !== undefined) {
335 if (mime_md && mime_md[key] !== undefined) {
332 return mime_md[key];
336 return mime_md[key];
333 }
337 }
334 // fallback on global
338 // fallback on global
335 return metadata[key];
339 return metadata[key];
336 }
340 }
337
341
338 OutputArea.prototype.create_output_subarea = function(md, classes, mime) {
342 OutputArea.prototype.create_output_subarea = function(md, classes, mime) {
339 var subarea = $('<div/>').addClass('output_subarea').addClass(classes);
343 var subarea = $('<div/>').addClass('output_subarea').addClass(classes);
340 if (_get_metadata_key(md, 'isolated', mime)) {
344 if (_get_metadata_key(md, 'isolated', mime)) {
341 // Create an iframe to isolate the subarea from the rest of the
345 // Create an iframe to isolate the subarea from the rest of the
342 // document
346 // document
343 var iframe = $('<iframe/>').addClass('box-flex1');
347 var iframe = $('<iframe/>').addClass('box-flex1');
344 iframe.css({'height':1, 'width':'100%', 'display':'block'});
348 iframe.css({'height':1, 'width':'100%', 'display':'block'});
345 iframe.attr('frameborder', 0);
349 iframe.attr('frameborder', 0);
346 iframe.attr('scrolling', 'auto');
350 iframe.attr('scrolling', 'auto');
347
351
348 // Once the iframe is loaded, the subarea is dynamically inserted
352 // Once the iframe is loaded, the subarea is dynamically inserted
349 iframe.on('load', function() {
353 iframe.on('load', function() {
350 // Workaround needed by Firefox, to properly render svg inside
354 // Workaround needed by Firefox, to properly render svg inside
351 // iframes, see http://stackoverflow.com/questions/10177190/
355 // iframes, see http://stackoverflow.com/questions/10177190/
352 // svg-dynamically-added-to-iframe-does-not-render-correctly
356 // svg-dynamically-added-to-iframe-does-not-render-correctly
353 this.contentDocument.open();
357 this.contentDocument.open();
354
358
355 // Insert the subarea into the iframe
359 // Insert the subarea into the iframe
356 // We must directly write the html. When using Jquery's append
360 // We must directly write the html. When using Jquery's append
357 // method, javascript is evaluated in the parent document and
361 // method, javascript is evaluated in the parent document and
358 // not in the iframe document. At this point, subarea doesn't
362 // not in the iframe document. At this point, subarea doesn't
359 // contain any user content.
363 // contain any user content.
360 this.contentDocument.write(subarea.html());
364 this.contentDocument.write(subarea.html());
361
365
362 this.contentDocument.close();
366 this.contentDocument.close();
363
367
364 var body = this.contentDocument.body;
368 var body = this.contentDocument.body;
365 // Adjust the iframe height automatically
369 // Adjust the iframe height automatically
366 iframe.height(body.scrollHeight + 'px');
370 iframe.height(body.scrollHeight + 'px');
367 });
371 });
368
372
369 // Elements should be appended to the inner subarea and not to the
373 // Elements should be appended to the inner subarea and not to the
370 // iframe
374 // iframe
371 iframe.append = function(that) {
375 iframe.append = function(that) {
372 subarea.append(that);
376 subarea.append(that);
373 };
377 };
374
378
375 return iframe;
379 return iframe;
376 } else {
380 } else {
377 return subarea;
381 return subarea;
378 }
382 }
379 };
383 };
380
384
381
385
382 OutputArea.prototype._append_javascript_error = function (err, element) {
386 OutputArea.prototype._append_javascript_error = function (err, element) {
383 // display a message when a javascript error occurs in display output
387 /**
388 * display a message when a javascript error occurs in display output
389 */
384 var msg = "Javascript error adding output!";
390 var msg = "Javascript error adding output!";
385 if ( element === undefined ) return;
391 if ( element === undefined ) return;
386 element
392 element
387 .append($('<div/>').text(msg).addClass('js-error'))
393 .append($('<div/>').text(msg).addClass('js-error'))
388 .append($('<div/>').text(err.toString()).addClass('js-error'))
394 .append($('<div/>').text(err.toString()).addClass('js-error'))
389 .append($('<div/>').text('See your browser Javascript console for more details.').addClass('js-error'));
395 .append($('<div/>').text('See your browser Javascript console for more details.').addClass('js-error'));
390 };
396 };
391
397
392 OutputArea.prototype._safe_append = function (toinsert) {
398 OutputArea.prototype._safe_append = function (toinsert) {
393 // safely append an item to the document
399 /**
394 // this is an object created by user code,
400 * safely append an item to the document
395 // and may have errors, which should not be raised
401 * this is an object created by user code,
396 // under any circumstances.
402 * and may have errors, which should not be raised
403 * under any circumstances.
404 */
397 try {
405 try {
398 this.element.append(toinsert);
406 this.element.append(toinsert);
399 } catch(err) {
407 } catch(err) {
400 console.log(err);
408 console.log(err);
401 // Create an actual output_area and output_subarea, which creates
409 // Create an actual output_area and output_subarea, which creates
402 // the prompt area and the proper indentation.
410 // the prompt area and the proper indentation.
403 var toinsert = this.create_output_area();
411 var toinsert = this.create_output_area();
404 var subarea = $('<div/>').addClass('output_subarea');
412 var subarea = $('<div/>').addClass('output_subarea');
405 toinsert.append(subarea);
413 toinsert.append(subarea);
406 this._append_javascript_error(err, subarea);
414 this._append_javascript_error(err, subarea);
407 this.element.append(toinsert);
415 this.element.append(toinsert);
408 }
416 }
409
417
410 // Notify others of changes.
418 // Notify others of changes.
411 this.element.trigger('changed');
419 this.element.trigger('changed');
412 };
420 };
413
421
414
422
415 OutputArea.prototype.append_execute_result = function (json) {
423 OutputArea.prototype.append_execute_result = function (json) {
416 var n = json.execution_count || ' ';
424 var n = json.execution_count || ' ';
417 var toinsert = this.create_output_area();
425 var toinsert = this.create_output_area();
418 if (this.prompt_area) {
426 if (this.prompt_area) {
419 toinsert.find('div.prompt').addClass('output_prompt').text('Out[' + n + ']:');
427 toinsert.find('div.prompt').addClass('output_prompt').text('Out[' + n + ']:');
420 }
428 }
421 var inserted = this.append_mime_type(json, toinsert);
429 var inserted = this.append_mime_type(json, toinsert);
422 if (inserted) {
430 if (inserted) {
423 inserted.addClass('output_result');
431 inserted.addClass('output_result');
424 }
432 }
425 this._safe_append(toinsert);
433 this._safe_append(toinsert);
426 // If we just output latex, typeset it.
434 // If we just output latex, typeset it.
427 if ((json.data['text/latex'] !== undefined) ||
435 if ((json.data['text/latex'] !== undefined) ||
428 (json.data['text/html'] !== undefined) ||
436 (json.data['text/html'] !== undefined) ||
429 (json.data['text/markdown'] !== undefined)) {
437 (json.data['text/markdown'] !== undefined)) {
430 this.typeset();
438 this.typeset();
431 }
439 }
432 };
440 };
433
441
434
442
435 OutputArea.prototype.append_error = function (json) {
443 OutputArea.prototype.append_error = function (json) {
436 var tb = json.traceback;
444 var tb = json.traceback;
437 if (tb !== undefined && tb.length > 0) {
445 if (tb !== undefined && tb.length > 0) {
438 var s = '';
446 var s = '';
439 var len = tb.length;
447 var len = tb.length;
440 for (var i=0; i<len; i++) {
448 for (var i=0; i<len; i++) {
441 s = s + tb[i] + '\n';
449 s = s + tb[i] + '\n';
442 }
450 }
443 s = s + '\n';
451 s = s + '\n';
444 var toinsert = this.create_output_area();
452 var toinsert = this.create_output_area();
445 var append_text = OutputArea.append_map['text/plain'];
453 var append_text = OutputArea.append_map['text/plain'];
446 if (append_text) {
454 if (append_text) {
447 append_text.apply(this, [s, {}, toinsert]).addClass('output_error');
455 append_text.apply(this, [s, {}, toinsert]).addClass('output_error');
448 }
456 }
449 this._safe_append(toinsert);
457 this._safe_append(toinsert);
450 }
458 }
451 };
459 };
452
460
453
461
454 OutputArea.prototype.append_stream = function (json) {
462 OutputArea.prototype.append_stream = function (json) {
455 var text = json.text;
463 var text = json.text;
456 var subclass = "output_"+json.name;
464 var subclass = "output_"+json.name;
457 if (this.outputs.length > 0){
465 if (this.outputs.length > 0){
458 // have at least one output to consider
466 // have at least one output to consider
459 var last = this.outputs[this.outputs.length-1];
467 var last = this.outputs[this.outputs.length-1];
460 if (last.output_type == 'stream' && json.name == last.name){
468 if (last.output_type == 'stream' && json.name == last.name){
461 // latest output was in the same stream,
469 // latest output was in the same stream,
462 // so append directly into its pre tag
470 // so append directly into its pre tag
463 // escape ANSI & HTML specials:
471 // escape ANSI & HTML specials:
464 last.text = utils.fixCarriageReturn(last.text + json.text);
472 last.text = utils.fixCarriageReturn(last.text + json.text);
465 var pre = this.element.find('div.'+subclass).last().find('pre');
473 var pre = this.element.find('div.'+subclass).last().find('pre');
466 var html = utils.fixConsole(last.text);
474 var html = utils.fixConsole(last.text);
467 // The only user content injected with this HTML call is
475 // The only user content injected with this HTML call is
468 // escaped by the fixConsole() method.
476 // escaped by the fixConsole() method.
469 pre.html(html);
477 pre.html(html);
470 // return false signals that we merged this output with the previous one,
478 // return false signals that we merged this output with the previous one,
471 // and the new output shouldn't be recorded.
479 // and the new output shouldn't be recorded.
472 return false;
480 return false;
473 }
481 }
474 }
482 }
475
483
476 if (!text.replace("\r", "")) {
484 if (!text.replace("\r", "")) {
477 // text is nothing (empty string, \r, etc.)
485 // text is nothing (empty string, \r, etc.)
478 // so don't append any elements, which might add undesirable space
486 // so don't append any elements, which might add undesirable space
479 // return true to indicate the output should be recorded.
487 // return true to indicate the output should be recorded.
480 return true;
488 return true;
481 }
489 }
482
490
483 // If we got here, attach a new div
491 // If we got here, attach a new div
484 var toinsert = this.create_output_area();
492 var toinsert = this.create_output_area();
485 var append_text = OutputArea.append_map['text/plain'];
493 var append_text = OutputArea.append_map['text/plain'];
486 if (append_text) {
494 if (append_text) {
487 append_text.apply(this, [text, {}, toinsert]).addClass("output_stream " + subclass);
495 append_text.apply(this, [text, {}, toinsert]).addClass("output_stream " + subclass);
488 }
496 }
489 this._safe_append(toinsert);
497 this._safe_append(toinsert);
490 return true;
498 return true;
491 };
499 };
492
500
493
501
494 OutputArea.prototype.append_unrecognized = function (json) {
502 OutputArea.prototype.append_unrecognized = function (json) {
495 var that = this;
503 var that = this;
496 var toinsert = this.create_output_area();
504 var toinsert = this.create_output_area();
497 var subarea = $('<div/>').addClass('output_subarea output_unrecognized');
505 var subarea = $('<div/>').addClass('output_subarea output_unrecognized');
498 toinsert.append(subarea);
506 toinsert.append(subarea);
499 subarea.append(
507 subarea.append(
500 $("<a>")
508 $("<a>")
501 .attr("href", "#")
509 .attr("href", "#")
502 .text("Unrecognized output: " + json.output_type)
510 .text("Unrecognized output: " + json.output_type)
503 .click(function () {
511 .click(function () {
504 that.events.trigger('unrecognized_output.OutputArea', {output: json})
512 that.events.trigger('unrecognized_output.OutputArea', {output: json})
505 })
513 })
506 );
514 );
507 this._safe_append(toinsert);
515 this._safe_append(toinsert);
508 };
516 };
509
517
510
518
511 OutputArea.prototype.append_display_data = function (json, handle_inserted) {
519 OutputArea.prototype.append_display_data = function (json, handle_inserted) {
512 var toinsert = this.create_output_area();
520 var toinsert = this.create_output_area();
513 if (this.append_mime_type(json, toinsert, handle_inserted)) {
521 if (this.append_mime_type(json, toinsert, handle_inserted)) {
514 this._safe_append(toinsert);
522 this._safe_append(toinsert);
515 // If we just output latex, typeset it.
523 // If we just output latex, typeset it.
516 if ((json.data['text/latex'] !== undefined) ||
524 if ((json.data['text/latex'] !== undefined) ||
517 (json.data['text/html'] !== undefined) ||
525 (json.data['text/html'] !== undefined) ||
518 (json.data['text/markdown'] !== undefined)) {
526 (json.data['text/markdown'] !== undefined)) {
519 this.typeset();
527 this.typeset();
520 }
528 }
521 }
529 }
522 };
530 };
523
531
524
532
525 OutputArea.safe_outputs = {
533 OutputArea.safe_outputs = {
526 'text/plain' : true,
534 'text/plain' : true,
527 'text/latex' : true,
535 'text/latex' : true,
528 'image/png' : true,
536 'image/png' : true,
529 'image/jpeg' : true
537 'image/jpeg' : true
530 };
538 };
531
539
532 OutputArea.prototype.append_mime_type = function (json, element, handle_inserted) {
540 OutputArea.prototype.append_mime_type = function (json, element, handle_inserted) {
533 for (var i=0; i < OutputArea.display_order.length; i++) {
541 for (var i=0; i < OutputArea.display_order.length; i++) {
534 var type = OutputArea.display_order[i];
542 var type = OutputArea.display_order[i];
535 var append = OutputArea.append_map[type];
543 var append = OutputArea.append_map[type];
536 if ((json.data[type] !== undefined) && append) {
544 if ((json.data[type] !== undefined) && append) {
537 var value = json.data[type];
545 var value = json.data[type];
538 if (!this.trusted && !OutputArea.safe_outputs[type]) {
546 if (!this.trusted && !OutputArea.safe_outputs[type]) {
539 // not trusted, sanitize HTML
547 // not trusted, sanitize HTML
540 if (type==='text/html' || type==='text/svg') {
548 if (type==='text/html' || type==='text/svg') {
541 value = security.sanitize_html(value);
549 value = security.sanitize_html(value);
542 } else {
550 } else {
543 // don't display if we don't know how to sanitize it
551 // don't display if we don't know how to sanitize it
544 console.log("Ignoring untrusted " + type + " output.");
552 console.log("Ignoring untrusted " + type + " output.");
545 continue;
553 continue;
546 }
554 }
547 }
555 }
548 var md = json.metadata || {};
556 var md = json.metadata || {};
549 var toinsert = append.apply(this, [value, md, element, handle_inserted]);
557 var toinsert = append.apply(this, [value, md, element, handle_inserted]);
550 // Since only the png and jpeg mime types call the inserted
558 // Since only the png and jpeg mime types call the inserted
551 // callback, if the mime type is something other we must call the
559 // callback, if the mime type is something other we must call the
552 // inserted callback only when the element is actually inserted
560 // inserted callback only when the element is actually inserted
553 // into the DOM. Use a timeout of 0 to do this.
561 // into the DOM. Use a timeout of 0 to do this.
554 if (['image/png', 'image/jpeg'].indexOf(type) < 0 && handle_inserted !== undefined) {
562 if (['image/png', 'image/jpeg'].indexOf(type) < 0 && handle_inserted !== undefined) {
555 setTimeout(handle_inserted, 0);
563 setTimeout(handle_inserted, 0);
556 }
564 }
557 this.events.trigger('output_appended.OutputArea', [type, value, md, toinsert]);
565 this.events.trigger('output_appended.OutputArea', [type, value, md, toinsert]);
558 return toinsert;
566 return toinsert;
559 }
567 }
560 }
568 }
561 return null;
569 return null;
562 };
570 };
563
571
564
572
565 var append_html = function (html, md, element) {
573 var append_html = function (html, md, element) {
566 var type = 'text/html';
574 var type = 'text/html';
567 var toinsert = this.create_output_subarea(md, "output_html rendered_html", type);
575 var toinsert = this.create_output_subarea(md, "output_html rendered_html", type);
568 this.keyboard_manager.register_events(toinsert);
576 this.keyboard_manager.register_events(toinsert);
569 toinsert.append(html);
577 toinsert.append(html);
570 element.append(toinsert);
578 element.append(toinsert);
571 return toinsert;
579 return toinsert;
572 };
580 };
573
581
574
582
575 var append_markdown = function(markdown, md, element) {
583 var append_markdown = function(markdown, md, element) {
576 var type = 'text/markdown';
584 var type = 'text/markdown';
577 var toinsert = this.create_output_subarea(md, "output_markdown", type);
585 var toinsert = this.create_output_subarea(md, "output_markdown", type);
578 var text_and_math = mathjaxutils.remove_math(markdown);
586 var text_and_math = mathjaxutils.remove_math(markdown);
579 var text = text_and_math[0];
587 var text = text_and_math[0];
580 var math = text_and_math[1];
588 var math = text_and_math[1];
581 marked(text, function (err, html) {
589 marked(text, function (err, html) {
582 html = mathjaxutils.replace_math(html, math);
590 html = mathjaxutils.replace_math(html, math);
583 toinsert.append(html);
591 toinsert.append(html);
584 });
592 });
585 element.append(toinsert);
593 element.append(toinsert);
586 return toinsert;
594 return toinsert;
587 };
595 };
588
596
589
597
590 var append_javascript = function (js, md, element) {
598 var append_javascript = function (js, md, element) {
591 // We just eval the JS code, element appears in the local scope.
599 /**
600 * We just eval the JS code, element appears in the local scope.
601 */
592 var type = 'application/javascript';
602 var type = 'application/javascript';
593 var toinsert = this.create_output_subarea(md, "output_javascript", type);
603 var toinsert = this.create_output_subarea(md, "output_javascript", type);
594 this.keyboard_manager.register_events(toinsert);
604 this.keyboard_manager.register_events(toinsert);
595 element.append(toinsert);
605 element.append(toinsert);
596
606
597 // Fix for ipython/issues/5293, make sure `element` is the area which
607 // Fix for ipython/issues/5293, make sure `element` is the area which
598 // output can be inserted into at the time of JS execution.
608 // output can be inserted into at the time of JS execution.
599 element = toinsert;
609 element = toinsert;
600 try {
610 try {
601 eval(js);
611 eval(js);
602 } catch(err) {
612 } catch(err) {
603 console.log(err);
613 console.log(err);
604 this._append_javascript_error(err, toinsert);
614 this._append_javascript_error(err, toinsert);
605 }
615 }
606 return toinsert;
616 return toinsert;
607 };
617 };
608
618
609
619
610 var append_text = function (data, md, element) {
620 var append_text = function (data, md, element) {
611 var type = 'text/plain';
621 var type = 'text/plain';
612 var toinsert = this.create_output_subarea(md, "output_text", type);
622 var toinsert = this.create_output_subarea(md, "output_text", type);
613 // escape ANSI & HTML specials in plaintext:
623 // escape ANSI & HTML specials in plaintext:
614 data = utils.fixConsole(data);
624 data = utils.fixConsole(data);
615 data = utils.fixCarriageReturn(data);
625 data = utils.fixCarriageReturn(data);
616 data = utils.autoLinkUrls(data);
626 data = utils.autoLinkUrls(data);
617 // The only user content injected with this HTML call is
627 // The only user content injected with this HTML call is
618 // escaped by the fixConsole() method.
628 // escaped by the fixConsole() method.
619 toinsert.append($("<pre/>").html(data));
629 toinsert.append($("<pre/>").html(data));
620 element.append(toinsert);
630 element.append(toinsert);
621 return toinsert;
631 return toinsert;
622 };
632 };
623
633
624
634
625 var append_svg = function (svg_html, md, element) {
635 var append_svg = function (svg_html, md, element) {
626 var type = 'image/svg+xml';
636 var type = 'image/svg+xml';
627 var toinsert = this.create_output_subarea(md, "output_svg", type);
637 var toinsert = this.create_output_subarea(md, "output_svg", type);
628
638
629 // Get the svg element from within the HTML.
639 // Get the svg element from within the HTML.
630 var svg = $('<div />').html(svg_html).find('svg');
640 var svg = $('<div />').html(svg_html).find('svg');
631 var svg_area = $('<div />');
641 var svg_area = $('<div />');
632 var width = svg.attr('width');
642 var width = svg.attr('width');
633 var height = svg.attr('height');
643 var height = svg.attr('height');
634 svg
644 svg
635 .width('100%')
645 .width('100%')
636 .height('100%');
646 .height('100%');
637 svg_area
647 svg_area
638 .width(width)
648 .width(width)
639 .height(height);
649 .height(height);
640
650
641 // The jQuery resize handlers don't seem to work on the svg element.
651 // The jQuery resize handlers don't seem to work on the svg element.
642 // When the svg renders completely, measure it's size and set the parent
652 // When the svg renders completely, measure it's size and set the parent
643 // div to that size. Then set the svg to 100% the size of the parent
653 // div to that size. Then set the svg to 100% the size of the parent
644 // div and make the parent div resizable.
654 // div and make the parent div resizable.
645 this._dblclick_to_reset_size(svg_area, true, false);
655 this._dblclick_to_reset_size(svg_area, true, false);
646
656
647 svg_area.append(svg);
657 svg_area.append(svg);
648 toinsert.append(svg_area);
658 toinsert.append(svg_area);
649 element.append(toinsert);
659 element.append(toinsert);
650
660
651 return toinsert;
661 return toinsert;
652 };
662 };
653
663
654 OutputArea.prototype._dblclick_to_reset_size = function (img, immediately, resize_parent) {
664 OutputArea.prototype._dblclick_to_reset_size = function (img, immediately, resize_parent) {
655 // Add a resize handler to an element
665 /**
656 //
666 * Add a resize handler to an element
657 // img: jQuery element
667 *
658 // immediately: bool=False
668 * img: jQuery element
659 // Wait for the element to load before creating the handle.
669 * immediately: bool=False
660 // resize_parent: bool=True
670 * Wait for the element to load before creating the handle.
661 // Should the parent of the element be resized when the element is
671 * resize_parent: bool=True
662 // reset (by double click).
672 * Should the parent of the element be resized when the element is
673 * reset (by double click).
674 */
663 var callback = function (){
675 var callback = function (){
664 var h0 = img.height();
676 var h0 = img.height();
665 var w0 = img.width();
677 var w0 = img.width();
666 if (!(h0 && w0)) {
678 if (!(h0 && w0)) {
667 // zero size, don't make it resizable
679 // zero size, don't make it resizable
668 return;
680 return;
669 }
681 }
670 img.resizable({
682 img.resizable({
671 aspectRatio: true,
683 aspectRatio: true,
672 autoHide: true
684 autoHide: true
673 });
685 });
674 img.dblclick(function () {
686 img.dblclick(function () {
675 // resize wrapper & image together for some reason:
687 // resize wrapper & image together for some reason:
676 img.height(h0);
688 img.height(h0);
677 img.width(w0);
689 img.width(w0);
678 if (resize_parent === undefined || resize_parent) {
690 if (resize_parent === undefined || resize_parent) {
679 img.parent().height(h0);
691 img.parent().height(h0);
680 img.parent().width(w0);
692 img.parent().width(w0);
681 }
693 }
682 });
694 });
683 };
695 };
684
696
685 if (immediately) {
697 if (immediately) {
686 callback();
698 callback();
687 } else {
699 } else {
688 img.on("load", callback);
700 img.on("load", callback);
689 }
701 }
690 };
702 };
691
703
692 var set_width_height = function (img, md, mime) {
704 var set_width_height = function (img, md, mime) {
693 // set width and height of an img element from metadata
705 /**
706 * set width and height of an img element from metadata
707 */
694 var height = _get_metadata_key(md, 'height', mime);
708 var height = _get_metadata_key(md, 'height', mime);
695 if (height !== undefined) img.attr('height', height);
709 if (height !== undefined) img.attr('height', height);
696 var width = _get_metadata_key(md, 'width', mime);
710 var width = _get_metadata_key(md, 'width', mime);
697 if (width !== undefined) img.attr('width', width);
711 if (width !== undefined) img.attr('width', width);
698 };
712 };
699
713
700 var append_png = function (png, md, element, handle_inserted) {
714 var append_png = function (png, md, element, handle_inserted) {
701 var type = 'image/png';
715 var type = 'image/png';
702 var toinsert = this.create_output_subarea(md, "output_png", type);
716 var toinsert = this.create_output_subarea(md, "output_png", type);
703 var img = $("<img/>");
717 var img = $("<img/>");
704 if (handle_inserted !== undefined) {
718 if (handle_inserted !== undefined) {
705 img.on('load', function(){
719 img.on('load', function(){
706 handle_inserted(img);
720 handle_inserted(img);
707 });
721 });
708 }
722 }
709 img[0].src = 'data:image/png;base64,'+ png;
723 img[0].src = 'data:image/png;base64,'+ png;
710 set_width_height(img, md, 'image/png');
724 set_width_height(img, md, 'image/png');
711 this._dblclick_to_reset_size(img);
725 this._dblclick_to_reset_size(img);
712 toinsert.append(img);
726 toinsert.append(img);
713 element.append(toinsert);
727 element.append(toinsert);
714 return toinsert;
728 return toinsert;
715 };
729 };
716
730
717
731
718 var append_jpeg = function (jpeg, md, element, handle_inserted) {
732 var append_jpeg = function (jpeg, md, element, handle_inserted) {
719 var type = 'image/jpeg';
733 var type = 'image/jpeg';
720 var toinsert = this.create_output_subarea(md, "output_jpeg", type);
734 var toinsert = this.create_output_subarea(md, "output_jpeg", type);
721 var img = $("<img/>");
735 var img = $("<img/>");
722 if (handle_inserted !== undefined) {
736 if (handle_inserted !== undefined) {
723 img.on('load', function(){
737 img.on('load', function(){
724 handle_inserted(img);
738 handle_inserted(img);
725 });
739 });
726 }
740 }
727 img[0].src = 'data:image/jpeg;base64,'+ jpeg;
741 img[0].src = 'data:image/jpeg;base64,'+ jpeg;
728 set_width_height(img, md, 'image/jpeg');
742 set_width_height(img, md, 'image/jpeg');
729 this._dblclick_to_reset_size(img);
743 this._dblclick_to_reset_size(img);
730 toinsert.append(img);
744 toinsert.append(img);
731 element.append(toinsert);
745 element.append(toinsert);
732 return toinsert;
746 return toinsert;
733 };
747 };
734
748
735
749
736 var append_pdf = function (pdf, md, element) {
750 var append_pdf = function (pdf, md, element) {
737 var type = 'application/pdf';
751 var type = 'application/pdf';
738 var toinsert = this.create_output_subarea(md, "output_pdf", type);
752 var toinsert = this.create_output_subarea(md, "output_pdf", type);
739 var a = $('<a/>').attr('href', 'data:application/pdf;base64,'+pdf);
753 var a = $('<a/>').attr('href', 'data:application/pdf;base64,'+pdf);
740 a.attr('target', '_blank');
754 a.attr('target', '_blank');
741 a.text('View PDF');
755 a.text('View PDF');
742 toinsert.append(a);
756 toinsert.append(a);
743 element.append(toinsert);
757 element.append(toinsert);
744 return toinsert;
758 return toinsert;
745 };
759 };
746
760
747 var append_latex = function (latex, md, element) {
761 var append_latex = function (latex, md, element) {
748 // This method cannot do the typesetting because the latex first has to
762 /**
749 // be on the page.
763 * This method cannot do the typesetting because the latex first has to
764 * be on the page.
765 */
750 var type = 'text/latex';
766 var type = 'text/latex';
751 var toinsert = this.create_output_subarea(md, "output_latex", type);
767 var toinsert = this.create_output_subarea(md, "output_latex", type);
752 toinsert.append(latex);
768 toinsert.append(latex);
753 element.append(toinsert);
769 element.append(toinsert);
754 return toinsert;
770 return toinsert;
755 };
771 };
756
772
757
773
758 OutputArea.prototype.append_raw_input = function (msg) {
774 OutputArea.prototype.append_raw_input = function (msg) {
759 var that = this;
775 var that = this;
760 this.expand();
776 this.expand();
761 var content = msg.content;
777 var content = msg.content;
762 var area = this.create_output_area();
778 var area = this.create_output_area();
763
779
764 // disable any other raw_inputs, if they are left around
780 // disable any other raw_inputs, if they are left around
765 $("div.output_subarea.raw_input_container").remove();
781 $("div.output_subarea.raw_input_container").remove();
766
782
767 var input_type = content.password ? 'password' : 'text';
783 var input_type = content.password ? 'password' : 'text';
768
784
769 area.append(
785 area.append(
770 $("<div/>")
786 $("<div/>")
771 .addClass("box-flex1 output_subarea raw_input_container")
787 .addClass("box-flex1 output_subarea raw_input_container")
772 .append(
788 .append(
773 $("<span/>")
789 $("<span/>")
774 .addClass("raw_input_prompt")
790 .addClass("raw_input_prompt")
775 .text(content.prompt)
791 .text(content.prompt)
776 )
792 )
777 .append(
793 .append(
778 $("<input/>")
794 $("<input/>")
779 .addClass("raw_input")
795 .addClass("raw_input")
780 .attr('type', input_type)
796 .attr('type', input_type)
781 .attr("size", 47)
797 .attr("size", 47)
782 .keydown(function (event, ui) {
798 .keydown(function (event, ui) {
783 // make sure we submit on enter,
799 // make sure we submit on enter,
784 // and don't re-execute the *cell* on shift-enter
800 // and don't re-execute the *cell* on shift-enter
785 if (event.which === keyboard.keycodes.enter) {
801 if (event.which === keyboard.keycodes.enter) {
786 that._submit_raw_input();
802 that._submit_raw_input();
787 return false;
803 return false;
788 }
804 }
789 })
805 })
790 )
806 )
791 );
807 );
792
808
793 this.element.append(area);
809 this.element.append(area);
794 var raw_input = area.find('input.raw_input');
810 var raw_input = area.find('input.raw_input');
795 // Register events that enable/disable the keyboard manager while raw
811 // Register events that enable/disable the keyboard manager while raw
796 // input is focused.
812 // input is focused.
797 this.keyboard_manager.register_events(raw_input);
813 this.keyboard_manager.register_events(raw_input);
798 // Note, the following line used to read raw_input.focus().focus().
814 // Note, the following line used to read raw_input.focus().focus().
799 // This seemed to be needed otherwise only the cell would be focused.
815 // This seemed to be needed otherwise only the cell would be focused.
800 // But with the modal UI, this seems to work fine with one call to focus().
816 // But with the modal UI, this seems to work fine with one call to focus().
801 raw_input.focus();
817 raw_input.focus();
802 };
818 };
803
819
804 OutputArea.prototype._submit_raw_input = function (evt) {
820 OutputArea.prototype._submit_raw_input = function (evt) {
805 var container = this.element.find("div.raw_input_container");
821 var container = this.element.find("div.raw_input_container");
806 var theprompt = container.find("span.raw_input_prompt");
822 var theprompt = container.find("span.raw_input_prompt");
807 var theinput = container.find("input.raw_input");
823 var theinput = container.find("input.raw_input");
808 var value = theinput.val();
824 var value = theinput.val();
809 var echo = value;
825 var echo = value;
810 // don't echo if it's a password
826 // don't echo if it's a password
811 if (theinput.attr('type') == 'password') {
827 if (theinput.attr('type') == 'password') {
812 echo = '········';
828 echo = '········';
813 }
829 }
814 var content = {
830 var content = {
815 output_type : 'stream',
831 output_type : 'stream',
816 name : 'stdout',
832 name : 'stdout',
817 text : theprompt.text() + echo + '\n'
833 text : theprompt.text() + echo + '\n'
818 };
834 };
819 // remove form container
835 // remove form container
820 container.parent().remove();
836 container.parent().remove();
821 // replace with plaintext version in stdout
837 // replace with plaintext version in stdout
822 this.append_output(content, false);
838 this.append_output(content, false);
823 this.events.trigger('send_input_reply.Kernel', value);
839 this.events.trigger('send_input_reply.Kernel', value);
824 };
840 };
825
841
826
842
827 OutputArea.prototype.handle_clear_output = function (msg) {
843 OutputArea.prototype.handle_clear_output = function (msg) {
828 // msg spec v4 had stdout, stderr, display keys
844 /**
829 // v4.1 replaced these with just wait
845 * msg spec v4 had stdout, stderr, display keys
830 // The default behavior is the same (stdout=stderr=display=True, wait=False),
846 * v4.1 replaced these with just wait
831 // so v4 messages will still be properly handled,
847 * The default behavior is the same (stdout=stderr=display=True, wait=False),
832 // except for the rarely used clearing less than all output.
848 * so v4 messages will still be properly handled,
849 * except for the rarely used clearing less than all output.
850 */
833 this.clear_output(msg.content.wait || false);
851 this.clear_output(msg.content.wait || false);
834 };
852 };
835
853
836
854
837 OutputArea.prototype.clear_output = function(wait) {
855 OutputArea.prototype.clear_output = function(wait) {
838 if (wait) {
856 if (wait) {
839
857
840 // If a clear is queued, clear before adding another to the queue.
858 // If a clear is queued, clear before adding another to the queue.
841 if (this.clear_queued) {
859 if (this.clear_queued) {
842 this.clear_output(false);
860 this.clear_output(false);
843 }
861 }
844
862
845 this.clear_queued = true;
863 this.clear_queued = true;
846 } else {
864 } else {
847
865
848 // Fix the output div's height if the clear_output is waiting for
866 // Fix the output div's height if the clear_output is waiting for
849 // new output (it is being used in an animation).
867 // new output (it is being used in an animation).
850 if (this.clear_queued) {
868 if (this.clear_queued) {
851 var height = this.element.height();
869 var height = this.element.height();
852 this.element.height(height);
870 this.element.height(height);
853 this.clear_queued = false;
871 this.clear_queued = false;
854 }
872 }
855
873
856 // Clear all
874 // Clear all
857 // Remove load event handlers from img tags because we don't want
875 // Remove load event handlers from img tags because we don't want
858 // them to fire if the image is never added to the page.
876 // them to fire if the image is never added to the page.
859 this.element.find('img').off('load');
877 this.element.find('img').off('load');
860 this.element.html("");
878 this.element.html("");
861
879
862 // Notify others of changes.
880 // Notify others of changes.
863 this.element.trigger('changed');
881 this.element.trigger('changed');
864
882
865 this.outputs = [];
883 this.outputs = [];
866 this.trusted = true;
884 this.trusted = true;
867 this.unscroll_area();
885 this.unscroll_area();
868 return;
886 return;
869 }
887 }
870 };
888 };
871
889
872
890
873 // JSON serialization
891 // JSON serialization
874
892
875 OutputArea.prototype.fromJSON = function (outputs, metadata) {
893 OutputArea.prototype.fromJSON = function (outputs, metadata) {
876 var len = outputs.length;
894 var len = outputs.length;
877 metadata = metadata || {};
895 metadata = metadata || {};
878
896
879 for (var i=0; i<len; i++) {
897 for (var i=0; i<len; i++) {
880 this.append_output(outputs[i]);
898 this.append_output(outputs[i]);
881 }
899 }
882
900
883 if (metadata.collapsed !== undefined) {
901 if (metadata.collapsed !== undefined) {
884 this.collapsed = metadata.collapsed;
902 this.collapsed = metadata.collapsed;
885 if (metadata.collapsed) {
903 if (metadata.collapsed) {
886 this.collapse_output();
904 this.collapse_output();
887 }
905 }
888 }
906 }
889 if (metadata.autoscroll !== undefined) {
907 if (metadata.autoscroll !== undefined) {
890 this.collapsed = metadata.collapsed;
908 this.collapsed = metadata.collapsed;
891 if (metadata.collapsed) {
909 if (metadata.collapsed) {
892 this.collapse_output();
910 this.collapse_output();
893 } else {
911 } else {
894 this.expand_output();
912 this.expand_output();
895 }
913 }
896 }
914 }
897 };
915 };
898
916
899
917
900 OutputArea.prototype.toJSON = function () {
918 OutputArea.prototype.toJSON = function () {
901 return this.outputs;
919 return this.outputs;
902 };
920 };
903
921
904 /**
922 /**
905 * Class properties
923 * Class properties
906 **/
924 **/
907
925
908 /**
926 /**
909 * Threshold to trigger autoscroll when the OutputArea is resized,
927 * Threshold to trigger autoscroll when the OutputArea is resized,
910 * typically when new outputs are added.
928 * typically when new outputs are added.
911 *
929 *
912 * Behavior is undefined if autoscroll is lower than minimum_scroll_threshold,
930 * Behavior is undefined if autoscroll is lower than minimum_scroll_threshold,
913 * unless it is < 0, in which case autoscroll will never be triggered
931 * unless it is < 0, in which case autoscroll will never be triggered
914 *
932 *
915 * @property auto_scroll_threshold
933 * @property auto_scroll_threshold
916 * @type Number
934 * @type Number
917 * @default 100
935 * @default 100
918 *
936 *
919 **/
937 **/
920 OutputArea.auto_scroll_threshold = 100;
938 OutputArea.auto_scroll_threshold = 100;
921
939
922 /**
940 /**
923 * Lower limit (in lines) for OutputArea to be made scrollable. OutputAreas
941 * Lower limit (in lines) for OutputArea to be made scrollable. OutputAreas
924 * shorter than this are never scrolled.
942 * shorter than this are never scrolled.
925 *
943 *
926 * @property minimum_scroll_threshold
944 * @property minimum_scroll_threshold
927 * @type Number
945 * @type Number
928 * @default 20
946 * @default 20
929 *
947 *
930 **/
948 **/
931 OutputArea.minimum_scroll_threshold = 20;
949 OutputArea.minimum_scroll_threshold = 20;
932
950
933
951
934 OutputArea.display_order = [
952 OutputArea.display_order = [
935 'application/javascript',
953 'application/javascript',
936 'text/html',
954 'text/html',
937 'text/markdown',
955 'text/markdown',
938 'text/latex',
956 'text/latex',
939 'image/svg+xml',
957 'image/svg+xml',
940 'image/png',
958 'image/png',
941 'image/jpeg',
959 'image/jpeg',
942 'application/pdf',
960 'application/pdf',
943 'text/plain'
961 'text/plain'
944 ];
962 ];
945
963
946 OutputArea.append_map = {
964 OutputArea.append_map = {
947 "text/plain" : append_text,
965 "text/plain" : append_text,
948 "text/html" : append_html,
966 "text/html" : append_html,
949 "text/markdown": append_markdown,
967 "text/markdown": append_markdown,
950 "image/svg+xml" : append_svg,
968 "image/svg+xml" : append_svg,
951 "image/png" : append_png,
969 "image/png" : append_png,
952 "image/jpeg" : append_jpeg,
970 "image/jpeg" : append_jpeg,
953 "text/latex" : append_latex,
971 "text/latex" : append_latex,
954 "application/javascript" : append_javascript,
972 "application/javascript" : append_javascript,
955 "application/pdf" : append_pdf
973 "application/pdf" : append_pdf
956 };
974 };
957
975
958 // For backwards compatability.
976 // For backwards compatability.
959 IPython.OutputArea = OutputArea;
977 IPython.OutputArea = OutputArea;
960
978
961 return {'OutputArea': OutputArea};
979 return {'OutputArea': OutputArea};
962 });
980 });
@@ -1,185 +1,191 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 'jqueryui',
6 'jqueryui',
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, options) {
11 var Pager = function (pager_selector, pager_splitter_selector, options) {
12 // Constructor
12 /**
13 //
13 * Constructor
14 // Parameters:
14 *
15 // pager_selector: string
15 * Parameters:
16 // pager_splitter_selector: string
16 * pager_selector: string
17 // options: dictionary
17 * pager_splitter_selector: string
18 // Dictionary of keyword arguments.
18 * options: dictionary
19 // events: $(Events) instance
19 * Dictionary of keyword arguments.
20 // layout_manager: LayoutManager instance
20 * events: $(Events) instance
21 * layout_manager: LayoutManager instance
22 */
21 this.events = options.events;
23 this.events = options.events;
22 this.pager_element = $(pager_selector);
24 this.pager_element = $(pager_selector);
23 this.pager_button_area = $('#pager_button_area');
25 this.pager_button_area = $('#pager_button_area');
24 var that = this;
26 var that = this;
25 this.percentage_height = 0.40;
27 this.percentage_height = 0.40;
26 options.layout_manager.pager = this;
28 options.layout_manager.pager = this;
27 this.pager_splitter_element = $(pager_splitter_selector)
29 this.pager_splitter_element = $(pager_splitter_selector)
28 .draggable({
30 .draggable({
29 containment: 'window',
31 containment: 'window',
30 axis:'y',
32 axis:'y',
31 helper: null ,
33 helper: null ,
32 drag: function(event, ui) {
34 drag: function(event, ui) {
33 // recalculate the amount of space the pager should take
35 /**
36 * recalculate the amount of space the pager should take
37 */
34 var pheight = ($(document.body).height()-event.clientY-4);
38 var pheight = ($(document.body).height()-event.clientY-4);
35 var downprct = pheight/options.layout_manager.app_height();
39 var downprct = pheight/options.layout_manager.app_height();
36 downprct = Math.min(0.9, downprct);
40 downprct = Math.min(0.9, downprct);
37 if (downprct < 0.1) {
41 if (downprct < 0.1) {
38 that.percentage_height = 0.1;
42 that.percentage_height = 0.1;
39 that.collapse({'duration':0});
43 that.collapse({'duration':0});
40 } else if (downprct > 0.2) {
44 } else if (downprct > 0.2) {
41 that.percentage_height = downprct;
45 that.percentage_height = downprct;
42 that.expand({'duration':0});
46 that.expand({'duration':0});
43 }
47 }
44 options.layout_manager.do_resize();
48 options.layout_manager.do_resize();
45 }
49 }
46 });
50 });
47 this.expanded = false;
51 this.expanded = false;
48 this.style();
52 this.style();
49 this.create_button_area();
53 this.create_button_area();
50 this.bind_events();
54 this.bind_events();
51 };
55 };
52
56
53 Pager.prototype.create_button_area = function(){
57 Pager.prototype.create_button_area = function(){
54 var that = this;
58 var that = this;
55 this.pager_button_area.append(
59 this.pager_button_area.append(
56 $('<a>').attr('role', "button")
60 $('<a>').attr('role', "button")
57 .attr('title',"Open the pager in an external window")
61 .attr('title',"Open the pager in an external window")
58 .addClass('ui-button')
62 .addClass('ui-button')
59 .click(function(){that.detach();})
63 .click(function(){that.detach();})
60 .attr('style','position: absolute; right: 20px;')
64 .attr('style','position: absolute; right: 20px;')
61 .append(
65 .append(
62 $('<span>').addClass("ui-icon ui-icon-extlink")
66 $('<span>').addClass("ui-icon ui-icon-extlink")
63 )
67 )
64 );
68 );
65 this.pager_button_area.append(
69 this.pager_button_area.append(
66 $('<a>').attr('role', "button")
70 $('<a>').attr('role', "button")
67 .attr('title',"Close the pager")
71 .attr('title',"Close the pager")
68 .addClass('ui-button')
72 .addClass('ui-button')
69 .click(function(){that.collapse();})
73 .click(function(){that.collapse();})
70 .attr('style','position: absolute; right: 5px;')
74 .attr('style','position: absolute; right: 5px;')
71 .append(
75 .append(
72 $('<span>').addClass("ui-icon ui-icon-close")
76 $('<span>').addClass("ui-icon ui-icon-close")
73 )
77 )
74 );
78 );
75 };
79 };
76
80
77 Pager.prototype.style = function () {
81 Pager.prototype.style = function () {
78 this.pager_splitter_element.addClass('ui-widget ui-state-default');
82 this.pager_splitter_element.addClass('ui-widget ui-state-default');
79 this.pager_splitter_element.attr('title', 'Click to Show/Hide pager area, drag to Resize');
83 this.pager_splitter_element.attr('title', 'Click to Show/Hide pager area, drag to Resize');
80 };
84 };
81
85
82
86
83 Pager.prototype.bind_events = function () {
87 Pager.prototype.bind_events = function () {
84 var that = this;
88 var that = this;
85
89
86 this.pager_element.bind('collapse_pager', function (event, extrap) {
90 this.pager_element.bind('collapse_pager', function (event, extrap) {
87 var time = 'fast';
91 var time = 'fast';
88 if (extrap && extrap.duration) {
92 if (extrap && extrap.duration) {
89 time = extrap.duration;
93 time = extrap.duration;
90 }
94 }
91 that.pager_element.hide(time);
95 that.pager_element.hide(time);
92 });
96 });
93
97
94 this.pager_element.bind('expand_pager', function (event, extrap) {
98 this.pager_element.bind('expand_pager', function (event, extrap) {
95 var time = 'fast';
99 var time = 'fast';
96 if (extrap && extrap.duration) {
100 if (extrap && extrap.duration) {
97 time = extrap.duration;
101 time = extrap.duration;
98 }
102 }
99 that.pager_element.show(time);
103 that.pager_element.show(time);
100 });
104 });
101
105
102 this.pager_splitter_element.hover(
106 this.pager_splitter_element.hover(
103 function () {
107 function () {
104 that.pager_splitter_element.addClass('ui-state-hover');
108 that.pager_splitter_element.addClass('ui-state-hover');
105 },
109 },
106 function () {
110 function () {
107 that.pager_splitter_element.removeClass('ui-state-hover');
111 that.pager_splitter_element.removeClass('ui-state-hover');
108 }
112 }
109 );
113 );
110
114
111 this.pager_splitter_element.click(function () {
115 this.pager_splitter_element.click(function () {
112 that.toggle();
116 that.toggle();
113 });
117 });
114
118
115 this.events.on('open_with_text.Pager', function (event, payload) {
119 this.events.on('open_with_text.Pager', function (event, payload) {
116 // FIXME: support other mime types
120 // FIXME: support other mime types
117 if (payload.data['text/plain'] && payload.data['text/plain'] !== "") {
121 if (payload.data['text/plain'] && payload.data['text/plain'] !== "") {
118 that.clear();
122 that.clear();
119 that.expand();
123 that.expand();
120 that.append_text(payload.data['text/plain']);
124 that.append_text(payload.data['text/plain']);
121 }
125 }
122 });
126 });
123 };
127 };
124
128
125
129
126 Pager.prototype.collapse = function (extrap) {
130 Pager.prototype.collapse = function (extrap) {
127 if (this.expanded === true) {
131 if (this.expanded === true) {
128 this.expanded = false;
132 this.expanded = false;
129 this.pager_element.add($('div#notebook')).trigger('collapse_pager', extrap);
133 this.pager_element.add($('div#notebook')).trigger('collapse_pager', extrap);
130 }
134 }
131 };
135 };
132
136
133
137
134 Pager.prototype.expand = function (extrap) {
138 Pager.prototype.expand = function (extrap) {
135 if (this.expanded !== true) {
139 if (this.expanded !== true) {
136 this.expanded = true;
140 this.expanded = true;
137 this.pager_element.add($('div#notebook')).trigger('expand_pager', extrap);
141 this.pager_element.add($('div#notebook')).trigger('expand_pager', extrap);
138 }
142 }
139 };
143 };
140
144
141
145
142 Pager.prototype.toggle = function () {
146 Pager.prototype.toggle = function () {
143 if (this.expanded === true) {
147 if (this.expanded === true) {
144 this.collapse();
148 this.collapse();
145 } else {
149 } else {
146 this.expand();
150 this.expand();
147 }
151 }
148 };
152 };
149
153
150
154
151 Pager.prototype.clear = function (text) {
155 Pager.prototype.clear = function (text) {
152 this.pager_element.find(".container").empty();
156 this.pager_element.find(".container").empty();
153 };
157 };
154
158
155 Pager.prototype.detach = function(){
159 Pager.prototype.detach = function(){
156 var w = window.open("","_blank");
160 var w = window.open("","_blank");
157 $(w.document.head)
161 $(w.document.head)
158 .append(
162 .append(
159 $('<link>')
163 $('<link>')
160 .attr('rel',"stylesheet")
164 .attr('rel',"stylesheet")
161 .attr('href',"/static/css/notebook.css")
165 .attr('href',"/static/css/notebook.css")
162 .attr('type',"text/css")
166 .attr('type',"text/css")
163 )
167 )
164 .append(
168 .append(
165 $('<title>').text("IPython Pager")
169 $('<title>').text("IPython Pager")
166 );
170 );
167 var pager_body = $(w.document.body);
171 var pager_body = $(w.document.body);
168 pager_body.css('overflow','scroll');
172 pager_body.css('overflow','scroll');
169
173
170 pager_body.append(this.pager_element.clone().children());
174 pager_body.append(this.pager_element.clone().children());
171 w.document.close();
175 w.document.close();
172 this.collapse();
176 this.collapse();
173 };
177 };
174
178
175 Pager.prototype.append_text = function (text) {
179 Pager.prototype.append_text = function (text) {
176 // The only user content injected with this HTML call is escaped by
180 /**
177 // the fixConsole() method.
181 * The only user content injected with this HTML call is escaped by
182 * the fixConsole() method.
183 */
178 this.pager_element.find(".container").append($('<pre/>').html(utils.fixCarriageReturn(utils.fixConsole(text))));
184 this.pager_element.find(".container").append($('<pre/>').html(utils.fixCarriageReturn(utils.fixConsole(text))));
179 };
185 };
180
186
181 // Backwards compatability.
187 // Backwards compatability.
182 IPython.Pager = Pager;
188 IPython.Pager = Pager;
183
189
184 return {'Pager': Pager};
190 return {'Pager': Pager};
185 });
191 });
@@ -1,182 +1,186 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 (options) {
13 var QuickHelp = function (options) {
14 // Constructor
14 /**
15 //
15 * Constructor
16 // Parameters:
16 *
17 // options: dictionary
17 * Parameters:
18 // Dictionary of keyword arguments.
18 * options: dictionary
19 // events: $(Events) instance
19 * Dictionary of keyword arguments.
20 // keyboard_manager: KeyboardManager instance
20 * events: $(Events) instance
21 // notebook: Notebook instance
21 * keyboard_manager: KeyboardManager instance
22 * notebook: Notebook instance
23 */
22 this.keyboard_manager = options.keyboard_manager;
24 this.keyboard_manager = options.keyboard_manager;
23 this.notebook = options.notebook;
25 this.notebook = options.notebook;
24 this.keyboard_manager.quick_help = this;
26 this.keyboard_manager.quick_help = this;
25 this.events = options.events;
27 this.events = options.events;
26 };
28 };
27
29
28 var cmd_ctrl = 'Ctrl-';
30 var cmd_ctrl = 'Ctrl-';
29 var platform_specific;
31 var platform_specific;
30
32
31 if (platform === 'MacOS') {
33 if (platform === 'MacOS') {
32 // Mac OS X specific
34 // Mac OS X specific
33 cmd_ctrl = 'Cmd-';
35 cmd_ctrl = 'Cmd-';
34 platform_specific = [
36 platform_specific = [
35 { shortcut: "Cmd-Up", help:"go to cell start" },
37 { shortcut: "Cmd-Up", help:"go to cell start" },
36 { shortcut: "Cmd-Down", help:"go to cell end" },
38 { shortcut: "Cmd-Down", help:"go to cell end" },
37 { shortcut: "Alt-Left", help:"go one word left" },
39 { shortcut: "Alt-Left", help:"go one word left" },
38 { shortcut: "Alt-Right", help:"go one word right" },
40 { shortcut: "Alt-Right", help:"go one word right" },
39 { shortcut: "Alt-Backspace", help:"del word before" },
41 { shortcut: "Alt-Backspace", help:"del word before" },
40 { shortcut: "Alt-Delete", help:"del word after" },
42 { shortcut: "Alt-Delete", help:"del word after" },
41 ];
43 ];
42 } else {
44 } else {
43 // PC specific
45 // PC specific
44 platform_specific = [
46 platform_specific = [
45 { shortcut: "Ctrl-Home", help:"go to cell start" },
47 { shortcut: "Ctrl-Home", help:"go to cell start" },
46 { shortcut: "Ctrl-Up", help:"go to cell start" },
48 { shortcut: "Ctrl-Up", help:"go to cell start" },
47 { shortcut: "Ctrl-End", help:"go to cell end" },
49 { shortcut: "Ctrl-End", help:"go to cell end" },
48 { shortcut: "Ctrl-Down", help:"go to cell end" },
50 { shortcut: "Ctrl-Down", help:"go to cell end" },
49 { shortcut: "Ctrl-Left", help:"go one word left" },
51 { shortcut: "Ctrl-Left", help:"go one word left" },
50 { shortcut: "Ctrl-Right", help:"go one word right" },
52 { shortcut: "Ctrl-Right", help:"go one word right" },
51 { shortcut: "Ctrl-Backspace", help:"del word before" },
53 { shortcut: "Ctrl-Backspace", help:"del word before" },
52 { shortcut: "Ctrl-Delete", help:"del word after" },
54 { shortcut: "Ctrl-Delete", help:"del word after" },
53 ];
55 ];
54 }
56 }
55
57
56 var cm_shortcuts = [
58 var cm_shortcuts = [
57 { shortcut:"Tab", help:"code completion or indent" },
59 { shortcut:"Tab", help:"code completion or indent" },
58 { shortcut:"Shift-Tab", help:"tooltip" },
60 { shortcut:"Shift-Tab", help:"tooltip" },
59 { shortcut: cmd_ctrl + "]", help:"indent" },
61 { shortcut: cmd_ctrl + "]", help:"indent" },
60 { shortcut: cmd_ctrl + "[", help:"dedent" },
62 { shortcut: cmd_ctrl + "[", help:"dedent" },
61 { shortcut: cmd_ctrl + "a", help:"select all" },
63 { shortcut: cmd_ctrl + "a", help:"select all" },
62 { shortcut: cmd_ctrl + "z", help:"undo" },
64 { shortcut: cmd_ctrl + "z", help:"undo" },
63 { shortcut: cmd_ctrl + "Shift-z", help:"redo" },
65 { shortcut: cmd_ctrl + "Shift-z", help:"redo" },
64 { shortcut: cmd_ctrl + "y", help:"redo" },
66 { shortcut: cmd_ctrl + "y", help:"redo" },
65 ].concat( platform_specific );
67 ].concat( platform_specific );
66
68
67
69
68 QuickHelp.prototype.show_keyboard_shortcuts = function () {
70 QuickHelp.prototype.show_keyboard_shortcuts = function () {
69 // toggles display of keyboard shortcut dialog
71 /**
72 * toggles display of keyboard shortcut dialog
73 */
70 var that = this;
74 var that = this;
71 if ( this.force_rebuild ) {
75 if ( this.force_rebuild ) {
72 this.shortcut_dialog.remove();
76 this.shortcut_dialog.remove();
73 delete(this.shortcut_dialog);
77 delete(this.shortcut_dialog);
74 this.force_rebuild = false;
78 this.force_rebuild = false;
75 }
79 }
76 if ( this.shortcut_dialog ){
80 if ( this.shortcut_dialog ){
77 // if dialog is already shown, close it
81 // if dialog is already shown, close it
78 $(this.shortcut_dialog).modal("toggle");
82 $(this.shortcut_dialog).modal("toggle");
79 return;
83 return;
80 }
84 }
81 var command_shortcuts = this.keyboard_manager.command_shortcuts.help();
85 var command_shortcuts = this.keyboard_manager.command_shortcuts.help();
82 var edit_shortcuts = this.keyboard_manager.edit_shortcuts.help();
86 var edit_shortcuts = this.keyboard_manager.edit_shortcuts.help();
83 var help, shortcut;
87 var help, shortcut;
84 var i, half, n;
88 var i, half, n;
85 var element = $('<div/>');
89 var element = $('<div/>');
86
90
87 // The documentation
91 // The documentation
88 var doc = $('<div/>').addClass('alert alert-warning');
92 var doc = $('<div/>').addClass('alert alert-warning');
89 doc.append(
93 doc.append(
90 $('<button/>').addClass('close').attr('data-dismiss','alert').html('&times;')
94 $('<button/>').addClass('close').attr('data-dismiss','alert').html('&times;')
91 ).append(
95 ).append(
92 'The IPython Notebook has two different keyboard input modes. <b>Edit mode</b> '+
96 'The IPython Notebook has two different keyboard input modes. <b>Edit mode</b> '+
93 'allows you to type code/text into a cell and is indicated by a green cell '+
97 'allows you to type code/text into a cell and is indicated by a green cell '+
94 'border. <b>Command mode</b> binds the keyboard to notebook level actions '+
98 'border. <b>Command mode</b> binds the keyboard to notebook level actions '+
95 'and is indicated by a grey cell border.'
99 'and is indicated by a grey cell border.'
96 );
100 );
97 element.append(doc);
101 element.append(doc);
98
102
99 // Command mode
103 // Command mode
100 var cmd_div = this.build_command_help();
104 var cmd_div = this.build_command_help();
101 element.append(cmd_div);
105 element.append(cmd_div);
102
106
103 // Edit mode
107 // Edit mode
104 var edit_div = this.build_edit_help(cm_shortcuts);
108 var edit_div = this.build_edit_help(cm_shortcuts);
105 element.append(edit_div);
109 element.append(edit_div);
106
110
107 this.shortcut_dialog = dialog.modal({
111 this.shortcut_dialog = dialog.modal({
108 title : "Keyboard shortcuts",
112 title : "Keyboard shortcuts",
109 body : element,
113 body : element,
110 destroy : false,
114 destroy : false,
111 buttons : {
115 buttons : {
112 Close : {}
116 Close : {}
113 },
117 },
114 notebook: this.notebook,
118 notebook: this.notebook,
115 keyboard_manager: this.keyboard_manager,
119 keyboard_manager: this.keyboard_manager,
116 });
120 });
117 this.shortcut_dialog.addClass("modal_stretch");
121 this.shortcut_dialog.addClass("modal_stretch");
118
122
119 this.events.on('rebuild.QuickHelp', function() { that.force_rebuild = true;});
123 this.events.on('rebuild.QuickHelp', function() { that.force_rebuild = true;});
120 };
124 };
121
125
122 QuickHelp.prototype.build_command_help = function () {
126 QuickHelp.prototype.build_command_help = function () {
123 var command_shortcuts = this.keyboard_manager.command_shortcuts.help();
127 var command_shortcuts = this.keyboard_manager.command_shortcuts.help();
124 return build_div('<h4>Command Mode (press <code>Esc</code> to enable)</h4>', command_shortcuts);
128 return build_div('<h4>Command Mode (press <code>Esc</code> to enable)</h4>', command_shortcuts);
125 };
129 };
126
130
127 var special_case = { pageup: "PageUp", pagedown: "Page Down", 'minus': '-' };
131 var special_case = { pageup: "PageUp", pagedown: "Page Down", 'minus': '-' };
128 var prettify = function (s) {
132 var prettify = function (s) {
129 s = s.replace(/-$/, 'minus'); // catch shortcuts using '-' key
133 s = s.replace(/-$/, 'minus'); // catch shortcuts using '-' key
130 var keys = s.split('-');
134 var keys = s.split('-');
131 var k, i;
135 var k, i;
132 for (i=0; i < keys.length; i++) {
136 for (i=0; i < keys.length; i++) {
133 k = keys[i];
137 k = keys[i];
134 if ( k.length == 1 ) {
138 if ( k.length == 1 ) {
135 keys[i] = "<code><strong>" + k + "</strong></code>";
139 keys[i] = "<code><strong>" + k + "</strong></code>";
136 continue; // leave individual keys lower-cased
140 continue; // leave individual keys lower-cased
137 }
141 }
138 if (k.indexOf(',') === -1){
142 if (k.indexOf(',') === -1){
139 keys[i] = ( special_case[k] ? special_case[k] : k.charAt(0).toUpperCase() + k.slice(1) );
143 keys[i] = ( special_case[k] ? special_case[k] : k.charAt(0).toUpperCase() + k.slice(1) );
140 }
144 }
141 keys[i] = "<code><strong>" + keys[i] + "</strong></code>";
145 keys[i] = "<code><strong>" + keys[i] + "</strong></code>";
142 }
146 }
143 return keys.join('-');
147 return keys.join('-');
144
148
145
149
146 };
150 };
147
151
148 QuickHelp.prototype.build_edit_help = function (cm_shortcuts) {
152 QuickHelp.prototype.build_edit_help = function (cm_shortcuts) {
149 var edit_shortcuts = this.keyboard_manager.edit_shortcuts.help();
153 var edit_shortcuts = this.keyboard_manager.edit_shortcuts.help();
150 jQuery.merge(cm_shortcuts, edit_shortcuts);
154 jQuery.merge(cm_shortcuts, edit_shortcuts);
151 return build_div('<h4>Edit Mode (press <code>Enter</code> to enable)</h4>', cm_shortcuts);
155 return build_div('<h4>Edit Mode (press <code>Enter</code> to enable)</h4>', cm_shortcuts);
152 };
156 };
153
157
154 var build_one = function (s) {
158 var build_one = function (s) {
155 var help = s.help;
159 var help = s.help;
156 var shortcut = prettify(s.shortcut);
160 var shortcut = prettify(s.shortcut);
157 return $('<div>').addClass('quickhelp').
161 return $('<div>').addClass('quickhelp').
158 append($('<span/>').addClass('shortcut_key').append($(shortcut))).
162 append($('<span/>').addClass('shortcut_key').append($(shortcut))).
159 append($('<span/>').addClass('shortcut_descr').text(' : ' + help));
163 append($('<span/>').addClass('shortcut_descr').text(' : ' + help));
160
164
161 };
165 };
162
166
163 var build_div = function (title, shortcuts) {
167 var build_div = function (title, shortcuts) {
164 var i, half, n;
168 var i, half, n;
165 var div = $('<div/>').append($(title));
169 var div = $('<div/>').append($(title));
166 var sub_div = $('<div/>').addClass('hbox');
170 var sub_div = $('<div/>').addClass('hbox');
167 var col1 = $('<div/>').addClass('box-flex1');
171 var col1 = $('<div/>').addClass('box-flex1');
168 var col2 = $('<div/>').addClass('box-flex1');
172 var col2 = $('<div/>').addClass('box-flex1');
169 n = shortcuts.length;
173 n = shortcuts.length;
170 half = ~~(n/2); // Truncate :)
174 half = ~~(n/2); // Truncate :)
171 for (i=0; i<half; i++) { col1.append( build_one(shortcuts[i]) ); }
175 for (i=0; i<half; i++) { col1.append( build_one(shortcuts[i]) ); }
172 for (i=half; i<n; i++) { col2.append( build_one(shortcuts[i]) ); }
176 for (i=half; i<n; i++) { col2.append( build_one(shortcuts[i]) ); }
173 sub_div.append(col1).append(col2);
177 sub_div.append(col1).append(col2);
174 div.append(sub_div);
178 div.append(sub_div);
175 return div;
179 return div;
176 };
180 };
177
181
178 // Backwards compatability.
182 // Backwards compatability.
179 IPython.QuickHelp = QuickHelp;
183 IPython.QuickHelp = QuickHelp;
180
184
181 return {'QuickHelp': QuickHelp};
185 return {'QuickHelp': QuickHelp};
182 });
186 });
@@ -1,254 +1,260 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 'moment',
10 'moment',
11 ], function(IPython, $, utils, dialog, keyboard, moment) {
11 ], function(IPython, $, utils, dialog, keyboard, moment) {
12 "use strict";
12 "use strict";
13
13
14 var SaveWidget = function (selector, options) {
14 var SaveWidget = function (selector, options) {
15 // TODO: Remove circular ref.
15 /**
16 * TODO: Remove circular ref.
17 */
16 this.notebook = undefined;
18 this.notebook = undefined;
17 this.selector = selector;
19 this.selector = selector;
18 this.events = options.events;
20 this.events = options.events;
19 this._checkpoint_date = undefined;
21 this._checkpoint_date = undefined;
20 this.keyboard_manager = options.keyboard_manager;
22 this.keyboard_manager = options.keyboard_manager;
21 if (this.selector !== undefined) {
23 if (this.selector !== undefined) {
22 this.element = $(selector);
24 this.element = $(selector);
23 this.bind_events();
25 this.bind_events();
24 }
26 }
25 };
27 };
26
28
27
29
28 SaveWidget.prototype.bind_events = function () {
30 SaveWidget.prototype.bind_events = function () {
29 var that = this;
31 var that = this;
30 this.element.find('span#notebook_name').click(function () {
32 this.element.find('span#notebook_name').click(function () {
31 that.rename_notebook({notebook: that.notebook});
33 that.rename_notebook({notebook: that.notebook});
32 });
34 });
33 this.events.on('notebook_loaded.Notebook', function () {
35 this.events.on('notebook_loaded.Notebook', function () {
34 that.update_notebook_name();
36 that.update_notebook_name();
35 that.update_document_title();
37 that.update_document_title();
36 });
38 });
37 this.events.on('notebook_saved.Notebook', function () {
39 this.events.on('notebook_saved.Notebook', function () {
38 that.update_notebook_name();
40 that.update_notebook_name();
39 that.update_document_title();
41 that.update_document_title();
40 });
42 });
41 this.events.on('notebook_renamed.Notebook', function () {
43 this.events.on('notebook_renamed.Notebook', function () {
42 that.update_notebook_name();
44 that.update_notebook_name();
43 that.update_document_title();
45 that.update_document_title();
44 that.update_address_bar();
46 that.update_address_bar();
45 });
47 });
46 this.events.on('notebook_save_failed.Notebook', function () {
48 this.events.on('notebook_save_failed.Notebook', function () {
47 that.set_save_status('Autosave Failed!');
49 that.set_save_status('Autosave Failed!');
48 });
50 });
49 this.events.on('notebook_read_only.Notebook', function () {
51 this.events.on('notebook_read_only.Notebook', function () {
50 that.set_save_status('(read only)');
52 that.set_save_status('(read only)');
51 // disable future set_save_status
53 // disable future set_save_status
52 that.set_save_status = function () {};
54 that.set_save_status = function () {};
53 });
55 });
54 this.events.on('checkpoints_listed.Notebook', function (event, data) {
56 this.events.on('checkpoints_listed.Notebook', function (event, data) {
55 that._set_last_checkpoint(data[0]);
57 that._set_last_checkpoint(data[0]);
56 });
58 });
57
59
58 this.events.on('checkpoint_created.Notebook', function (event, data) {
60 this.events.on('checkpoint_created.Notebook', function (event, data) {
59 that._set_last_checkpoint(data);
61 that._set_last_checkpoint(data);
60 });
62 });
61 this.events.on('set_dirty.Notebook', function (event, data) {
63 this.events.on('set_dirty.Notebook', function (event, data) {
62 that.set_autosaved(data.value);
64 that.set_autosaved(data.value);
63 });
65 });
64 };
66 };
65
67
66
68
67 SaveWidget.prototype.rename_notebook = function (options) {
69 SaveWidget.prototype.rename_notebook = function (options) {
68 options = options || {};
70 options = options || {};
69 var that = this;
71 var that = this;
70 var dialog_body = $('<div/>').append(
72 var dialog_body = $('<div/>').append(
71 $("<p/>").addClass("rename-message")
73 $("<p/>").addClass("rename-message")
72 .text('Enter a new notebook name:')
74 .text('Enter a new notebook name:')
73 ).append(
75 ).append(
74 $("<br/>")
76 $("<br/>")
75 ).append(
77 ).append(
76 $('<input/>').attr('type','text').attr('size','25').addClass('form-control')
78 $('<input/>').attr('type','text').attr('size','25').addClass('form-control')
77 .val(options.notebook.get_notebook_name())
79 .val(options.notebook.get_notebook_name())
78 );
80 );
79 var d = dialog.modal({
81 var d = dialog.modal({
80 title: "Rename Notebook",
82 title: "Rename Notebook",
81 body: dialog_body,
83 body: dialog_body,
82 notebook: options.notebook,
84 notebook: options.notebook,
83 keyboard_manager: this.keyboard_manager,
85 keyboard_manager: this.keyboard_manager,
84 buttons : {
86 buttons : {
85 "OK": {
87 "OK": {
86 class: "btn-primary",
88 class: "btn-primary",
87 click: function () {
89 click: function () {
88 var new_name = d.find('input').val();
90 var new_name = d.find('input').val();
89 if (!options.notebook.test_notebook_name(new_name)) {
91 if (!options.notebook.test_notebook_name(new_name)) {
90 d.find('.rename-message').text(
92 d.find('.rename-message').text(
91 "Invalid notebook name. Notebook names must "+
93 "Invalid notebook name. Notebook names must "+
92 "have 1 or more characters and can contain any characters " +
94 "have 1 or more characters and can contain any characters " +
93 "except :/\\. Please enter a new notebook name:"
95 "except :/\\. Please enter a new notebook name:"
94 );
96 );
95 return false;
97 return false;
96 } else {
98 } else {
97 d.find('.rename-message').text("Renaming...");
99 d.find('.rename-message').text("Renaming...");
98 d.find('input[type="text"]').prop('disabled', true);
100 d.find('input[type="text"]').prop('disabled', true);
99 that.notebook.rename(new_name).then(
101 that.notebook.rename(new_name).then(
100 function () {
102 function () {
101 d.modal('hide');
103 d.modal('hide');
102 }, function (error) {
104 }, function (error) {
103 d.find('.rename-message').text(error.message || 'Unknown error');
105 d.find('.rename-message').text(error.message || 'Unknown error');
104 d.find('input[type="text"]').prop('disabled', false).focus().select();
106 d.find('input[type="text"]').prop('disabled', false).focus().select();
105 }
107 }
106 );
108 );
107 return false;
109 return false;
108 }
110 }
109 }
111 }
110 },
112 },
111 "Cancel": {}
113 "Cancel": {}
112 },
114 },
113 open : function () {
115 open : function () {
114 // Upon ENTER, click the OK button.
116 /**
117 * Upon ENTER, click the OK button.
118 */
115 d.find('input[type="text"]').keydown(function (event) {
119 d.find('input[type="text"]').keydown(function (event) {
116 if (event.which === keyboard.keycodes.enter) {
120 if (event.which === keyboard.keycodes.enter) {
117 d.find('.btn-primary').first().click();
121 d.find('.btn-primary').first().click();
118 return false;
122 return false;
119 }
123 }
120 });
124 });
121 d.find('input[type="text"]').focus().select();
125 d.find('input[type="text"]').focus().select();
122 }
126 }
123 });
127 });
124 };
128 };
125
129
126
130
127 SaveWidget.prototype.update_notebook_name = function () {
131 SaveWidget.prototype.update_notebook_name = function () {
128 var nbname = this.notebook.get_notebook_name();
132 var nbname = this.notebook.get_notebook_name();
129 this.element.find('span#notebook_name').text(nbname);
133 this.element.find('span#notebook_name').text(nbname);
130 };
134 };
131
135
132
136
133 SaveWidget.prototype.update_document_title = function () {
137 SaveWidget.prototype.update_document_title = function () {
134 var nbname = this.notebook.get_notebook_name();
138 var nbname = this.notebook.get_notebook_name();
135 document.title = nbname;
139 document.title = nbname;
136 };
140 };
137
141
138 SaveWidget.prototype.update_address_bar = function(){
142 SaveWidget.prototype.update_address_bar = function(){
139 var base_url = this.notebook.base_url;
143 var base_url = this.notebook.base_url;
140 var path = this.notebook.notebook_path;
144 var path = this.notebook.notebook_path;
141 var state = {path : path};
145 var state = {path : path};
142 window.history.replaceState(state, "", utils.url_join_encode(
146 window.history.replaceState(state, "", utils.url_join_encode(
143 base_url,
147 base_url,
144 "notebooks",
148 "notebooks",
145 path)
149 path)
146 );
150 );
147 };
151 };
148
152
149
153
150 SaveWidget.prototype.set_save_status = function (msg) {
154 SaveWidget.prototype.set_save_status = function (msg) {
151 this.element.find('span#autosave_status').text(msg);
155 this.element.find('span#autosave_status').text(msg);
152 };
156 };
153
157
154 SaveWidget.prototype._set_checkpoint_status = function (human_date, iso_date) {
158 SaveWidget.prototype._set_checkpoint_status = function (human_date, iso_date) {
155 var el = this.element.find('span#checkpoint_status');
159 var el = this.element.find('span#checkpoint_status');
156 if(human_date){
160 if(human_date){
157 el.text("Last Checkpoint: "+human_date).attr('title',iso_date);
161 el.text("Last Checkpoint: "+human_date).attr('title',iso_date);
158 } else {
162 } else {
159 el.text('').attr('title', 'no-checkpoint');
163 el.text('').attr('title', 'no-checkpoint');
160 }
164 }
161 };
165 };
162
166
163 // compute (roughly) the remaining time in millisecond until the next
167 // compute (roughly) the remaining time in millisecond until the next
164 // moment.js relative time update of the string, which by default
168 // moment.js relative time update of the string, which by default
165 // happend at
169 // happend at
166 // (a few seconds ago)
170 // (a few seconds ago)
167 // - 45sec,
171 // - 45sec,
168 // (a minute ago)
172 // (a minute ago)
169 // - 90sec,
173 // - 90sec,
170 // ( x minutes ago)
174 // ( x minutes ago)
171 // - then every minutes until
175 // - then every minutes until
172 // - 45 min,
176 // - 45 min,
173 // (an hour ago)
177 // (an hour ago)
174 // - 1h45,
178 // - 1h45,
175 // (x hours ago )
179 // (x hours ago )
176 // - then every hours
180 // - then every hours
177 // - 22 hours ago
181 // - 22 hours ago
178 var _next_timeago_update = function(deltatime_ms){
182 var _next_timeago_update = function(deltatime_ms){
179 var s = 1000;
183 var s = 1000;
180 var m = 60*s;
184 var m = 60*s;
181 var h = 60*m;
185 var h = 60*m;
182
186
183 var mtt = moment.relativeTimeThreshold;
187 var mtt = moment.relativeTimeThreshold;
184
188
185 if(deltatime_ms < mtt.s*s){
189 if(deltatime_ms < mtt.s*s){
186 return mtt.s*s-deltatime_ms;
190 return mtt.s*s-deltatime_ms;
187 } else if (deltatime_ms < (mtt.s*s+m)) {
191 } else if (deltatime_ms < (mtt.s*s+m)) {
188 return (mtt.s*s+m)-deltatime_ms;
192 return (mtt.s*s+m)-deltatime_ms;
189 } else if (deltatime_ms < mtt.m*m){
193 } else if (deltatime_ms < mtt.m*m){
190 return m;
194 return m;
191 } else if (deltatime_ms < (mtt.m*m+h)){
195 } else if (deltatime_ms < (mtt.m*m+h)){
192 return (mtt.m*m+h)-deltatime_ms;
196 return (mtt.m*m+h)-deltatime_ms;
193 } else {
197 } else {
194 return h;
198 return h;
195 }
199 }
196 };
200 };
197
201
198 SaveWidget.prototype._regularly_update_checkpoint_date = function(){
202 SaveWidget.prototype._regularly_update_checkpoint_date = function(){
199 if (!this._checkpoint_date) {
203 if (!this._checkpoint_date) {
200 this._set_checkpoint_status(null);
204 this._set_checkpoint_status(null);
201 console.log('no checkpoint done');
205 console.log('no checkpoint done');
202 return;
206 return;
203 }
207 }
204 var chkd = moment(this._checkpoint_date);
208 var chkd = moment(this._checkpoint_date);
205 var longdate = chkd.format('llll');
209 var longdate = chkd.format('llll');
206
210
207 var that = this;
211 var that = this;
208 var recall = function(t){
212 var recall = function(t){
209 // recall slightly later (1s) as long timeout in js might be imprecise,
213 /**
210 // and you want to be call **after** the change of formatting should happend.
214 * recall slightly later (1s) as long timeout in js might be imprecise,
215 * and you want to be call **after** the change of formatting should happend.
216 */
211 return setTimeout(
217 return setTimeout(
212 $.proxy(that._regularly_update_checkpoint_date, that),
218 $.proxy(that._regularly_update_checkpoint_date, that),
213 t + 1000
219 t + 1000
214 );
220 );
215 };
221 };
216 var tdelta = Math.ceil(new Date()-this._checkpoint_date);
222 var tdelta = Math.ceil(new Date()-this._checkpoint_date);
217
223
218 // update regularly for the first 6hours and show
224 // update regularly for the first 6hours and show
219 // <x time> ago
225 // <x time> ago
220 if(tdelta < tdelta < 6*3600*1000){
226 if(tdelta < tdelta < 6*3600*1000){
221 recall(_next_timeago_update(tdelta));
227 recall(_next_timeago_update(tdelta));
222 this._set_checkpoint_status(chkd.fromNow(), longdate);
228 this._set_checkpoint_status(chkd.fromNow(), longdate);
223 // otherwise update every hour and show
229 // otherwise update every hour and show
224 // <Today | yesterday|...> at hh,mm,ss
230 // <Today | yesterday|...> at hh,mm,ss
225 } else {
231 } else {
226 recall(1*3600*1000);
232 recall(1*3600*1000);
227 this._set_checkpoint_status(chkd.calendar(), longdate);
233 this._set_checkpoint_status(chkd.calendar(), longdate);
228 }
234 }
229 };
235 };
230
236
231 SaveWidget.prototype._set_last_checkpoint = function (checkpoint) {
237 SaveWidget.prototype._set_last_checkpoint = function (checkpoint) {
232 if (checkpoint) {
238 if (checkpoint) {
233 this._checkpoint_date = new Date(checkpoint.last_modified);
239 this._checkpoint_date = new Date(checkpoint.last_modified);
234 } else {
240 } else {
235 this._checkpoint_date = null;
241 this._checkpoint_date = null;
236 }
242 }
237 this._regularly_update_checkpoint_date();
243 this._regularly_update_checkpoint_date();
238
244
239 };
245 };
240
246
241 SaveWidget.prototype.set_autosaved = function (dirty) {
247 SaveWidget.prototype.set_autosaved = function (dirty) {
242 if (dirty) {
248 if (dirty) {
243 this.set_save_status("(unsaved changes)");
249 this.set_save_status("(unsaved changes)");
244 } else {
250 } else {
245 this.set_save_status("(autosaved)");
251 this.set_save_status("(autosaved)");
246 }
252 }
247 };
253 };
248
254
249 // Backwards compatibility.
255 // Backwards compatibility.
250 IPython.SaveWidget = SaveWidget;
256 IPython.SaveWidget = SaveWidget;
251
257
252 return {'SaveWidget': SaveWidget};
258 return {'SaveWidget': SaveWidget};
253
259
254 });
260 });
@@ -1,193 +1,215 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 define(['jquery'], function($){
3 define(['jquery'], function($){
4 "use strict";
4 "use strict";
5
5
6 var ScrollManager = function(notebook, options) {
6 var ScrollManager = function(notebook, options) {
7 // Public constructor.
7 /**
8 * Public constructor.
9 */
8 this.notebook = notebook;
10 this.notebook = notebook;
9 options = options || {};
11 options = options || {};
10 this.animation_speed = options.animation_speed || 250; //ms
12 this.animation_speed = options.animation_speed || 250; //ms
11 };
13 };
12
14
13 ScrollManager.prototype.scroll = function (delta) {
15 ScrollManager.prototype.scroll = function (delta) {
14 // Scroll the document.
16 /**
15 //
17 * Scroll the document.
16 // Parameters
18 *
17 // ----------
19 * Parameters
18 // delta: integer
20 * ----------
19 // direction to scroll the document. Positive is downwards.
21 * delta: integer
20 // Unit is one page length.
22 * direction to scroll the document. Positive is downwards.
23 * Unit is one page length.
24 */
21 this.scroll_some(delta);
25 this.scroll_some(delta);
22 return false;
26 return false;
23 };
27 };
24
28
25 ScrollManager.prototype.scroll_to = function(selector) {
29 ScrollManager.prototype.scroll_to = function(selector) {
26 // Scroll to an element in the notebook.
30 /**
31 * Scroll to an element in the notebook.
32 */
27 $('#notebook').animate({'scrollTop': $(selector).offset().top + $('#notebook').scrollTop() - $('#notebook').offset().top}, this.animation_speed);
33 $('#notebook').animate({'scrollTop': $(selector).offset().top + $('#notebook').scrollTop() - $('#notebook').offset().top}, this.animation_speed);
28 };
34 };
29
35
30 ScrollManager.prototype.scroll_some = function(pages) {
36 ScrollManager.prototype.scroll_some = function(pages) {
31 // Scroll up or down a given number of pages.
37 /**
32 //
38 * Scroll up or down a given number of pages.
33 // Parameters
39 *
34 // ----------
40 * Parameters
35 // pages: integer
41 * ----------
36 // number of pages to scroll the document, may be positive or negative.
42 * pages: integer
43 * number of pages to scroll the document, may be positive or negative.
44 */
37 $('#notebook').animate({'scrollTop': $('#notebook').scrollTop() + pages * $('#notebook').height()}, this.animation_speed);
45 $('#notebook').animate({'scrollTop': $('#notebook').scrollTop() + pages * $('#notebook').height()}, this.animation_speed);
38 };
46 };
39
47
40 ScrollManager.prototype.get_first_visible_cell = function() {
48 ScrollManager.prototype.get_first_visible_cell = function() {
41 // Gets the index of the first visible cell in the document.
49 /**
42
50 * Gets the index of the first visible cell in the document.
43 // First, attempt to be smart by guessing the index of the cell we are
51 *
44 // scrolled to. Then, walk from there up or down until the right cell
52 * First, attempt to be smart by guessing the index of the cell we are
45 // is found. To guess the index, get the top of the last cell, and
53 * scrolled to. Then, walk from there up or down until the right cell
46 // divide that by the number of cells to get an average cell height.
54 * is found. To guess the index, get the top of the last cell, and
47 // Then divide the scroll height by the average cell height.
55 * divide that by the number of cells to get an average cell height.
56 * Then divide the scroll height by the average cell height.
57 */
48 var cell_count = this.notebook.ncells();
58 var cell_count = this.notebook.ncells();
49 var first_cell_top = this.notebook.get_cell(0).element.offset().top;
59 var first_cell_top = this.notebook.get_cell(0).element.offset().top;
50 var last_cell_top = this.notebook.get_cell(cell_count-1).element.offset().top;
60 var last_cell_top = this.notebook.get_cell(cell_count-1).element.offset().top;
51 var avg_cell_height = (last_cell_top - first_cell_top) / cell_count;
61 var avg_cell_height = (last_cell_top - first_cell_top) / cell_count;
52 var notebook = $('#notebook');
62 var notebook = $('#notebook');
53 var i = Math.ceil(notebook.scrollTop() / avg_cell_height);
63 var i = Math.ceil(notebook.scrollTop() / avg_cell_height);
54 i = Math.min(Math.max(i , 0), cell_count - 1);
64 i = Math.min(Math.max(i , 0), cell_count - 1);
55
65
56 while (this.notebook.get_cell(i).element.offset().top - first_cell_top < notebook.scrollTop() && i < cell_count - 1) {
66 while (this.notebook.get_cell(i).element.offset().top - first_cell_top < notebook.scrollTop() && i < cell_count - 1) {
57 i += 1;
67 i += 1;
58 }
68 }
59
69
60 while (this.notebook.get_cell(i).element.offset().top - first_cell_top > notebook.scrollTop() - 50 && i >= 0) {
70 while (this.notebook.get_cell(i).element.offset().top - first_cell_top > notebook.scrollTop() - 50 && i >= 0) {
61 i -= 1;
71 i -= 1;
62 }
72 }
63 return Math.min(i + 1, cell_count - 1);
73 return Math.min(i + 1, cell_count - 1);
64 };
74 };
65
75
66
76
67 var TargetScrollManager = function(notebook, options) {
77 var TargetScrollManager = function(notebook, options) {
68 // Public constructor.
78 /**
79 * Public constructor.
80 */
69 ScrollManager.apply(this, [notebook, options]);
81 ScrollManager.apply(this, [notebook, options]);
70 };
82 };
71 TargetScrollManager.prototype = Object.create(ScrollManager.prototype);
83 TargetScrollManager.prototype = Object.create(ScrollManager.prototype);
72
84
73 TargetScrollManager.prototype.is_target = function (index) {
85 TargetScrollManager.prototype.is_target = function (index) {
74 // Check if a cell should be a scroll stop.
86 /**
75 //
87 * Check if a cell should be a scroll stop.
76 // Returns `true` if the cell is a cell that the scroll manager
88 *
77 // should scroll to. Otherwise, false is returned.
89 * Returns `true` if the cell is a cell that the scroll manager
78 //
90 * should scroll to. Otherwise, false is returned.
79 // Parameters
91 *
80 // ----------
92 * Parameters
81 // index: integer
93 * ----------
82 // index of the cell to test.
94 * index: integer
95 * index of the cell to test.
96 */
83 return false;
97 return false;
84 };
98 };
85
99
86 TargetScrollManager.prototype.scroll = function (delta) {
100 TargetScrollManager.prototype.scroll = function (delta) {
87 // Scroll the document.
101 /**
88 //
102 * Scroll the document.
89 // Parameters
103 *
90 // ----------
104 * Parameters
91 // delta: integer
105 * ----------
92 // direction to scroll the document. Positive is downwards.
106 * delta: integer
93 // Units are targets.
107 * direction to scroll the document. Positive is downwards.
94
108 * Units are targets.
95 // Try to scroll to the next slide.
109 *
110 * Try to scroll to the next slide.
111 */
96 var cell_count = this.notebook.ncells();
112 var cell_count = this.notebook.ncells();
97 var selected_index = this.get_first_visible_cell() + delta;
113 var selected_index = this.get_first_visible_cell() + delta;
98 while (0 <= selected_index && selected_index < cell_count && !this.is_target(selected_index)) {
114 while (0 <= selected_index && selected_index < cell_count && !this.is_target(selected_index)) {
99 selected_index += delta;
115 selected_index += delta;
100 }
116 }
101
117
102 if (selected_index < 0 || cell_count <= selected_index) {
118 if (selected_index < 0 || cell_count <= selected_index) {
103 return ScrollManager.prototype.scroll.apply(this, [delta]);
119 return ScrollManager.prototype.scroll.apply(this, [delta]);
104 } else {
120 } else {
105 this.scroll_to(this.notebook.get_cell(selected_index).element);
121 this.scroll_to(this.notebook.get_cell(selected_index).element);
106
122
107 // Cancel browser keyboard scroll.
123 // Cancel browser keyboard scroll.
108 return false;
124 return false;
109 }
125 }
110 };
126 };
111
127
112
128
113 var SlideScrollManager = function(notebook, options) {
129 var SlideScrollManager = function(notebook, options) {
114 // Public constructor.
130 /**
131 * Public constructor.
132 */
115 TargetScrollManager.apply(this, [notebook, options]);
133 TargetScrollManager.apply(this, [notebook, options]);
116 };
134 };
117 SlideScrollManager.prototype = Object.create(TargetScrollManager.prototype);
135 SlideScrollManager.prototype = Object.create(TargetScrollManager.prototype);
118
136
119 SlideScrollManager.prototype.is_target = function (index) {
137 SlideScrollManager.prototype.is_target = function (index) {
120 var cell = this.notebook.get_cell(index);
138 var cell = this.notebook.get_cell(index);
121 return cell.metadata && cell.metadata.slideshow &&
139 return cell.metadata && cell.metadata.slideshow &&
122 cell.metadata.slideshow.slide_type &&
140 cell.metadata.slideshow.slide_type &&
123 (cell.metadata.slideshow.slide_type === "slide" ||
141 (cell.metadata.slideshow.slide_type === "slide" ||
124 cell.metadata.slideshow.slide_type === "subslide");
142 cell.metadata.slideshow.slide_type === "subslide");
125 };
143 };
126
144
127
145
128 var HeadingScrollManager = function(notebook, options) {
146 var HeadingScrollManager = function(notebook, options) {
129 // Public constructor.
147 /**
148 * Public constructor.
149 */
130 ScrollManager.apply(this, [notebook, options]);
150 ScrollManager.apply(this, [notebook, options]);
131 options = options || {};
151 options = options || {};
132 this._level = options.heading_level || 1;
152 this._level = options.heading_level || 1;
133 };
153 };
134 HeadingScrollManager.prototype = Object.create(ScrollManager.prototype)
154 HeadingScrollManager.prototype = Object.create(ScrollManager.prototype)
135
155
136 HeadingScrollManager.prototype.scroll = function (delta) {
156 HeadingScrollManager.prototype.scroll = function (delta) {
137 // Scroll the document.
157 /**
138 //
158 * Scroll the document.
139 // Parameters
159 *
140 // ----------
160 * Parameters
141 // delta: integer
161 * ----------
142 // direction to scroll the document. Positive is downwards.
162 * delta: integer
143 // Units are headers.
163 * direction to scroll the document. Positive is downwards.
144
164 * Units are headers.
145 // Get all of the header elements that match the heading level or are of
165 *
146 // greater magnitude (a smaller header number).
166 * Get all of the header elements that match the heading level or are of
167 * greater magnitude (a smaller header number).
168 */
147 var headers = $();
169 var headers = $();
148 var i;
170 var i;
149 for (i = 1; i <= this._level; i++) {
171 for (i = 1; i <= this._level; i++) {
150 headers = headers.add('#notebook-container h' + i);
172 headers = headers.add('#notebook-container h' + i);
151 }
173 }
152
174
153 // Find the header the user is on or below.
175 // Find the header the user is on or below.
154 var first_cell_top = this.notebook.get_cell(0).element.offset().top;
176 var first_cell_top = this.notebook.get_cell(0).element.offset().top;
155 var notebook = $('#notebook');
177 var notebook = $('#notebook');
156 var current_scroll = notebook.scrollTop();
178 var current_scroll = notebook.scrollTop();
157 var header_scroll = 0;
179 var header_scroll = 0;
158 i = -1;
180 i = -1;
159 while (current_scroll >= header_scroll && i < headers.length) {
181 while (current_scroll >= header_scroll && i < headers.length) {
160 if (++i < headers.length) {
182 if (++i < headers.length) {
161 header_scroll = $(headers[i]).offset().top - first_cell_top;
183 header_scroll = $(headers[i]).offset().top - first_cell_top;
162 }
184 }
163 }
185 }
164 i--;
186 i--;
165
187
166 // Check if the user is below the header.
188 // Check if the user is below the header.
167 if (i < 0 || current_scroll > $(headers[i]).offset().top - first_cell_top + 30) {
189 if (i < 0 || current_scroll > $(headers[i]).offset().top - first_cell_top + 30) {
168 // Below the header, count the header as a target.
190 // Below the header, count the header as a target.
169 if (delta < 0) {
191 if (delta < 0) {
170 delta += 1;
192 delta += 1;
171 }
193 }
172 }
194 }
173 i += delta;
195 i += delta;
174
196
175 // Scroll!
197 // Scroll!
176 if (0 <= i && i < headers.length) {
198 if (0 <= i && i < headers.length) {
177 this.scroll_to(headers[i]);
199 this.scroll_to(headers[i]);
178 return false;
200 return false;
179 } else {
201 } else {
180 // Default to the base's scroll behavior when target header doesn't
202 // Default to the base's scroll behavior when target header doesn't
181 // exist.
203 // exist.
182 return ScrollManager.prototype.scroll.apply(this, [delta]);
204 return ScrollManager.prototype.scroll.apply(this, [delta]);
183 }
205 }
184 };
206 };
185
207
186 // Return naemspace for require.js loads
208 // Return naemspace for require.js loads
187 return {
209 return {
188 'ScrollManager': ScrollManager,
210 'ScrollManager': ScrollManager,
189 'SlideScrollManager': SlideScrollManager,
211 'SlideScrollManager': SlideScrollManager,
190 'HeadingScrollManager': HeadingScrollManager,
212 'HeadingScrollManager': HeadingScrollManager,
191 'TargetScrollManager': TargetScrollManager
213 'TargetScrollManager': TargetScrollManager
192 };
214 };
193 });
215 });
@@ -1,344 +1,352 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 'notebook/js/cell',
8 'notebook/js/cell',
9 'base/js/security',
9 'base/js/security',
10 'notebook/js/mathjaxutils',
10 'notebook/js/mathjaxutils',
11 'notebook/js/celltoolbar',
11 'notebook/js/celltoolbar',
12 'components/marked/lib/marked',
12 'components/marked/lib/marked',
13 'codemirror/lib/codemirror',
13 'codemirror/lib/codemirror',
14 'codemirror/mode/gfm/gfm',
14 'codemirror/mode/gfm/gfm',
15 'notebook/js/codemirror-ipythongfm'
15 'notebook/js/codemirror-ipythongfm'
16 ], function(IPython,utils , $, cell, security, mathjaxutils, celltoolbar, marked, CodeMirror, gfm, ipgfm) {
16 ], function(IPython,utils , $, cell, security, mathjaxutils, celltoolbar, marked, CodeMirror, gfm, ipgfm) {
17 "use strict";
17 "use strict";
18 var Cell = cell.Cell;
18 var Cell = cell.Cell;
19
19
20 var TextCell = function (options) {
20 var TextCell = function (options) {
21 // Constructor
21 /**
22 //
22 * Constructor
23 // Construct a new TextCell, codemirror mode is by default 'htmlmixed',
23 *
24 // and cell type is 'text' cell start as not redered.
24 * Construct a new TextCell, codemirror mode is by default 'htmlmixed',
25 //
25 * and cell type is 'text' cell start as not redered.
26 // Parameters:
26 *
27 // options: dictionary
27 * Parameters:
28 // Dictionary of keyword arguments.
28 * options: dictionary
29 // events: $(Events) instance
29 * Dictionary of keyword arguments.
30 // config: dictionary
30 * events: $(Events) instance
31 // keyboard_manager: KeyboardManager instance
31 * config: dictionary
32 // notebook: Notebook instance
32 * keyboard_manager: KeyboardManager instance
33 * notebook: Notebook instance
34 */
33 options = options || {};
35 options = options || {};
34
36
35 // in all TextCell/Cell subclasses
37 // in all TextCell/Cell subclasses
36 // do not assign most of members here, just pass it down
38 // do not assign most of members here, just pass it down
37 // in the options dict potentially overwriting what you wish.
39 // in the options dict potentially overwriting what you wish.
38 // they will be assigned in the base class.
40 // they will be assigned in the base class.
39 this.notebook = options.notebook;
41 this.notebook = options.notebook;
40 this.events = options.events;
42 this.events = options.events;
41 this.config = options.config;
43 this.config = options.config;
42
44
43 // we cannot put this as a class key as it has handle to "this".
45 // we cannot put this as a class key as it has handle to "this".
44 var config = utils.mergeopt(TextCell, this.config);
46 var config = utils.mergeopt(TextCell, this.config);
45 Cell.apply(this, [{
47 Cell.apply(this, [{
46 config: config,
48 config: config,
47 keyboard_manager: options.keyboard_manager,
49 keyboard_manager: options.keyboard_manager,
48 events: this.events}]);
50 events: this.events}]);
49
51
50 this.cell_type = this.cell_type || 'text';
52 this.cell_type = this.cell_type || 'text';
51 mathjaxutils = mathjaxutils;
53 mathjaxutils = mathjaxutils;
52 this.rendered = false;
54 this.rendered = false;
53 };
55 };
54
56
55 TextCell.prototype = Object.create(Cell.prototype);
57 TextCell.prototype = Object.create(Cell.prototype);
56
58
57 TextCell.options_default = {
59 TextCell.options_default = {
58 cm_config : {
60 cm_config : {
59 extraKeys: {"Tab": "indentMore","Shift-Tab" : "indentLess"},
61 extraKeys: {"Tab": "indentMore","Shift-Tab" : "indentLess"},
60 mode: 'htmlmixed',
62 mode: 'htmlmixed',
61 lineWrapping : true,
63 lineWrapping : true,
62 }
64 }
63 };
65 };
64
66
65
67
66 /**
68 /**
67 * Create the DOM element of the TextCell
69 * Create the DOM element of the TextCell
68 * @method create_element
70 * @method create_element
69 * @private
71 * @private
70 */
72 */
71 TextCell.prototype.create_element = function () {
73 TextCell.prototype.create_element = function () {
72 Cell.prototype.create_element.apply(this, arguments);
74 Cell.prototype.create_element.apply(this, arguments);
73
75
74 var cell = $("<div>").addClass('cell text_cell');
76 var cell = $("<div>").addClass('cell text_cell');
75 cell.attr('tabindex','2');
77 cell.attr('tabindex','2');
76
78
77 var prompt = $('<div/>').addClass('prompt input_prompt');
79 var prompt = $('<div/>').addClass('prompt input_prompt');
78 cell.append(prompt);
80 cell.append(prompt);
79 var inner_cell = $('<div/>').addClass('inner_cell');
81 var inner_cell = $('<div/>').addClass('inner_cell');
80 this.celltoolbar = new celltoolbar.CellToolbar({
82 this.celltoolbar = new celltoolbar.CellToolbar({
81 cell: this,
83 cell: this,
82 notebook: this.notebook});
84 notebook: this.notebook});
83 inner_cell.append(this.celltoolbar.element);
85 inner_cell.append(this.celltoolbar.element);
84 var input_area = $('<div/>').addClass('input_area');
86 var input_area = $('<div/>').addClass('input_area');
85 this.code_mirror = new CodeMirror(input_area.get(0), this.cm_config);
87 this.code_mirror = new CodeMirror(input_area.get(0), this.cm_config);
86 this.code_mirror.on('keydown', $.proxy(this.handle_keyevent,this))
88 this.code_mirror.on('keydown', $.proxy(this.handle_keyevent,this))
87 // The tabindex=-1 makes this div focusable.
89 // The tabindex=-1 makes this div focusable.
88 var render_area = $('<div/>').addClass('text_cell_render rendered_html')
90 var render_area = $('<div/>').addClass('text_cell_render rendered_html')
89 .attr('tabindex','-1');
91 .attr('tabindex','-1');
90 inner_cell.append(input_area).append(render_area);
92 inner_cell.append(input_area).append(render_area);
91 cell.append(inner_cell);
93 cell.append(inner_cell);
92 this.element = cell;
94 this.element = cell;
93 };
95 };
94
96
95
97
96 // Cell level actions
98 // Cell level actions
97
99
98 TextCell.prototype.select = function () {
100 TextCell.prototype.select = function () {
99 var cont = Cell.prototype.select.apply(this);
101 var cont = Cell.prototype.select.apply(this);
100 if (cont) {
102 if (cont) {
101 if (this.mode === 'edit') {
103 if (this.mode === 'edit') {
102 this.code_mirror.refresh();
104 this.code_mirror.refresh();
103 }
105 }
104 }
106 }
105 return cont;
107 return cont;
106 };
108 };
107
109
108 TextCell.prototype.unrender = function () {
110 TextCell.prototype.unrender = function () {
109 if (this.read_only) return;
111 if (this.read_only) return;
110 var cont = Cell.prototype.unrender.apply(this);
112 var cont = Cell.prototype.unrender.apply(this);
111 if (cont) {
113 if (cont) {
112 var text_cell = this.element;
114 var text_cell = this.element;
113 var output = text_cell.find("div.text_cell_render");
115 var output = text_cell.find("div.text_cell_render");
114 if (this.get_text() === this.placeholder) {
116 if (this.get_text() === this.placeholder) {
115 this.set_text('');
117 this.set_text('');
116 }
118 }
117 this.refresh();
119 this.refresh();
118 }
120 }
119 return cont;
121 return cont;
120 };
122 };
121
123
122 TextCell.prototype.execute = function () {
124 TextCell.prototype.execute = function () {
123 this.render();
125 this.render();
124 };
126 };
125
127
126 /**
128 /**
127 * setter: {{#crossLink "TextCell/set_text"}}{{/crossLink}}
129 * setter: {{#crossLink "TextCell/set_text"}}{{/crossLink}}
128 * @method get_text
130 * @method get_text
129 * @retrun {string} CodeMirror current text value
131 * @retrun {string} CodeMirror current text value
130 */
132 */
131 TextCell.prototype.get_text = function() {
133 TextCell.prototype.get_text = function() {
132 return this.code_mirror.getValue();
134 return this.code_mirror.getValue();
133 };
135 };
134
136
135 /**
137 /**
136 * @param {string} text - Codemiror text value
138 * @param {string} text - Codemiror text value
137 * @see TextCell#get_text
139 * @see TextCell#get_text
138 * @method set_text
140 * @method set_text
139 * */
141 * */
140 TextCell.prototype.set_text = function(text) {
142 TextCell.prototype.set_text = function(text) {
141 this.code_mirror.setValue(text);
143 this.code_mirror.setValue(text);
142 this.unrender();
144 this.unrender();
143 this.code_mirror.refresh();
145 this.code_mirror.refresh();
144 };
146 };
145
147
146 /**
148 /**
147 * setter :{{#crossLink "TextCell/set_rendered"}}{{/crossLink}}
149 * setter :{{#crossLink "TextCell/set_rendered"}}{{/crossLink}}
148 * @method get_rendered
150 * @method get_rendered
149 * */
151 * */
150 TextCell.prototype.get_rendered = function() {
152 TextCell.prototype.get_rendered = function() {
151 return this.element.find('div.text_cell_render').html();
153 return this.element.find('div.text_cell_render').html();
152 };
154 };
153
155
154 /**
156 /**
155 * @method set_rendered
157 * @method set_rendered
156 */
158 */
157 TextCell.prototype.set_rendered = function(text) {
159 TextCell.prototype.set_rendered = function(text) {
158 this.element.find('div.text_cell_render').html(text);
160 this.element.find('div.text_cell_render').html(text);
159 };
161 };
160
162
161
163
162 /**
164 /**
163 * Create Text cell from JSON
165 * Create Text cell from JSON
164 * @param {json} data - JSON serialized text-cell
166 * @param {json} data - JSON serialized text-cell
165 * @method fromJSON
167 * @method fromJSON
166 */
168 */
167 TextCell.prototype.fromJSON = function (data) {
169 TextCell.prototype.fromJSON = function (data) {
168 Cell.prototype.fromJSON.apply(this, arguments);
170 Cell.prototype.fromJSON.apply(this, arguments);
169 if (data.cell_type === this.cell_type) {
171 if (data.cell_type === this.cell_type) {
170 if (data.source !== undefined) {
172 if (data.source !== undefined) {
171 this.set_text(data.source);
173 this.set_text(data.source);
172 // make this value the starting point, so that we can only undo
174 // make this value the starting point, so that we can only undo
173 // to this state, instead of a blank cell
175 // to this state, instead of a blank cell
174 this.code_mirror.clearHistory();
176 this.code_mirror.clearHistory();
175 // TODO: This HTML needs to be treated as potentially dangerous
177 // TODO: This HTML needs to be treated as potentially dangerous
176 // user input and should be handled before set_rendered.
178 // user input and should be handled before set_rendered.
177 this.set_rendered(data.rendered || '');
179 this.set_rendered(data.rendered || '');
178 this.rendered = false;
180 this.rendered = false;
179 this.render();
181 this.render();
180 }
182 }
181 }
183 }
182 };
184 };
183
185
184 /** Generate JSON from cell
186 /** Generate JSON from cell
185 * @return {object} cell data serialised to json
187 * @return {object} cell data serialised to json
186 */
188 */
187 TextCell.prototype.toJSON = function () {
189 TextCell.prototype.toJSON = function () {
188 var data = Cell.prototype.toJSON.apply(this);
190 var data = Cell.prototype.toJSON.apply(this);
189 data.source = this.get_text();
191 data.source = this.get_text();
190 if (data.source == this.placeholder) {
192 if (data.source == this.placeholder) {
191 data.source = "";
193 data.source = "";
192 }
194 }
193 return data;
195 return data;
194 };
196 };
195
197
196
198
197 var MarkdownCell = function (options) {
199 var MarkdownCell = function (options) {
198 // Constructor
200 /**
199 //
201 * Constructor
200 // Parameters:
202 *
201 // options: dictionary
203 * Parameters:
202 // Dictionary of keyword arguments.
204 * options: dictionary
203 // events: $(Events) instance
205 * Dictionary of keyword arguments.
204 // config: dictionary
206 * events: $(Events) instance
205 // keyboard_manager: KeyboardManager instance
207 * config: dictionary
206 // notebook: Notebook instance
208 * keyboard_manager: KeyboardManager instance
209 * notebook: Notebook instance
210 */
207 options = options || {};
211 options = options || {};
208 var config = utils.mergeopt(MarkdownCell, options.config);
212 var config = utils.mergeopt(MarkdownCell, options.config);
209 TextCell.apply(this, [$.extend({}, options, {config: config})]);
213 TextCell.apply(this, [$.extend({}, options, {config: config})]);
210
214
211 this.cell_type = 'markdown';
215 this.cell_type = 'markdown';
212 };
216 };
213
217
214 MarkdownCell.options_default = {
218 MarkdownCell.options_default = {
215 cm_config: {
219 cm_config: {
216 mode: 'ipythongfm'
220 mode: 'ipythongfm'
217 },
221 },
218 placeholder: "Type *Markdown* and LaTeX: $\\alpha^2$"
222 placeholder: "Type *Markdown* and LaTeX: $\\alpha^2$"
219 };
223 };
220
224
221 MarkdownCell.prototype = Object.create(TextCell.prototype);
225 MarkdownCell.prototype = Object.create(TextCell.prototype);
222
226
223 MarkdownCell.prototype.set_heading_level = function (level) {
227 MarkdownCell.prototype.set_heading_level = function (level) {
224 // make a markdown cell a heading
228 /**
229 * make a markdown cell a heading
230 */
225 level = level || 1;
231 level = level || 1;
226 var source = this.get_text();
232 var source = this.get_text();
227 source = source.replace(/^(#*)\s?/,
233 source = source.replace(/^(#*)\s?/,
228 new Array(level + 1).join('#') + ' ');
234 new Array(level + 1).join('#') + ' ');
229 this.set_text(source);
235 this.set_text(source);
230 this.refresh();
236 this.refresh();
231 if (this.rendered) {
237 if (this.rendered) {
232 this.render();
238 this.render();
233 }
239 }
234 };
240 };
235
241
236 /**
242 /**
237 * @method render
243 * @method render
238 */
244 */
239 MarkdownCell.prototype.render = function () {
245 MarkdownCell.prototype.render = function () {
240 var cont = TextCell.prototype.render.apply(this);
246 var cont = TextCell.prototype.render.apply(this);
241 if (cont) {
247 if (cont) {
242 var that = this;
248 var that = this;
243 var text = this.get_text();
249 var text = this.get_text();
244 var math = null;
250 var math = null;
245 if (text === "") { text = this.placeholder; }
251 if (text === "") { text = this.placeholder; }
246 var text_and_math = mathjaxutils.remove_math(text);
252 var text_and_math = mathjaxutils.remove_math(text);
247 text = text_and_math[0];
253 text = text_and_math[0];
248 math = text_and_math[1];
254 math = text_and_math[1];
249 marked(text, function (err, html) {
255 marked(text, function (err, html) {
250 html = mathjaxutils.replace_math(html, math);
256 html = mathjaxutils.replace_math(html, math);
251 html = security.sanitize_html(html);
257 html = security.sanitize_html(html);
252 html = $($.parseHTML(html));
258 html = $($.parseHTML(html));
253 // add anchors to headings
259 // add anchors to headings
254 html.find(":header").addBack(":header").each(function (i, h) {
260 html.find(":header").addBack(":header").each(function (i, h) {
255 h = $(h);
261 h = $(h);
256 var hash = h.text().replace(/ /g, '-');
262 var hash = h.text().replace(/ /g, '-');
257 h.attr('id', hash);
263 h.attr('id', hash);
258 h.append(
264 h.append(
259 $('<a/>')
265 $('<a/>')
260 .addClass('anchor-link')
266 .addClass('anchor-link')
261 .attr('href', '#' + hash)
267 .attr('href', '#' + hash)
262 .text('¶')
268 .text('¶')
263 );
269 );
264 });
270 });
265 // links in markdown cells should open in new tabs
271 // links in markdown cells should open in new tabs
266 html.find("a[href]").not('[href^="#"]').attr("target", "_blank");
272 html.find("a[href]").not('[href^="#"]').attr("target", "_blank");
267 that.set_rendered(html);
273 that.set_rendered(html);
268 that.typeset();
274 that.typeset();
269 that.events.trigger("rendered.MarkdownCell", {cell: that});
275 that.events.trigger("rendered.MarkdownCell", {cell: that});
270 });
276 });
271 }
277 }
272 return cont;
278 return cont;
273 };
279 };
274
280
275
281
276 var RawCell = function (options) {
282 var RawCell = function (options) {
277 // Constructor
283 /**
278 //
284 * Constructor
279 // Parameters:
285 *
280 // options: dictionary
286 * Parameters:
281 // Dictionary of keyword arguments.
287 * options: dictionary
282 // events: $(Events) instance
288 * Dictionary of keyword arguments.
283 // config: dictionary
289 * events: $(Events) instance
284 // keyboard_manager: KeyboardManager instance
290 * config: dictionary
285 // notebook: Notebook instance
291 * keyboard_manager: KeyboardManager instance
292 * notebook: Notebook instance
293 */
286 options = options || {};
294 options = options || {};
287 var config = utils.mergeopt(RawCell, options.config);
295 var config = utils.mergeopt(RawCell, options.config);
288 TextCell.apply(this, [$.extend({}, options, {config: config})]);
296 TextCell.apply(this, [$.extend({}, options, {config: config})]);
289
297
290 this.cell_type = 'raw';
298 this.cell_type = 'raw';
291 };
299 };
292
300
293 RawCell.options_default = {
301 RawCell.options_default = {
294 placeholder : "Write raw LaTeX or other formats here, for use with nbconvert. " +
302 placeholder : "Write raw LaTeX or other formats here, for use with nbconvert. " +
295 "It will not be rendered in the notebook. " +
303 "It will not be rendered in the notebook. " +
296 "When passing through nbconvert, a Raw Cell's content is added to the output unmodified."
304 "When passing through nbconvert, a Raw Cell's content is added to the output unmodified."
297 };
305 };
298
306
299 RawCell.prototype = Object.create(TextCell.prototype);
307 RawCell.prototype = Object.create(TextCell.prototype);
300
308
301 /** @method bind_events **/
309 /** @method bind_events **/
302 RawCell.prototype.bind_events = function () {
310 RawCell.prototype.bind_events = function () {
303 TextCell.prototype.bind_events.apply(this);
311 TextCell.prototype.bind_events.apply(this);
304 var that = this;
312 var that = this;
305 this.element.focusout(function() {
313 this.element.focusout(function() {
306 that.auto_highlight();
314 that.auto_highlight();
307 that.render();
315 that.render();
308 });
316 });
309
317
310 this.code_mirror.on('focus', function() { that.unrender(); });
318 this.code_mirror.on('focus', function() { that.unrender(); });
311 };
319 };
312
320
313 /**
321 /**
314 * Trigger autodetection of highlight scheme for current cell
322 * Trigger autodetection of highlight scheme for current cell
315 * @method auto_highlight
323 * @method auto_highlight
316 */
324 */
317 RawCell.prototype.auto_highlight = function () {
325 RawCell.prototype.auto_highlight = function () {
318 this._auto_highlight(this.config.raw_cell_highlight);
326 this._auto_highlight(this.config.raw_cell_highlight);
319 };
327 };
320
328
321 /** @method render **/
329 /** @method render **/
322 RawCell.prototype.render = function () {
330 RawCell.prototype.render = function () {
323 var cont = TextCell.prototype.render.apply(this);
331 var cont = TextCell.prototype.render.apply(this);
324 if (cont){
332 if (cont){
325 var text = this.get_text();
333 var text = this.get_text();
326 if (text === "") { text = this.placeholder; }
334 if (text === "") { text = this.placeholder; }
327 this.set_text(text);
335 this.set_text(text);
328 this.element.removeClass('rendered');
336 this.element.removeClass('rendered');
329 }
337 }
330 return cont;
338 return cont;
331 };
339 };
332
340
333 // Backwards compatability.
341 // Backwards compatability.
334 IPython.TextCell = TextCell;
342 IPython.TextCell = TextCell;
335 IPython.MarkdownCell = MarkdownCell;
343 IPython.MarkdownCell = MarkdownCell;
336 IPython.RawCell = RawCell;
344 IPython.RawCell = RawCell;
337
345
338 var textcell = {
346 var textcell = {
339 TextCell: TextCell,
347 TextCell: TextCell,
340 MarkdownCell: MarkdownCell,
348 MarkdownCell: MarkdownCell,
341 RawCell: RawCell
349 RawCell: RawCell
342 };
350 };
343 return textcell;
351 return textcell;
344 });
352 });
@@ -1,309 +1,317 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 /**
120 * reexecute last call in pager by appending ? to show back in pager
121 */
120 this.events.trigger('open_with_text.Pager', this._reply.content);
122 this.events.trigger('open_with_text.Pager', this._reply.content);
121 this.remove_and_cancel_tooltip();
123 this.remove_and_cancel_tooltip();
122 };
124 };
123
125
124 // grow the tooltip verticaly
126 // grow the tooltip verticaly
125 Tooltip.prototype.expand = function () {
127 Tooltip.prototype.expand = function () {
126 this.text.removeClass('smalltooltip');
128 this.text.removeClass('smalltooltip');
127 this.text.addClass('bigtooltip');
129 this.text.addClass('bigtooltip');
128 $('#expanbutton').hide('slow');
130 $('#expanbutton').hide('slow');
129 };
131 };
130
132
131 // deal with all the logic of hiding the tooltip
133 // deal with all the logic of hiding the tooltip
132 // and reset it's status
134 // and reset it's status
133 Tooltip.prototype._hide = function () {
135 Tooltip.prototype._hide = function () {
134 this._hidden = true;
136 this._hidden = true;
135 this.tooltip.fadeOut('fast');
137 this.tooltip.fadeOut('fast');
136 $('#expanbutton').show('slow');
138 $('#expanbutton').show('slow');
137 this.text.removeClass('bigtooltip');
139 this.text.removeClass('bigtooltip');
138 this.text.addClass('smalltooltip');
140 this.text.addClass('smalltooltip');
139 // keep scroll top to be sure to always see the first line
141 // keep scroll top to be sure to always see the first line
140 this.text.scrollTop(0);
142 this.text.scrollTop(0);
141 this.code_mirror = null;
143 this.code_mirror = null;
142 };
144 };
143
145
144 // return true on successfully removing a visible tooltip; otherwise return
146 // return true on successfully removing a visible tooltip; otherwise return
145 // false.
147 // false.
146 Tooltip.prototype.remove_and_cancel_tooltip = function (force) {
148 Tooltip.prototype.remove_and_cancel_tooltip = function (force) {
147 // note that we don't handle closing directly inside the calltip
149 /**
148 // as in the completer, because it is not focusable, so won't
150 * note that we don't handle closing directly inside the calltip
149 // get the event.
151 * as in the completer, because it is not focusable, so won't
152 * get the event.
153 */
150 this.cancel_pending();
154 this.cancel_pending();
151 if (!this._hidden) {
155 if (!this._hidden) {
152 if (force || !this._sticky) {
156 if (force || !this._sticky) {
153 this.cancel_stick();
157 this.cancel_stick();
154 this._hide();
158 this._hide();
155 }
159 }
156 this.reset_tabs_function();
160 this.reset_tabs_function();
157 return true;
161 return true;
158 } else {
162 } else {
159 return false;
163 return false;
160 }
164 }
161 };
165 };
162
166
163 // cancel autocall done after '(' for example.
167 // cancel autocall done after '(' for example.
164 Tooltip.prototype.cancel_pending = function () {
168 Tooltip.prototype.cancel_pending = function () {
165 if (this._tooltip_timeout !== null) {
169 if (this._tooltip_timeout !== null) {
166 clearTimeout(this._tooltip_timeout);
170 clearTimeout(this._tooltip_timeout);
167 this._tooltip_timeout = null;
171 this._tooltip_timeout = null;
168 }
172 }
169 };
173 };
170
174
171 // will trigger tooltip after timeout
175 // will trigger tooltip after timeout
172 Tooltip.prototype.pending = function (cell, hide_if_no_docstring) {
176 Tooltip.prototype.pending = function (cell, hide_if_no_docstring) {
173 var that = this;
177 var that = this;
174 this._tooltip_timeout = setTimeout(function () {
178 this._tooltip_timeout = setTimeout(function () {
175 that.request(cell, hide_if_no_docstring);
179 that.request(cell, hide_if_no_docstring);
176 }, that.time_before_tooltip);
180 }, that.time_before_tooltip);
177 };
181 };
178
182
179 // easy access for julia monkey patching.
183 // easy access for julia monkey patching.
180 Tooltip.last_token_re = /[a-z_][0-9a-z._]*$/gi;
184 Tooltip.last_token_re = /[a-z_][0-9a-z._]*$/gi;
181
185
182 Tooltip.prototype._request_tooltip = function (cell, text, cursor_pos) {
186 Tooltip.prototype._request_tooltip = function (cell, text, cursor_pos) {
183 var callbacks = $.proxy(this._show, this);
187 var callbacks = $.proxy(this._show, this);
184 var msg_id = cell.kernel.inspect(text, cursor_pos, callbacks);
188 var msg_id = cell.kernel.inspect(text, cursor_pos, callbacks);
185 };
189 };
186
190
187 // make an immediate completion request
191 // make an immediate completion request
188 Tooltip.prototype.request = function (cell, hide_if_no_docstring) {
192 Tooltip.prototype.request = function (cell, hide_if_no_docstring) {
189 // request(codecell)
193 /**
190 // Deal with extracting the text from the cell and counting
194 * request(codecell)
191 // call in a row
195 * Deal with extracting the text from the cell and counting
196 * call in a row
197 */
192 this.cancel_pending();
198 this.cancel_pending();
193 var editor = cell.code_mirror;
199 var editor = cell.code_mirror;
194 var cursor = editor.getCursor();
200 var cursor = editor.getCursor();
195 var cursor_pos = utils.to_absolute_cursor_pos(editor, cursor);
201 var cursor_pos = utils.to_absolute_cursor_pos(editor, cursor);
196 var text = cell.get_text();
202 var text = cell.get_text();
197
203
198 this._hide_if_no_docstring = hide_if_no_docstring;
204 this._hide_if_no_docstring = hide_if_no_docstring;
199
205
200 if(editor.somethingSelected()){
206 if(editor.somethingSelected()){
201 // get only the most recent selection.
207 // get only the most recent selection.
202 text = editor.getSelection();
208 text = editor.getSelection();
203 }
209 }
204
210
205 // need a permanent handle to code_mirror for future auto recall
211 // need a permanent handle to code_mirror for future auto recall
206 this.code_mirror = editor;
212 this.code_mirror = editor;
207
213
208 // now we treat the different number of keypress
214 // now we treat the different number of keypress
209 // first if same cell, same text, increment counter by 1
215 // first if same cell, same text, increment counter by 1
210 if (this._old_cell == cell && this._old_request == text && this._hidden === false) {
216 if (this._old_cell == cell && this._old_request == text && this._hidden === false) {
211 this._consecutive_counter++;
217 this._consecutive_counter++;
212 } else {
218 } else {
213 // else reset
219 // else reset
214 this.cancel_stick();
220 this.cancel_stick();
215 this.reset_tabs_function (cell, text);
221 this.reset_tabs_function (cell, text);
216 }
222 }
217
223
218 this.tabs_functions[this._consecutive_counter](cell, text, cursor_pos);
224 this.tabs_functions[this._consecutive_counter](cell, text, cursor_pos);
219
225
220 // then if we are at the end of list function, reset
226 // then if we are at the end of list function, reset
221 if (this._consecutive_counter == this.tabs_functions.length) {
227 if (this._consecutive_counter == this.tabs_functions.length) {
222 this.reset_tabs_function (cell, text, cursor);
228 this.reset_tabs_function (cell, text, cursor);
223 }
229 }
224
230
225 return;
231 return;
226 };
232 };
227
233
228 // cancel the option of having the tooltip to stick
234 // cancel the option of having the tooltip to stick
229 Tooltip.prototype.cancel_stick = function () {
235 Tooltip.prototype.cancel_stick = function () {
230 clearTimeout(this._stick_timeout);
236 clearTimeout(this._stick_timeout);
231 this._stick_timeout = null;
237 this._stick_timeout = null;
232 this._clocklink.hide('slow');
238 this._clocklink.hide('slow');
233 this._sticky = false;
239 this._sticky = false;
234 };
240 };
235
241
236 // put the tooltip in a sicky state for 10 seconds
242 // put the tooltip in a sicky state for 10 seconds
237 // it won't be removed by remove_and_cancell() unless you called with
243 // it won't be removed by remove_and_cancell() unless you called with
238 // the first parameter set to true.
244 // the first parameter set to true.
239 // remove_and_cancell_tooltip(true)
245 // remove_and_cancell_tooltip(true)
240 Tooltip.prototype.stick = function (time) {
246 Tooltip.prototype.stick = function (time) {
241 time = (time !== undefined) ? time : 10;
247 time = (time !== undefined) ? time : 10;
242 var that = this;
248 var that = this;
243 this._sticky = true;
249 this._sticky = true;
244 this._clocklink.show('slow');
250 this._clocklink.show('slow');
245 this._stick_timeout = setTimeout(function () {
251 this._stick_timeout = setTimeout(function () {
246 that._sticky = false;
252 that._sticky = false;
247 that._clocklink.hide('slow');
253 that._clocklink.hide('slow');
248 }, time * 1000);
254 }, time * 1000);
249 };
255 };
250
256
251 // should be called with the kernel reply to actually show the tooltip
257 // should be called with the kernel reply to actually show the tooltip
252 Tooltip.prototype._show = function (reply) {
258 Tooltip.prototype._show = function (reply) {
253 // move the bubble if it is not hidden
259 /**
254 // otherwise fade it
260 * move the bubble if it is not hidden
261 * otherwise fade it
262 */
255 this._reply = reply;
263 this._reply = reply;
256 var content = reply.content;
264 var content = reply.content;
257 if (!content.found) {
265 if (!content.found) {
258 // object not found, nothing to show
266 // object not found, nothing to show
259 return;
267 return;
260 }
268 }
261 this.name = content.name;
269 this.name = content.name;
262
270
263 // do some math to have the tooltip arrow on more or less on left or right
271 // do some math to have the tooltip arrow on more or less on left or right
264 // width of the editor
272 // width of the editor
265 var w = $(this.code_mirror.getScrollerElement()).width();
273 var w = $(this.code_mirror.getScrollerElement()).width();
266 // ofset of the editor
274 // ofset of the editor
267 var o = $(this.code_mirror.getScrollerElement()).offset();
275 var o = $(this.code_mirror.getScrollerElement()).offset();
268
276
269 // whatever anchor/head order but arrow at mid x selection
277 // whatever anchor/head order but arrow at mid x selection
270 var anchor = this.code_mirror.cursorCoords(false);
278 var anchor = this.code_mirror.cursorCoords(false);
271 var head = this.code_mirror.cursorCoords(true);
279 var head = this.code_mirror.cursorCoords(true);
272 var xinit = (head.left+anchor.left)/2;
280 var xinit = (head.left+anchor.left)/2;
273 var xinter = o.left + (xinit - o.left) / w * (w - 450);
281 var xinter = o.left + (xinit - o.left) / w * (w - 450);
274 var posarrowleft = xinit - xinter;
282 var posarrowleft = xinit - xinter;
275
283
276 if (this._hidden === false) {
284 if (this._hidden === false) {
277 this.tooltip.animate({
285 this.tooltip.animate({
278 'left': xinter - 30 + 'px',
286 'left': xinter - 30 + 'px',
279 'top': (head.bottom + 10) + 'px'
287 'top': (head.bottom + 10) + 'px'
280 });
288 });
281 } else {
289 } else {
282 this.tooltip.css({
290 this.tooltip.css({
283 'left': xinter - 30 + 'px'
291 'left': xinter - 30 + 'px'
284 });
292 });
285 this.tooltip.css({
293 this.tooltip.css({
286 'top': (head.bottom + 10) + 'px'
294 'top': (head.bottom + 10) + 'px'
287 });
295 });
288 }
296 }
289 this.arrow.animate({
297 this.arrow.animate({
290 'left': posarrowleft + 'px'
298 'left': posarrowleft + 'px'
291 });
299 });
292
300
293 this._hidden = false;
301 this._hidden = false;
294 this.tooltip.fadeIn('fast');
302 this.tooltip.fadeIn('fast');
295 this.text.children().remove();
303 this.text.children().remove();
296
304
297 // This should support rich data types, but only text/plain for now
305 // This should support rich data types, but only text/plain for now
298 // Any HTML within the docstring is escaped by the fixConsole() method.
306 // Any HTML within the docstring is escaped by the fixConsole() method.
299 var pre = $('<pre/>').html(utils.fixConsole(content.data['text/plain']));
307 var pre = $('<pre/>').html(utils.fixConsole(content.data['text/plain']));
300 this.text.append(pre);
308 this.text.append(pre);
301 // keep scroll top to be sure to always see the first line
309 // keep scroll top to be sure to always see the first line
302 this.text.scrollTop(0);
310 this.text.scrollTop(0);
303 };
311 };
304
312
305 // Backwards compatibility.
313 // Backwards compatibility.
306 IPython.Tooltip = Tooltip;
314 IPython.Tooltip = Tooltip;
307
315
308 return {'Tooltip': Tooltip};
316 return {'Tooltip': Tooltip};
309 });
317 });
@@ -1,242 +1,250 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 var Contents = function(options) {
9 var Contents = function(options) {
10 // Constructor
10 /**
11 //
11 * Constructor
12 // A contents handles passing file operations
12 *
13 // to the back-end. This includes checkpointing
13 * A contents handles passing file operations
14 // with the normal file operations.
14 * to the back-end. This includes checkpointing
15 //
15 * with the normal file operations.
16 // Parameters:
16 *
17 // options: dictionary
17 * Parameters:
18 // Dictionary of keyword arguments.
18 * options: dictionary
19 // base_url: string
19 * Dictionary of keyword arguments.
20 * base_url: string
21 */
20 this.base_url = options.base_url;
22 this.base_url = options.base_url;
21 };
23 };
22
24
23 /** Error type */
25 /** Error type */
24 Contents.DIRECTORY_NOT_EMPTY_ERROR = 'DirectoryNotEmptyError';
26 Contents.DIRECTORY_NOT_EMPTY_ERROR = 'DirectoryNotEmptyError';
25
27
26 Contents.DirectoryNotEmptyError = function() {
28 Contents.DirectoryNotEmptyError = function() {
27 // Constructor
29 // Constructor
28 //
30 //
29 // An error representing the result of attempting to delete a non-empty
31 // An error representing the result of attempting to delete a non-empty
30 // directory.
32 // directory.
31 this.message = 'A directory must be empty before being deleted.';
33 this.message = 'A directory must be empty before being deleted.';
32 };
34 };
33
35
34 Contents.DirectoryNotEmptyError.prototype = Object.create(Error.prototype);
36 Contents.DirectoryNotEmptyError.prototype = Object.create(Error.prototype);
35 Contents.DirectoryNotEmptyError.prototype.name =
37 Contents.DirectoryNotEmptyError.prototype.name =
36 Contents.DIRECTORY_NOT_EMPTY_ERROR;
38 Contents.DIRECTORY_NOT_EMPTY_ERROR;
37
39
38
40
39 Contents.prototype.api_url = function() {
41 Contents.prototype.api_url = function() {
40 var url_parts = [this.base_url, 'api/contents'].concat(
42 var url_parts = [this.base_url, 'api/contents'].concat(
41 Array.prototype.slice.apply(arguments));
43 Array.prototype.slice.apply(arguments));
42 return utils.url_join_encode.apply(null, url_parts);
44 return utils.url_join_encode.apply(null, url_parts);
43 };
45 };
44
46
45 /**
47 /**
46 * Creates a basic error handler that wraps a jqXHR error as an Error.
48 * Creates a basic error handler that wraps a jqXHR error as an Error.
47 *
49 *
48 * Takes a callback that accepts an Error, and returns a callback that can
50 * Takes a callback that accepts an Error, and returns a callback that can
49 * be passed directly to $.ajax, which will wrap the error from jQuery
51 * be passed directly to $.ajax, which will wrap the error from jQuery
50 * as an Error, and pass that to the original callback.
52 * as an Error, and pass that to the original callback.
51 *
53 *
52 * @method create_basic_error_handler
54 * @method create_basic_error_handler
53 * @param{Function} callback
55 * @param{Function} callback
54 * @return{Function}
56 * @return{Function}
55 */
57 */
56 Contents.prototype.create_basic_error_handler = function(callback) {
58 Contents.prototype.create_basic_error_handler = function(callback) {
57 if (!callback) {
59 if (!callback) {
58 return utils.log_ajax_error;
60 return utils.log_ajax_error;
59 }
61 }
60 return function(xhr, status, error) {
62 return function(xhr, status, error) {
61 callback(utils.wrap_ajax_error(xhr, status, error));
63 callback(utils.wrap_ajax_error(xhr, status, error));
62 };
64 };
63 };
65 };
64
66
65 /**
67 /**
66 * File Functions (including notebook operations)
68 * File Functions (including notebook operations)
67 */
69 */
68
70
69 /**
71 /**
70 * Get a file.
72 * Get a file.
71 *
73 *
72 * Calls success with file JSON model, or error with error.
74 * Calls success with file JSON model, or error with error.
73 *
75 *
74 * @method get
76 * @method get
75 * @param {String} path
77 * @param {String} path
76 * @param {Object} options
78 * @param {Object} options
77 * type : 'notebook', 'file', or 'directory'
79 * type : 'notebook', 'file', or 'directory'
78 * format: 'text' or 'base64'; only relevant for type: 'file'
80 * format: 'text' or 'base64'; only relevant for type: 'file'
79 */
81 */
80 Contents.prototype.get = function (path, options) {
82 Contents.prototype.get = function (path, options) {
81 // We do the call with settings so we can set cache to false.
83 /**
84 * We do the call with settings so we can set cache to false.
85 */
82 var settings = {
86 var settings = {
83 processData : false,
87 processData : false,
84 cache : false,
88 cache : false,
85 type : "GET",
89 type : "GET",
86 dataType : "json",
90 dataType : "json",
87 };
91 };
88 var url = this.api_url(path);
92 var url = this.api_url(path);
89 params = {};
93 params = {};
90 if (options.type) { params.type = options.type; }
94 if (options.type) { params.type = options.type; }
91 if (options.format) { params.format = options.format; }
95 if (options.format) { params.format = options.format; }
92 return utils.promising_ajax(url + '?' + $.param(params), settings);
96 return utils.promising_ajax(url + '?' + $.param(params), settings);
93 };
97 };
94
98
95
99
96 /**
100 /**
97 * Creates a new untitled file or directory in the specified directory path.
101 * Creates a new untitled file or directory in the specified directory path.
98 *
102 *
99 * @method new
103 * @method new
100 * @param {String} path: the directory in which to create the new file/directory
104 * @param {String} path: the directory in which to create the new file/directory
101 * @param {Object} options:
105 * @param {Object} options:
102 * ext: file extension to use
106 * ext: file extension to use
103 * type: model type to create ('notebook', 'file', or 'directory')
107 * type: model type to create ('notebook', 'file', or 'directory')
104 */
108 */
105 Contents.prototype.new_untitled = function(path, options) {
109 Contents.prototype.new_untitled = function(path, options) {
106 var data = JSON.stringify({
110 var data = JSON.stringify({
107 ext: options.ext,
111 ext: options.ext,
108 type: options.type
112 type: options.type
109 });
113 });
110
114
111 var settings = {
115 var settings = {
112 processData : false,
116 processData : false,
113 type : "POST",
117 type : "POST",
114 data: data,
118 data: data,
115 dataType : "json",
119 dataType : "json",
116 };
120 };
117 return utils.promising_ajax(this.api_url(path), settings);
121 return utils.promising_ajax(this.api_url(path), settings);
118 };
122 };
119
123
120 Contents.prototype.delete = function(path) {
124 Contents.prototype.delete = function(path) {
121 var settings = {
125 var settings = {
122 processData : false,
126 processData : false,
123 type : "DELETE",
127 type : "DELETE",
124 dataType : "json",
128 dataType : "json",
125 };
129 };
126 var url = this.api_url(path);
130 var url = this.api_url(path);
127 return utils.promising_ajax(url, settings).catch(
131 return utils.promising_ajax(url, settings).catch(
128 // Translate certain errors to more specific ones.
132 // Translate certain errors to more specific ones.
129 function(error) {
133 function(error) {
130 // TODO: update IPEP27 to specify errors more precisely, so
134 // TODO: update IPEP27 to specify errors more precisely, so
131 // that error types can be detected here with certainty.
135 // that error types can be detected here with certainty.
132 if (error.xhr.status === 400) {
136 if (error.xhr.status === 400) {
133 throw new Contents.DirectoryNotEmptyError();
137 throw new Contents.DirectoryNotEmptyError();
134 }
138 }
135 throw error;
139 throw error;
136 }
140 }
137 );
141 );
138 };
142 };
139
143
140 Contents.prototype.rename = function(path, new_path) {
144 Contents.prototype.rename = function(path, new_path) {
141 var data = {path: new_path};
145 var data = {path: new_path};
142 var settings = {
146 var settings = {
143 processData : false,
147 processData : false,
144 type : "PATCH",
148 type : "PATCH",
145 data : JSON.stringify(data),
149 data : JSON.stringify(data),
146 dataType: "json",
150 dataType: "json",
147 contentType: 'application/json',
151 contentType: 'application/json',
148 };
152 };
149 var url = this.api_url(path);
153 var url = this.api_url(path);
150 return utils.promising_ajax(url, settings);
154 return utils.promising_ajax(url, settings);
151 };
155 };
152
156
153 Contents.prototype.save = function(path, model) {
157 Contents.prototype.save = function(path, model) {
154 // We do the call with settings so we can set cache to false.
158 /**
159 * We do the call with settings so we can set cache to false.
160 */
155 var settings = {
161 var settings = {
156 processData : false,
162 processData : false,
157 type : "PUT",
163 type : "PUT",
158 data : JSON.stringify(model),
164 data : JSON.stringify(model),
159 contentType: 'application/json',
165 contentType: 'application/json',
160 };
166 };
161 var url = this.api_url(path);
167 var url = this.api_url(path);
162 return utils.promising_ajax(url, settings);
168 return utils.promising_ajax(url, settings);
163 };
169 };
164
170
165 Contents.prototype.copy = function(from_file, to_dir) {
171 Contents.prototype.copy = function(from_file, to_dir) {
166 // Copy a file into a given directory via POST
172 /**
167 // The server will select the name of the copied file
173 * Copy a file into a given directory via POST
174 * The server will select the name of the copied file
175 */
168 var url = this.api_url(to_dir);
176 var url = this.api_url(to_dir);
169
177
170 var settings = {
178 var settings = {
171 processData : false,
179 processData : false,
172 type: "POST",
180 type: "POST",
173 data: JSON.stringify({copy_from: from_file}),
181 data: JSON.stringify({copy_from: from_file}),
174 dataType : "json",
182 dataType : "json",
175 };
183 };
176 return utils.promising_ajax(url, settings);
184 return utils.promising_ajax(url, settings);
177 };
185 };
178
186
179 /**
187 /**
180 * Checkpointing Functions
188 * Checkpointing Functions
181 */
189 */
182
190
183 Contents.prototype.create_checkpoint = function(path) {
191 Contents.prototype.create_checkpoint = function(path) {
184 var url = this.api_url(path, 'checkpoints');
192 var url = this.api_url(path, 'checkpoints');
185 var settings = {
193 var settings = {
186 type : "POST",
194 type : "POST",
187 dataType : "json",
195 dataType : "json",
188 };
196 };
189 return utils.promising_ajax(url, settings);
197 return utils.promising_ajax(url, settings);
190 };
198 };
191
199
192 Contents.prototype.list_checkpoints = function(path) {
200 Contents.prototype.list_checkpoints = function(path) {
193 var url = this.api_url(path, 'checkpoints');
201 var url = this.api_url(path, 'checkpoints');
194 var settings = {
202 var settings = {
195 type : "GET",
203 type : "GET",
196 cache: false,
204 cache: false,
197 dataType: "json",
205 dataType: "json",
198 };
206 };
199 return utils.promising_ajax(url, settings);
207 return utils.promising_ajax(url, settings);
200 };
208 };
201
209
202 Contents.prototype.restore_checkpoint = function(path, checkpoint_id) {
210 Contents.prototype.restore_checkpoint = function(path, checkpoint_id) {
203 var url = this.api_url(path, 'checkpoints', checkpoint_id);
211 var url = this.api_url(path, 'checkpoints', checkpoint_id);
204 var settings = {
212 var settings = {
205 type : "POST",
213 type : "POST",
206 };
214 };
207 return utils.promising_ajax(url, settings);
215 return utils.promising_ajax(url, settings);
208 };
216 };
209
217
210 Contents.prototype.delete_checkpoint = function(path, checkpoint_id) {
218 Contents.prototype.delete_checkpoint = function(path, checkpoint_id) {
211 var url = this.api_url(path, 'checkpoints', checkpoint_id);
219 var url = this.api_url(path, 'checkpoints', checkpoint_id);
212 var settings = {
220 var settings = {
213 type : "DELETE",
221 type : "DELETE",
214 };
222 };
215 return utils.promising_ajax(url, settings);
223 return utils.promising_ajax(url, settings);
216 };
224 };
217
225
218 /**
226 /**
219 * File management functions
227 * File management functions
220 */
228 */
221
229
222 /**
230 /**
223 * List notebooks and directories at a given path
231 * List notebooks and directories at a given path
224 *
232 *
225 * On success, load_callback is called with an array of dictionaries
233 * On success, load_callback is called with an array of dictionaries
226 * representing individual files or directories. Each dictionary has
234 * representing individual files or directories. Each dictionary has
227 * the keys:
235 * the keys:
228 * type: "notebook" or "directory"
236 * type: "notebook" or "directory"
229 * created: created date
237 * created: created date
230 * last_modified: last modified dat
238 * last_modified: last modified dat
231 * @method list_notebooks
239 * @method list_notebooks
232 * @param {String} path The path to list notebooks in
240 * @param {String} path The path to list notebooks in
233 */
241 */
234 Contents.prototype.list_contents = function(path) {
242 Contents.prototype.list_contents = function(path) {
235 return this.get(path, {type: 'directory'});
243 return this.get(path, {type: 'directory'});
236 };
244 };
237
245
238
246
239 IPython.Contents = Contents;
247 IPython.Contents = Contents;
240
248
241 return {'Contents': Contents};
249 return {'Contents': Contents};
242 });
250 });
@@ -1,205 +1,217 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 define([
4 define([
5 'base/js/namespace',
5 'base/js/namespace',
6 'jquery',
6 'jquery',
7 'base/js/utils',
7 'base/js/utils',
8 ], function(IPython, $, utils) {
8 ], function(IPython, $, utils) {
9 "use strict";
9 "use strict";
10
10
11 //-----------------------------------------------------------------------
11 //-----------------------------------------------------------------------
12 // CommManager class
12 // CommManager class
13 //-----------------------------------------------------------------------
13 //-----------------------------------------------------------------------
14
14
15 var CommManager = function (kernel) {
15 var CommManager = function (kernel) {
16 this.comms = {};
16 this.comms = {};
17 this.targets = {};
17 this.targets = {};
18 if (kernel !== undefined) {
18 if (kernel !== undefined) {
19 this.init_kernel(kernel);
19 this.init_kernel(kernel);
20 }
20 }
21 };
21 };
22
22
23 CommManager.prototype.init_kernel = function (kernel) {
23 CommManager.prototype.init_kernel = function (kernel) {
24 // connect the kernel, and register message handlers
24 /**
25 * connect the kernel, and register message handlers
26 */
25 this.kernel = kernel;
27 this.kernel = kernel;
26 var msg_types = ['comm_open', 'comm_msg', 'comm_close'];
28 var msg_types = ['comm_open', 'comm_msg', 'comm_close'];
27 for (var i = 0; i < msg_types.length; i++) {
29 for (var i = 0; i < msg_types.length; i++) {
28 var msg_type = msg_types[i];
30 var msg_type = msg_types[i];
29 kernel.register_iopub_handler(msg_type, $.proxy(this[msg_type], this));
31 kernel.register_iopub_handler(msg_type, $.proxy(this[msg_type], this));
30 }
32 }
31 };
33 };
32
34
33 CommManager.prototype.new_comm = function (target_name, data, callbacks, metadata) {
35 CommManager.prototype.new_comm = function (target_name, data, callbacks, metadata) {
34 // Create a new Comm, register it, and open its Kernel-side counterpart
36 /**
35 // Mimics the auto-registration in `Comm.__init__` in the IPython Comm
37 * Create a new Comm, register it, and open its Kernel-side counterpart
38 * Mimics the auto-registration in `Comm.__init__` in the IPython Comm
39 */
36 var comm = new Comm(target_name);
40 var comm = new Comm(target_name);
37 this.register_comm(comm);
41 this.register_comm(comm);
38 comm.open(data, callbacks, metadata);
42 comm.open(data, callbacks, metadata);
39 return comm;
43 return comm;
40 };
44 };
41
45
42 CommManager.prototype.register_target = function (target_name, f) {
46 CommManager.prototype.register_target = function (target_name, f) {
43 // Register a target function for a given target name
47 /**
48 * Register a target function for a given target name
49 */
44 this.targets[target_name] = f;
50 this.targets[target_name] = f;
45 };
51 };
46
52
47 CommManager.prototype.unregister_target = function (target_name, f) {
53 CommManager.prototype.unregister_target = function (target_name, f) {
48 // Unregister a target function for a given target name
54 /**
55 * Unregister a target function for a given target name
56 */
49 delete this.targets[target_name];
57 delete this.targets[target_name];
50 };
58 };
51
59
52 CommManager.prototype.register_comm = function (comm) {
60 CommManager.prototype.register_comm = function (comm) {
53 // Register a comm in the mapping
61 /**
62 * Register a comm in the mapping
63 */
54 this.comms[comm.comm_id] = Promise.resolve(comm);
64 this.comms[comm.comm_id] = Promise.resolve(comm);
55 comm.kernel = this.kernel;
65 comm.kernel = this.kernel;
56 return comm.comm_id;
66 return comm.comm_id;
57 };
67 };
58
68
59 CommManager.prototype.unregister_comm = function (comm) {
69 CommManager.prototype.unregister_comm = function (comm) {
60 // Remove a comm from the mapping
70 /**
71 * Remove a comm from the mapping
72 */
61 delete this.comms[comm.comm_id];
73 delete this.comms[comm.comm_id];
62 };
74 };
63
75
64 // comm message handlers
76 // comm message handlers
65
77
66 CommManager.prototype.comm_open = function (msg) {
78 CommManager.prototype.comm_open = function (msg) {
67 var content = msg.content;
79 var content = msg.content;
68 var that = this;
80 var that = this;
69 var comm_id = content.comm_id;
81 var comm_id = content.comm_id;
70
82
71 this.comms[comm_id] = utils.load_class(content.target_name, content.target_module,
83 this.comms[comm_id] = utils.load_class(content.target_name, content.target_module,
72 this.targets).then(function(target) {
84 this.targets).then(function(target) {
73 var comm = new Comm(content.target_name, comm_id);
85 var comm = new Comm(content.target_name, comm_id);
74 comm.kernel = that.kernel;
86 comm.kernel = that.kernel;
75 try {
87 try {
76 var response = target(comm, msg);
88 var response = target(comm, msg);
77 } catch (e) {
89 } catch (e) {
78 comm.close();
90 comm.close();
79 that.unregister_comm(comm);
91 that.unregister_comm(comm);
80 var wrapped_error = new utils.WrappedError("Exception opening new comm", e);
92 var wrapped_error = new utils.WrappedError("Exception opening new comm", e);
81 console.error(wrapped_error);
93 console.error(wrapped_error);
82 return Promise.reject(wrapped_error);
94 return Promise.reject(wrapped_error);
83 }
95 }
84 // Regardless of the target return value, we need to
96 // Regardless of the target return value, we need to
85 // then return the comm
97 // then return the comm
86 return Promise.resolve(response).then(function() {return comm;});
98 return Promise.resolve(response).then(function() {return comm;});
87 }, utils.reject('Could not open comm', true));
99 }, utils.reject('Could not open comm', true));
88 return this.comms[comm_id];
100 return this.comms[comm_id];
89 };
101 };
90
102
91 CommManager.prototype.comm_close = function(msg) {
103 CommManager.prototype.comm_close = function(msg) {
92 var content = msg.content;
104 var content = msg.content;
93 if (this.comms[content.comm_id] === undefined) {
105 if (this.comms[content.comm_id] === undefined) {
94 console.error('Comm promise not found for comm id ' + content.comm_id);
106 console.error('Comm promise not found for comm id ' + content.comm_id);
95 return;
107 return;
96 }
108 }
97
109
98 this.comms[content.comm_id] = this.comms[content.comm_id].then(function(comm) {
110 this.comms[content.comm_id] = this.comms[content.comm_id].then(function(comm) {
99 this.unregister_comm(comm);
111 this.unregister_comm(comm);
100 try {
112 try {
101 comm.handle_close(msg);
113 comm.handle_close(msg);
102 } catch (e) {
114 } catch (e) {
103 console.log("Exception closing comm: ", e, e.stack, msg);
115 console.log("Exception closing comm: ", e, e.stack, msg);
104 }
116 }
105 // don't return a comm, so that further .then() functions
117 // don't return a comm, so that further .then() functions
106 // get an undefined comm input
118 // get an undefined comm input
107 });
119 });
108 };
120 };
109
121
110 CommManager.prototype.comm_msg = function(msg) {
122 CommManager.prototype.comm_msg = function(msg) {
111 var content = msg.content;
123 var content = msg.content;
112 if (this.comms[content.comm_id] === undefined) {
124 if (this.comms[content.comm_id] === undefined) {
113 console.error('Comm promise not found for comm id ' + content.comm_id);
125 console.error('Comm promise not found for comm id ' + content.comm_id);
114 return;
126 return;
115 }
127 }
116
128
117 this.comms[content.comm_id] = this.comms[content.comm_id].then(function(comm) {
129 this.comms[content.comm_id] = this.comms[content.comm_id].then(function(comm) {
118 try {
130 try {
119 comm.handle_msg(msg);
131 comm.handle_msg(msg);
120 } catch (e) {
132 } catch (e) {
121 console.log("Exception handling comm msg: ", e, e.stack, msg);
133 console.log("Exception handling comm msg: ", e, e.stack, msg);
122 }
134 }
123 return comm;
135 return comm;
124 });
136 });
125 };
137 };
126
138
127 //-----------------------------------------------------------------------
139 //-----------------------------------------------------------------------
128 // Comm base class
140 // Comm base class
129 //-----------------------------------------------------------------------
141 //-----------------------------------------------------------------------
130
142
131 var Comm = function (target_name, comm_id) {
143 var Comm = function (target_name, comm_id) {
132 this.target_name = target_name;
144 this.target_name = target_name;
133 this.comm_id = comm_id || utils.uuid();
145 this.comm_id = comm_id || utils.uuid();
134 this._msg_callback = this._close_callback = null;
146 this._msg_callback = this._close_callback = null;
135 };
147 };
136
148
137 // methods for sending messages
149 // methods for sending messages
138 Comm.prototype.open = function (data, callbacks, metadata) {
150 Comm.prototype.open = function (data, callbacks, metadata) {
139 var content = {
151 var content = {
140 comm_id : this.comm_id,
152 comm_id : this.comm_id,
141 target_name : this.target_name,
153 target_name : this.target_name,
142 data : data || {},
154 data : data || {},
143 };
155 };
144 return this.kernel.send_shell_message("comm_open", content, callbacks, metadata);
156 return this.kernel.send_shell_message("comm_open", content, callbacks, metadata);
145 };
157 };
146
158
147 Comm.prototype.send = function (data, callbacks, metadata, buffers) {
159 Comm.prototype.send = function (data, callbacks, metadata, buffers) {
148 var content = {
160 var content = {
149 comm_id : this.comm_id,
161 comm_id : this.comm_id,
150 data : data || {},
162 data : data || {},
151 };
163 };
152 return this.kernel.send_shell_message("comm_msg", content, callbacks, metadata, buffers);
164 return this.kernel.send_shell_message("comm_msg", content, callbacks, metadata, buffers);
153 };
165 };
154
166
155 Comm.prototype.close = function (data, callbacks, metadata) {
167 Comm.prototype.close = function (data, callbacks, metadata) {
156 var content = {
168 var content = {
157 comm_id : this.comm_id,
169 comm_id : this.comm_id,
158 data : data || {},
170 data : data || {},
159 };
171 };
160 return this.kernel.send_shell_message("comm_close", content, callbacks, metadata);
172 return this.kernel.send_shell_message("comm_close", content, callbacks, metadata);
161 };
173 };
162
174
163 // methods for registering callbacks for incoming messages
175 // methods for registering callbacks for incoming messages
164 Comm.prototype._register_callback = function (key, callback) {
176 Comm.prototype._register_callback = function (key, callback) {
165 this['_' + key + '_callback'] = callback;
177 this['_' + key + '_callback'] = callback;
166 };
178 };
167
179
168 Comm.prototype.on_msg = function (callback) {
180 Comm.prototype.on_msg = function (callback) {
169 this._register_callback('msg', callback);
181 this._register_callback('msg', callback);
170 };
182 };
171
183
172 Comm.prototype.on_close = function (callback) {
184 Comm.prototype.on_close = function (callback) {
173 this._register_callback('close', callback);
185 this._register_callback('close', callback);
174 };
186 };
175
187
176 // methods for handling incoming messages
188 // methods for handling incoming messages
177
189
178 Comm.prototype._callback = function (key, msg) {
190 Comm.prototype._callback = function (key, msg) {
179 var callback = this['_' + key + '_callback'];
191 var callback = this['_' + key + '_callback'];
180 if (callback) {
192 if (callback) {
181 try {
193 try {
182 callback(msg);
194 callback(msg);
183 } catch (e) {
195 } catch (e) {
184 console.log("Exception in Comm callback", e, e.stack, msg);
196 console.log("Exception in Comm callback", e, e.stack, msg);
185 }
197 }
186 }
198 }
187 };
199 };
188
200
189 Comm.prototype.handle_msg = function (msg) {
201 Comm.prototype.handle_msg = function (msg) {
190 this._callback('msg', msg);
202 this._callback('msg', msg);
191 };
203 };
192
204
193 Comm.prototype.handle_close = function (msg) {
205 Comm.prototype.handle_close = function (msg) {
194 this._callback('close', msg);
206 this._callback('close', msg);
195 };
207 };
196
208
197 // For backwards compatability.
209 // For backwards compatability.
198 IPython.CommManager = CommManager;
210 IPython.CommManager = CommManager;
199 IPython.Comm = Comm;
211 IPython.Comm = Comm;
200
212
201 return {
213 return {
202 'CommManager': CommManager,
214 'CommManager': CommManager,
203 'Comm': Comm
215 'Comm': Comm
204 };
216 };
205 });
217 });
@@ -1,1052 +1,1056 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 './comm',
8 './comm',
9 './serialize',
9 './serialize',
10 'widgets/js/init'
10 'widgets/js/init'
11 ], function(IPython, $, utils, comm, serialize, widgetmanager) {
11 ], function(IPython, $, utils, comm, serialize, widgetmanager) {
12 "use strict";
12 "use strict";
13
13
14 /**
14 /**
15 * A Kernel class to communicate with the Python kernel. This
15 * A Kernel class to communicate with the Python kernel. This
16 * should generally not be constructed directly, but be created
16 * should generally not be constructed directly, but be created
17 * by. the `Session` object. Once created, this object should be
17 * by. the `Session` object. Once created, this object should be
18 * used to communicate with the kernel.
18 * used to communicate with the kernel.
19 *
19 *
20 * @class Kernel
20 * @class Kernel
21 * @param {string} kernel_service_url - the URL to access the kernel REST api
21 * @param {string} kernel_service_url - the URL to access the kernel REST api
22 * @param {string} ws_url - the websockets URL
22 * @param {string} ws_url - the websockets URL
23 * @param {Notebook} notebook - notebook object
23 * @param {Notebook} notebook - notebook object
24 * @param {string} name - the kernel type (e.g. python3)
24 * @param {string} name - the kernel type (e.g. python3)
25 */
25 */
26 var Kernel = function (kernel_service_url, ws_url, notebook, name) {
26 var Kernel = function (kernel_service_url, ws_url, notebook, name) {
27 this.events = notebook.events;
27 this.events = notebook.events;
28
28
29 this.id = null;
29 this.id = null;
30 this.name = name;
30 this.name = name;
31
31
32 this.channels = {
32 this.channels = {
33 'shell': null,
33 'shell': null,
34 'iopub': null,
34 'iopub': null,
35 'stdin': null
35 'stdin': null
36 };
36 };
37
37
38 this.kernel_service_url = kernel_service_url;
38 this.kernel_service_url = kernel_service_url;
39 this.kernel_url = null;
39 this.kernel_url = null;
40 this.ws_url = ws_url || IPython.utils.get_body_data("wsUrl");
40 this.ws_url = ws_url || IPython.utils.get_body_data("wsUrl");
41 if (!this.ws_url) {
41 if (!this.ws_url) {
42 // trailing 's' in https will become wss for secure web sockets
42 // trailing 's' in https will become wss for secure web sockets
43 this.ws_url = location.protocol.replace('http', 'ws') + "//" + location.host;
43 this.ws_url = location.protocol.replace('http', 'ws') + "//" + location.host;
44 }
44 }
45
45
46 this.username = "username";
46 this.username = "username";
47 this.session_id = utils.uuid();
47 this.session_id = utils.uuid();
48 this._msg_callbacks = {};
48 this._msg_callbacks = {};
49 this.info_reply = {}; // kernel_info_reply stored here after starting
49 this.info_reply = {}; // kernel_info_reply stored here after starting
50
50
51 if (typeof(WebSocket) !== 'undefined') {
51 if (typeof(WebSocket) !== 'undefined') {
52 this.WebSocket = WebSocket;
52 this.WebSocket = WebSocket;
53 } else if (typeof(MozWebSocket) !== 'undefined') {
53 } else if (typeof(MozWebSocket) !== 'undefined') {
54 this.WebSocket = MozWebSocket;
54 this.WebSocket = MozWebSocket;
55 } else {
55 } else {
56 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.');
56 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.');
57 }
57 }
58
58
59 this.bind_events();
59 this.bind_events();
60 this.init_iopub_handlers();
60 this.init_iopub_handlers();
61 this.comm_manager = new comm.CommManager(this);
61 this.comm_manager = new comm.CommManager(this);
62 this.widget_manager = new widgetmanager.WidgetManager(this.comm_manager, notebook);
62 this.widget_manager = new widgetmanager.WidgetManager(this.comm_manager, notebook);
63
63
64 this.last_msg_id = null;
64 this.last_msg_id = null;
65 this.last_msg_callbacks = {};
65 this.last_msg_callbacks = {};
66
66
67 this._autorestart_attempt = 0;
67 this._autorestart_attempt = 0;
68 this._reconnect_attempt = 0;
68 this._reconnect_attempt = 0;
69 this.reconnect_limit = 7;
69 this.reconnect_limit = 7;
70 };
70 };
71
71
72 /**
72 /**
73 * @function _get_msg
73 * @function _get_msg
74 */
74 */
75 Kernel.prototype._get_msg = function (msg_type, content, metadata, buffers) {
75 Kernel.prototype._get_msg = function (msg_type, content, metadata, buffers) {
76 var msg = {
76 var msg = {
77 header : {
77 header : {
78 msg_id : utils.uuid(),
78 msg_id : utils.uuid(),
79 username : this.username,
79 username : this.username,
80 session : this.session_id,
80 session : this.session_id,
81 msg_type : msg_type,
81 msg_type : msg_type,
82 version : "5.0"
82 version : "5.0"
83 },
83 },
84 metadata : metadata || {},
84 metadata : metadata || {},
85 content : content,
85 content : content,
86 buffers : buffers || [],
86 buffers : buffers || [],
87 parent_header : {}
87 parent_header : {}
88 };
88 };
89 return msg;
89 return msg;
90 };
90 };
91
91
92 /**
92 /**
93 * @function bind_events
93 * @function bind_events
94 */
94 */
95 Kernel.prototype.bind_events = function () {
95 Kernel.prototype.bind_events = function () {
96 var that = this;
96 var that = this;
97 this.events.on('send_input_reply.Kernel', function(evt, data) {
97 this.events.on('send_input_reply.Kernel', function(evt, data) {
98 that.send_input_reply(data);
98 that.send_input_reply(data);
99 });
99 });
100
100
101 var record_status = function (evt, info) {
101 var record_status = function (evt, info) {
102 console.log('Kernel: ' + evt.type + ' (' + info.kernel.id + ')');
102 console.log('Kernel: ' + evt.type + ' (' + info.kernel.id + ')');
103 };
103 };
104
104
105 this.events.on('kernel_created.Kernel', record_status);
105 this.events.on('kernel_created.Kernel', record_status);
106 this.events.on('kernel_reconnecting.Kernel', record_status);
106 this.events.on('kernel_reconnecting.Kernel', record_status);
107 this.events.on('kernel_connected.Kernel', record_status);
107 this.events.on('kernel_connected.Kernel', record_status);
108 this.events.on('kernel_starting.Kernel', record_status);
108 this.events.on('kernel_starting.Kernel', record_status);
109 this.events.on('kernel_restarting.Kernel', record_status);
109 this.events.on('kernel_restarting.Kernel', record_status);
110 this.events.on('kernel_autorestarting.Kernel', record_status);
110 this.events.on('kernel_autorestarting.Kernel', record_status);
111 this.events.on('kernel_interrupting.Kernel', record_status);
111 this.events.on('kernel_interrupting.Kernel', record_status);
112 this.events.on('kernel_disconnected.Kernel', record_status);
112 this.events.on('kernel_disconnected.Kernel', record_status);
113 // these are commented out because they are triggered a lot, but can
113 // these are commented out because they are triggered a lot, but can
114 // be uncommented for debugging purposes
114 // be uncommented for debugging purposes
115 //this.events.on('kernel_idle.Kernel', record_status);
115 //this.events.on('kernel_idle.Kernel', record_status);
116 //this.events.on('kernel_busy.Kernel', record_status);
116 //this.events.on('kernel_busy.Kernel', record_status);
117 this.events.on('kernel_ready.Kernel', record_status);
117 this.events.on('kernel_ready.Kernel', record_status);
118 this.events.on('kernel_killed.Kernel', record_status);
118 this.events.on('kernel_killed.Kernel', record_status);
119 this.events.on('kernel_dead.Kernel', record_status);
119 this.events.on('kernel_dead.Kernel', record_status);
120
120
121 this.events.on('kernel_ready.Kernel', function () {
121 this.events.on('kernel_ready.Kernel', function () {
122 that._autorestart_attempt = 0;
122 that._autorestart_attempt = 0;
123 });
123 });
124 this.events.on('kernel_connected.Kernel', function () {
124 this.events.on('kernel_connected.Kernel', function () {
125 that._reconnect_attempt = 0;
125 that._reconnect_attempt = 0;
126 });
126 });
127 };
127 };
128
128
129 /**
129 /**
130 * Initialize the iopub handlers.
130 * Initialize the iopub handlers.
131 *
131 *
132 * @function init_iopub_handlers
132 * @function init_iopub_handlers
133 */
133 */
134 Kernel.prototype.init_iopub_handlers = function () {
134 Kernel.prototype.init_iopub_handlers = function () {
135 var output_msg_types = ['stream', 'display_data', 'execute_result', 'error'];
135 var output_msg_types = ['stream', 'display_data', 'execute_result', 'error'];
136 this._iopub_handlers = {};
136 this._iopub_handlers = {};
137 this.register_iopub_handler('status', $.proxy(this._handle_status_message, this));
137 this.register_iopub_handler('status', $.proxy(this._handle_status_message, this));
138 this.register_iopub_handler('clear_output', $.proxy(this._handle_clear_output, this));
138 this.register_iopub_handler('clear_output', $.proxy(this._handle_clear_output, this));
139
139
140 for (var i=0; i < output_msg_types.length; i++) {
140 for (var i=0; i < output_msg_types.length; i++) {
141 this.register_iopub_handler(output_msg_types[i], $.proxy(this._handle_output_message, this));
141 this.register_iopub_handler(output_msg_types[i], $.proxy(this._handle_output_message, this));
142 }
142 }
143 };
143 };
144
144
145 /**
145 /**
146 * GET /api/kernels
146 * GET /api/kernels
147 *
147 *
148 * Get the list of running kernels.
148 * Get the list of running kernels.
149 *
149 *
150 * @function list
150 * @function list
151 * @param {function} [success] - function executed on ajax success
151 * @param {function} [success] - function executed on ajax success
152 * @param {function} [error] - functon executed on ajax error
152 * @param {function} [error] - functon executed on ajax error
153 */
153 */
154 Kernel.prototype.list = function (success, error) {
154 Kernel.prototype.list = function (success, error) {
155 $.ajax(this.kernel_service_url, {
155 $.ajax(this.kernel_service_url, {
156 processData: false,
156 processData: false,
157 cache: false,
157 cache: false,
158 type: "GET",
158 type: "GET",
159 dataType: "json",
159 dataType: "json",
160 success: success,
160 success: success,
161 error: this._on_error(error)
161 error: this._on_error(error)
162 });
162 });
163 };
163 };
164
164
165 /**
165 /**
166 * POST /api/kernels
166 * POST /api/kernels
167 *
167 *
168 * Start a new kernel.
168 * Start a new kernel.
169 *
169 *
170 * In general this shouldn't be used -- the kernel should be
170 * In general this shouldn't be used -- the kernel should be
171 * started through the session API. If you use this function and
171 * started through the session API. If you use this function and
172 * are also using the session API then your session and kernel
172 * are also using the session API then your session and kernel
173 * WILL be out of sync!
173 * WILL be out of sync!
174 *
174 *
175 * @function start
175 * @function start
176 * @param {params} [Object] - parameters to include in the query string
176 * @param {params} [Object] - parameters to include in the query string
177 * @param {function} [success] - function executed on ajax success
177 * @param {function} [success] - function executed on ajax success
178 * @param {function} [error] - functon executed on ajax error
178 * @param {function} [error] - functon executed on ajax error
179 */
179 */
180 Kernel.prototype.start = function (params, success, error) {
180 Kernel.prototype.start = function (params, success, error) {
181 var url = this.kernel_service_url;
181 var url = this.kernel_service_url;
182 var qs = $.param(params || {}); // query string for sage math stuff
182 var qs = $.param(params || {}); // query string for sage math stuff
183 if (qs !== "") {
183 if (qs !== "") {
184 url = url + "?" + qs;
184 url = url + "?" + qs;
185 }
185 }
186
186
187 var that = this;
187 var that = this;
188 var on_success = function (data, status, xhr) {
188 var on_success = function (data, status, xhr) {
189 that.events.trigger('kernel_created.Kernel', {kernel: that});
189 that.events.trigger('kernel_created.Kernel', {kernel: that});
190 that._kernel_created(data);
190 that._kernel_created(data);
191 if (success) {
191 if (success) {
192 success(data, status, xhr);
192 success(data, status, xhr);
193 }
193 }
194 };
194 };
195
195
196 $.ajax(url, {
196 $.ajax(url, {
197 processData: false,
197 processData: false,
198 cache: false,
198 cache: false,
199 type: "POST",
199 type: "POST",
200 data: JSON.stringify({name: this.name}),
200 data: JSON.stringify({name: this.name}),
201 dataType: "json",
201 dataType: "json",
202 success: this._on_success(on_success),
202 success: this._on_success(on_success),
203 error: this._on_error(error)
203 error: this._on_error(error)
204 });
204 });
205
205
206 return url;
206 return url;
207 };
207 };
208
208
209 /**
209 /**
210 * GET /api/kernels/[:kernel_id]
210 * GET /api/kernels/[:kernel_id]
211 *
211 *
212 * Get information about the kernel.
212 * Get information about the kernel.
213 *
213 *
214 * @function get_info
214 * @function get_info
215 * @param {function} [success] - function executed on ajax success
215 * @param {function} [success] - function executed on ajax success
216 * @param {function} [error] - functon executed on ajax error
216 * @param {function} [error] - functon executed on ajax error
217 */
217 */
218 Kernel.prototype.get_info = function (success, error) {
218 Kernel.prototype.get_info = function (success, error) {
219 $.ajax(this.kernel_url, {
219 $.ajax(this.kernel_url, {
220 processData: false,
220 processData: false,
221 cache: false,
221 cache: false,
222 type: "GET",
222 type: "GET",
223 dataType: "json",
223 dataType: "json",
224 success: this._on_success(success),
224 success: this._on_success(success),
225 error: this._on_error(error)
225 error: this._on_error(error)
226 });
226 });
227 };
227 };
228
228
229 /**
229 /**
230 * DELETE /api/kernels/[:kernel_id]
230 * DELETE /api/kernels/[:kernel_id]
231 *
231 *
232 * Shutdown the kernel.
232 * Shutdown the kernel.
233 *
233 *
234 * If you are also using sessions, then this function shoul NOT be
234 * If you are also using sessions, then this function shoul NOT be
235 * used. Instead, use Session.delete. Otherwise, the session and
235 * used. Instead, use Session.delete. Otherwise, the session and
236 * kernel WILL be out of sync.
236 * kernel WILL be out of sync.
237 *
237 *
238 * @function kill
238 * @function kill
239 * @param {function} [success] - function executed on ajax success
239 * @param {function} [success] - function executed on ajax success
240 * @param {function} [error] - functon executed on ajax error
240 * @param {function} [error] - functon executed on ajax error
241 */
241 */
242 Kernel.prototype.kill = function (success, error) {
242 Kernel.prototype.kill = function (success, error) {
243 this.events.trigger('kernel_killed.Kernel', {kernel: this});
243 this.events.trigger('kernel_killed.Kernel', {kernel: this});
244 this._kernel_dead();
244 this._kernel_dead();
245 $.ajax(this.kernel_url, {
245 $.ajax(this.kernel_url, {
246 processData: false,
246 processData: false,
247 cache: false,
247 cache: false,
248 type: "DELETE",
248 type: "DELETE",
249 dataType: "json",
249 dataType: "json",
250 success: this._on_success(success),
250 success: this._on_success(success),
251 error: this._on_error(error)
251 error: this._on_error(error)
252 });
252 });
253 };
253 };
254
254
255 /**
255 /**
256 * POST /api/kernels/[:kernel_id]/interrupt
256 * POST /api/kernels/[:kernel_id]/interrupt
257 *
257 *
258 * Interrupt the kernel.
258 * Interrupt the kernel.
259 *
259 *
260 * @function interrupt
260 * @function interrupt
261 * @param {function} [success] - function executed on ajax success
261 * @param {function} [success] - function executed on ajax success
262 * @param {function} [error] - functon executed on ajax error
262 * @param {function} [error] - functon executed on ajax error
263 */
263 */
264 Kernel.prototype.interrupt = function (success, error) {
264 Kernel.prototype.interrupt = function (success, error) {
265 this.events.trigger('kernel_interrupting.Kernel', {kernel: this});
265 this.events.trigger('kernel_interrupting.Kernel', {kernel: this});
266
266
267 var that = this;
267 var that = this;
268 var on_success = function (data, status, xhr) {
268 var on_success = function (data, status, xhr) {
269 // get kernel info so we know what state the kernel is in
269 /**
270 * get kernel info so we know what state the kernel is in
271 */
270 that.kernel_info();
272 that.kernel_info();
271 if (success) {
273 if (success) {
272 success(data, status, xhr);
274 success(data, status, xhr);
273 }
275 }
274 };
276 };
275
277
276 var url = utils.url_join_encode(this.kernel_url, 'interrupt');
278 var url = utils.url_join_encode(this.kernel_url, 'interrupt');
277 $.ajax(url, {
279 $.ajax(url, {
278 processData: false,
280 processData: false,
279 cache: false,
281 cache: false,
280 type: "POST",
282 type: "POST",
281 dataType: "json",
283 dataType: "json",
282 success: this._on_success(on_success),
284 success: this._on_success(on_success),
283 error: this._on_error(error)
285 error: this._on_error(error)
284 });
286 });
285 };
287 };
286
288
287 Kernel.prototype.restart = function (success, error) {
289 Kernel.prototype.restart = function (success, error) {
288 /**
290 /**
289 * POST /api/kernels/[:kernel_id]/restart
291 * POST /api/kernels/[:kernel_id]/restart
290 *
292 *
291 * Restart the kernel.
293 * Restart the kernel.
292 *
294 *
293 * @function interrupt
295 * @function interrupt
294 * @param {function} [success] - function executed on ajax success
296 * @param {function} [success] - function executed on ajax success
295 * @param {function} [error] - functon executed on ajax error
297 * @param {function} [error] - functon executed on ajax error
296 */
298 */
297 this.events.trigger('kernel_restarting.Kernel', {kernel: this});
299 this.events.trigger('kernel_restarting.Kernel', {kernel: this});
298 this.stop_channels();
300 this.stop_channels();
299
301
300 var that = this;
302 var that = this;
301 var on_success = function (data, status, xhr) {
303 var on_success = function (data, status, xhr) {
302 that.events.trigger('kernel_created.Kernel', {kernel: that});
304 that.events.trigger('kernel_created.Kernel', {kernel: that});
303 that._kernel_created(data);
305 that._kernel_created(data);
304 if (success) {
306 if (success) {
305 success(data, status, xhr);
307 success(data, status, xhr);
306 }
308 }
307 };
309 };
308
310
309 var on_error = function (xhr, status, err) {
311 var on_error = function (xhr, status, err) {
310 that.events.trigger('kernel_dead.Kernel', {kernel: that});
312 that.events.trigger('kernel_dead.Kernel', {kernel: that});
311 that._kernel_dead();
313 that._kernel_dead();
312 if (error) {
314 if (error) {
313 error(xhr, status, err);
315 error(xhr, status, err);
314 }
316 }
315 };
317 };
316
318
317 var url = utils.url_join_encode(this.kernel_url, 'restart');
319 var url = utils.url_join_encode(this.kernel_url, 'restart');
318 $.ajax(url, {
320 $.ajax(url, {
319 processData: false,
321 processData: false,
320 cache: false,
322 cache: false,
321 type: "POST",
323 type: "POST",
322 dataType: "json",
324 dataType: "json",
323 success: this._on_success(on_success),
325 success: this._on_success(on_success),
324 error: this._on_error(on_error)
326 error: this._on_error(on_error)
325 });
327 });
326 };
328 };
327
329
328 Kernel.prototype.reconnect = function () {
330 Kernel.prototype.reconnect = function () {
329 /**
331 /**
330 * Reconnect to a disconnected kernel. This is not actually a
332 * Reconnect to a disconnected kernel. This is not actually a
331 * standard HTTP request, but useful function nonetheless for
333 * standard HTTP request, but useful function nonetheless for
332 * reconnecting to the kernel if the connection is somehow lost.
334 * reconnecting to the kernel if the connection is somehow lost.
333 *
335 *
334 * @function reconnect
336 * @function reconnect
335 */
337 */
336 if (this.is_connected()) {
338 if (this.is_connected()) {
337 return;
339 return;
338 }
340 }
339 this._reconnect_attempt = this._reconnect_attempt + 1;
341 this._reconnect_attempt = this._reconnect_attempt + 1;
340 this.events.trigger('kernel_reconnecting.Kernel', {
342 this.events.trigger('kernel_reconnecting.Kernel', {
341 kernel: this,
343 kernel: this,
342 attempt: this._reconnect_attempt,
344 attempt: this._reconnect_attempt,
343 });
345 });
344 this.start_channels();
346 this.start_channels();
345 };
347 };
346
348
347 Kernel.prototype._on_success = function (success) {
349 Kernel.prototype._on_success = function (success) {
348 /**
350 /**
349 * Handle a successful AJAX request by updating the kernel id and
351 * Handle a successful AJAX request by updating the kernel id and
350 * name from the response, and then optionally calling a provided
352 * name from the response, and then optionally calling a provided
351 * callback.
353 * callback.
352 *
354 *
353 * @function _on_success
355 * @function _on_success
354 * @param {function} success - callback
356 * @param {function} success - callback
355 */
357 */
356 var that = this;
358 var that = this;
357 return function (data, status, xhr) {
359 return function (data, status, xhr) {
358 if (data) {
360 if (data) {
359 that.id = data.id;
361 that.id = data.id;
360 that.name = data.name;
362 that.name = data.name;
361 }
363 }
362 that.kernel_url = utils.url_join_encode(that.kernel_service_url, that.id);
364 that.kernel_url = utils.url_join_encode(that.kernel_service_url, that.id);
363 if (success) {
365 if (success) {
364 success(data, status, xhr);
366 success(data, status, xhr);
365 }
367 }
366 };
368 };
367 };
369 };
368
370
369 Kernel.prototype._on_error = function (error) {
371 Kernel.prototype._on_error = function (error) {
370 /**
372 /**
371 * Handle a failed AJAX request by logging the error message, and
373 * Handle a failed AJAX request by logging the error message, and
372 * then optionally calling a provided callback.
374 * then optionally calling a provided callback.
373 *
375 *
374 * @function _on_error
376 * @function _on_error
375 * @param {function} error - callback
377 * @param {function} error - callback
376 */
378 */
377 return function (xhr, status, err) {
379 return function (xhr, status, err) {
378 utils.log_ajax_error(xhr, status, err);
380 utils.log_ajax_error(xhr, status, err);
379 if (error) {
381 if (error) {
380 error(xhr, status, err);
382 error(xhr, status, err);
381 }
383 }
382 };
384 };
383 };
385 };
384
386
385 Kernel.prototype._kernel_created = function (data) {
387 Kernel.prototype._kernel_created = function (data) {
386 /**
388 /**
387 * Perform necessary tasks once the kernel has been started,
389 * Perform necessary tasks once the kernel has been started,
388 * including actually connecting to the kernel.
390 * including actually connecting to the kernel.
389 *
391 *
390 * @function _kernel_created
392 * @function _kernel_created
391 * @param {Object} data - information about the kernel including id
393 * @param {Object} data - information about the kernel including id
392 */
394 */
393 this.id = data.id;
395 this.id = data.id;
394 this.kernel_url = utils.url_join_encode(this.kernel_service_url, this.id);
396 this.kernel_url = utils.url_join_encode(this.kernel_service_url, this.id);
395 this.start_channels();
397 this.start_channels();
396 };
398 };
397
399
398 Kernel.prototype._kernel_connected = function () {
400 Kernel.prototype._kernel_connected = function () {
399 /**
401 /**
400 * Perform necessary tasks once the connection to the kernel has
402 * Perform necessary tasks once the connection to the kernel has
401 * been established. This includes requesting information about
403 * been established. This includes requesting information about
402 * the kernel.
404 * the kernel.
403 *
405 *
404 * @function _kernel_connected
406 * @function _kernel_connected
405 */
407 */
406 this.events.trigger('kernel_connected.Kernel', {kernel: this});
408 this.events.trigger('kernel_connected.Kernel', {kernel: this});
407 this.events.trigger('kernel_starting.Kernel', {kernel: this});
409 this.events.trigger('kernel_starting.Kernel', {kernel: this});
408 // get kernel info so we know what state the kernel is in
410 // get kernel info so we know what state the kernel is in
409 var that = this;
411 var that = this;
410 this.kernel_info(function (reply) {
412 this.kernel_info(function (reply) {
411 that.info_reply = reply.content;
413 that.info_reply = reply.content;
412 that.events.trigger('kernel_ready.Kernel', {kernel: that});
414 that.events.trigger('kernel_ready.Kernel', {kernel: that});
413 });
415 });
414 };
416 };
415
417
416 Kernel.prototype._kernel_dead = function () {
418 Kernel.prototype._kernel_dead = function () {
417 /**
419 /**
418 * Perform necessary tasks after the kernel has died. This closing
420 * Perform necessary tasks after the kernel has died. This closing
419 * communication channels to the kernel if they are still somehow
421 * communication channels to the kernel if they are still somehow
420 * open.
422 * open.
421 *
423 *
422 * @function _kernel_dead
424 * @function _kernel_dead
423 */
425 */
424 this.stop_channels();
426 this.stop_channels();
425 };
427 };
426
428
427 Kernel.prototype.start_channels = function () {
429 Kernel.prototype.start_channels = function () {
428 /**
430 /**
429 * Start the `shell`and `iopub` channels.
431 * Start the `shell`and `iopub` channels.
430 * Will stop and restart them if they already exist.
432 * Will stop and restart them if they already exist.
431 *
433 *
432 * @function start_channels
434 * @function start_channels
433 */
435 */
434 var that = this;
436 var that = this;
435 this.stop_channels();
437 this.stop_channels();
436 var ws_host_url = this.ws_url + this.kernel_url;
438 var ws_host_url = this.ws_url + this.kernel_url;
437
439
438 console.log("Starting WebSockets:", ws_host_url);
440 console.log("Starting WebSockets:", ws_host_url);
439
441
440 var channel_url = function(channel) {
442 var channel_url = function(channel) {
441 return [
443 return [
442 that.ws_url,
444 that.ws_url,
443 utils.url_join_encode(that.kernel_url, channel),
445 utils.url_join_encode(that.kernel_url, channel),
444 "?session_id=" + that.session_id
446 "?session_id=" + that.session_id
445 ].join('');
447 ].join('');
446 };
448 };
447 this.channels.shell = new this.WebSocket(channel_url("shell"));
449 this.channels.shell = new this.WebSocket(channel_url("shell"));
448 this.channels.stdin = new this.WebSocket(channel_url("stdin"));
450 this.channels.stdin = new this.WebSocket(channel_url("stdin"));
449 this.channels.iopub = new this.WebSocket(channel_url("iopub"));
451 this.channels.iopub = new this.WebSocket(channel_url("iopub"));
450
452
451 var already_called_onclose = false; // only alert once
453 var already_called_onclose = false; // only alert once
452 var ws_closed_early = function(evt){
454 var ws_closed_early = function(evt){
453 if (already_called_onclose){
455 if (already_called_onclose){
454 return;
456 return;
455 }
457 }
456 already_called_onclose = true;
458 already_called_onclose = true;
457 if ( ! evt.wasClean ){
459 if ( ! evt.wasClean ){
458 // If the websocket was closed early, that could mean
460 // If the websocket was closed early, that could mean
459 // that the kernel is actually dead. Try getting
461 // that the kernel is actually dead. Try getting
460 // information about the kernel from the API call --
462 // information about the kernel from the API call --
461 // if that fails, then assume the kernel is dead,
463 // if that fails, then assume the kernel is dead,
462 // otherwise just follow the typical websocket closed
464 // otherwise just follow the typical websocket closed
463 // protocol.
465 // protocol.
464 that.get_info(function () {
466 that.get_info(function () {
465 that._ws_closed(ws_host_url, false);
467 that._ws_closed(ws_host_url, false);
466 }, function () {
468 }, function () {
467 that.events.trigger('kernel_dead.Kernel', {kernel: that});
469 that.events.trigger('kernel_dead.Kernel', {kernel: that});
468 that._kernel_dead();
470 that._kernel_dead();
469 });
471 });
470 }
472 }
471 };
473 };
472 var ws_closed_late = function(evt){
474 var ws_closed_late = function(evt){
473 if (already_called_onclose){
475 if (already_called_onclose){
474 return;
476 return;
475 }
477 }
476 already_called_onclose = true;
478 already_called_onclose = true;
477 if ( ! evt.wasClean ){
479 if ( ! evt.wasClean ){
478 that._ws_closed(ws_host_url, false);
480 that._ws_closed(ws_host_url, false);
479 }
481 }
480 };
482 };
481 var ws_error = function(evt){
483 var ws_error = function(evt){
482 if (already_called_onclose){
484 if (already_called_onclose){
483 return;
485 return;
484 }
486 }
485 already_called_onclose = true;
487 already_called_onclose = true;
486 that._ws_closed(ws_host_url, true);
488 that._ws_closed(ws_host_url, true);
487 };
489 };
488
490
489 for (var c in this.channels) {
491 for (var c in this.channels) {
490 this.channels[c].onopen = $.proxy(this._ws_opened, this);
492 this.channels[c].onopen = $.proxy(this._ws_opened, this);
491 this.channels[c].onclose = ws_closed_early;
493 this.channels[c].onclose = ws_closed_early;
492 this.channels[c].onerror = ws_error;
494 this.channels[c].onerror = ws_error;
493 }
495 }
494 // switch from early-close to late-close message after 1s
496 // switch from early-close to late-close message after 1s
495 setTimeout(function() {
497 setTimeout(function() {
496 for (var c in that.channels) {
498 for (var c in that.channels) {
497 if (that.channels[c] !== null) {
499 if (that.channels[c] !== null) {
498 that.channels[c].onclose = ws_closed_late;
500 that.channels[c].onclose = ws_closed_late;
499 }
501 }
500 }
502 }
501 }, 1000);
503 }, 1000);
502 this.channels.shell.onmessage = $.proxy(this._handle_shell_reply, this);
504 this.channels.shell.onmessage = $.proxy(this._handle_shell_reply, this);
503 this.channels.iopub.onmessage = $.proxy(this._handle_iopub_message, this);
505 this.channels.iopub.onmessage = $.proxy(this._handle_iopub_message, this);
504 this.channels.stdin.onmessage = $.proxy(this._handle_input_request, this);
506 this.channels.stdin.onmessage = $.proxy(this._handle_input_request, this);
505 };
507 };
506
508
507 Kernel.prototype._ws_opened = function (evt) {
509 Kernel.prototype._ws_opened = function (evt) {
508 /**
510 /**
509 * Handle a websocket entering the open state,
511 * Handle a websocket entering the open state,
510 * signaling that the kernel is connected when all channels are open.
512 * signaling that the kernel is connected when all channels are open.
511 *
513 *
512 * @function _ws_opened
514 * @function _ws_opened
513 */
515 */
514 if (this.is_connected()) {
516 if (this.is_connected()) {
515 // all events ready, trigger started event.
517 // all events ready, trigger started event.
516 this._kernel_connected();
518 this._kernel_connected();
517 }
519 }
518 };
520 };
519
521
520 Kernel.prototype._ws_closed = function(ws_url, error) {
522 Kernel.prototype._ws_closed = function(ws_url, error) {
521 /**
523 /**
522 * Handle a websocket entering the closed state. This closes the
524 * Handle a websocket entering the closed state. This closes the
523 * other communication channels if they are open. If the websocket
525 * other communication channels if they are open. If the websocket
524 * was not closed due to an error, try to reconnect to the kernel.
526 * was not closed due to an error, try to reconnect to the kernel.
525 *
527 *
526 * @function _ws_closed
528 * @function _ws_closed
527 * @param {string} ws_url - the websocket url
529 * @param {string} ws_url - the websocket url
528 * @param {bool} error - whether the connection was closed due to an error
530 * @param {bool} error - whether the connection was closed due to an error
529 */
531 */
530 this.stop_channels();
532 this.stop_channels();
531
533
532 this.events.trigger('kernel_disconnected.Kernel', {kernel: this});
534 this.events.trigger('kernel_disconnected.Kernel', {kernel: this});
533 if (error) {
535 if (error) {
534 console.log('WebSocket connection failed: ', ws_url);
536 console.log('WebSocket connection failed: ', ws_url);
535 this.events.trigger('kernel_connection_failed.Kernel', {kernel: this, ws_url: ws_url, attempt: this._reconnect_attempt});
537 this.events.trigger('kernel_connection_failed.Kernel', {kernel: this, ws_url: ws_url, attempt: this._reconnect_attempt});
536 }
538 }
537 this._schedule_reconnect();
539 this._schedule_reconnect();
538 };
540 };
539
541
540 Kernel.prototype._schedule_reconnect = function () {
542 Kernel.prototype._schedule_reconnect = function () {
541 // function to call when kernel connection is lost
543 /**
542 // schedules reconnect, or fires 'connection_dead' if reconnect limit is hit
544 * function to call when kernel connection is lost
545 * schedules reconnect, or fires 'connection_dead' if reconnect limit is hit
546 */
543 if (this._reconnect_attempt < this.reconnect_limit) {
547 if (this._reconnect_attempt < this.reconnect_limit) {
544 var timeout = Math.pow(2, this._reconnect_attempt);
548 var timeout = Math.pow(2, this._reconnect_attempt);
545 console.log("Connection lost, reconnecting in " + timeout + " seconds.");
549 console.log("Connection lost, reconnecting in " + timeout + " seconds.");
546 setTimeout($.proxy(this.reconnect, this), 1e3 * timeout);
550 setTimeout($.proxy(this.reconnect, this), 1e3 * timeout);
547 } else {
551 } else {
548 this.events.trigger('kernel_connection_dead.Kernel', {
552 this.events.trigger('kernel_connection_dead.Kernel', {
549 kernel: this,
553 kernel: this,
550 reconnect_attempt: this._reconnect_attempt,
554 reconnect_attempt: this._reconnect_attempt,
551 });
555 });
552 console.log("Failed to reconnect, giving up.");
556 console.log("Failed to reconnect, giving up.");
553 }
557 }
554 };
558 };
555
559
556 Kernel.prototype.stop_channels = function () {
560 Kernel.prototype.stop_channels = function () {
557 /**
561 /**
558 * Close the websocket channels. After successful close, the value
562 * Close the websocket channels. After successful close, the value
559 * in `this.channels[channel_name]` will be null.
563 * in `this.channels[channel_name]` will be null.
560 *
564 *
561 * @function stop_channels
565 * @function stop_channels
562 */
566 */
563 var that = this;
567 var that = this;
564 var close = function (c) {
568 var close = function (c) {
565 return function () {
569 return function () {
566 if (that.channels[c] && that.channels[c].readyState === WebSocket.CLOSED) {
570 if (that.channels[c] && that.channels[c].readyState === WebSocket.CLOSED) {
567 that.channels[c] = null;
571 that.channels[c] = null;
568 }
572 }
569 };
573 };
570 };
574 };
571 for (var c in this.channels) {
575 for (var c in this.channels) {
572 if ( this.channels[c] !== null ) {
576 if ( this.channels[c] !== null ) {
573 if (this.channels[c].readyState === WebSocket.OPEN) {
577 if (this.channels[c].readyState === WebSocket.OPEN) {
574 this.channels[c].onclose = close(c);
578 this.channels[c].onclose = close(c);
575 this.channels[c].close();
579 this.channels[c].close();
576 } else {
580 } else {
577 close(c)();
581 close(c)();
578 }
582 }
579 }
583 }
580 }
584 }
581 };
585 };
582
586
583 Kernel.prototype.is_connected = function () {
587 Kernel.prototype.is_connected = function () {
584 /**
588 /**
585 * Check whether there is a connection to the kernel. This
589 * Check whether there is a connection to the kernel. This
586 * function only returns true if all channel objects have been
590 * function only returns true if all channel objects have been
587 * created and have a state of WebSocket.OPEN.
591 * created and have a state of WebSocket.OPEN.
588 *
592 *
589 * @function is_connected
593 * @function is_connected
590 * @returns {bool} - whether there is a connection
594 * @returns {bool} - whether there is a connection
591 */
595 */
592 for (var c in this.channels) {
596 for (var c in this.channels) {
593 // if any channel is not ready, then we're not connected
597 // if any channel is not ready, then we're not connected
594 if (this.channels[c] === null) {
598 if (this.channels[c] === null) {
595 return false;
599 return false;
596 }
600 }
597 if (this.channels[c].readyState !== WebSocket.OPEN) {
601 if (this.channels[c].readyState !== WebSocket.OPEN) {
598 return false;
602 return false;
599 }
603 }
600 }
604 }
601 return true;
605 return true;
602 };
606 };
603
607
604 Kernel.prototype.is_fully_disconnected = function () {
608 Kernel.prototype.is_fully_disconnected = function () {
605 /**
609 /**
606 * Check whether the connection to the kernel has been completely
610 * Check whether the connection to the kernel has been completely
607 * severed. This function only returns true if all channel objects
611 * severed. This function only returns true if all channel objects
608 * are null.
612 * are null.
609 *
613 *
610 * @function is_fully_disconnected
614 * @function is_fully_disconnected
611 * @returns {bool} - whether the kernel is fully disconnected
615 * @returns {bool} - whether the kernel is fully disconnected
612 */
616 */
613 for (var c in this.channels) {
617 for (var c in this.channels) {
614 if (this.channels[c] === null) {
618 if (this.channels[c] === null) {
615 return true;
619 return true;
616 }
620 }
617 }
621 }
618 return false;
622 return false;
619 };
623 };
620
624
621 Kernel.prototype.send_shell_message = function (msg_type, content, callbacks, metadata, buffers) {
625 Kernel.prototype.send_shell_message = function (msg_type, content, callbacks, metadata, buffers) {
622 /**
626 /**
623 * Send a message on the Kernel's shell channel
627 * Send a message on the Kernel's shell channel
624 *
628 *
625 * @function send_shell_message
629 * @function send_shell_message
626 */
630 */
627 if (!this.is_connected()) {
631 if (!this.is_connected()) {
628 throw new Error("kernel is not connected");
632 throw new Error("kernel is not connected");
629 }
633 }
630 var msg = this._get_msg(msg_type, content, metadata, buffers);
634 var msg = this._get_msg(msg_type, content, metadata, buffers);
631 this.channels.shell.send(serialize.serialize(msg));
635 this.channels.shell.send(serialize.serialize(msg));
632 this.set_callbacks_for_msg(msg.header.msg_id, callbacks);
636 this.set_callbacks_for_msg(msg.header.msg_id, callbacks);
633 return msg.header.msg_id;
637 return msg.header.msg_id;
634 };
638 };
635
639
636 Kernel.prototype.kernel_info = function (callback) {
640 Kernel.prototype.kernel_info = function (callback) {
637 /**
641 /**
638 * Get kernel info
642 * Get kernel info
639 *
643 *
640 * @function kernel_info
644 * @function kernel_info
641 * @param callback {function}
645 * @param callback {function}
642 *
646 *
643 * When calling this method, pass a callback function that expects one argument.
647 * When calling this method, pass a callback function that expects one argument.
644 * The callback will be passed the complete `kernel_info_reply` message documented
648 * The callback will be passed the complete `kernel_info_reply` message documented
645 * [here](http://ipython.org/ipython-doc/dev/development/messaging.html#kernel-info)
649 * [here](http://ipython.org/ipython-doc/dev/development/messaging.html#kernel-info)
646 */
650 */
647 var callbacks;
651 var callbacks;
648 if (callback) {
652 if (callback) {
649 callbacks = { shell : { reply : callback } };
653 callbacks = { shell : { reply : callback } };
650 }
654 }
651 return this.send_shell_message("kernel_info_request", {}, callbacks);
655 return this.send_shell_message("kernel_info_request", {}, callbacks);
652 };
656 };
653
657
654 Kernel.prototype.inspect = function (code, cursor_pos, callback) {
658 Kernel.prototype.inspect = function (code, cursor_pos, callback) {
655 /**
659 /**
656 * Get info on an object
660 * Get info on an object
657 *
661 *
658 * When calling this method, pass a callback function that expects one argument.
662 * When calling this method, pass a callback function that expects one argument.
659 * The callback will be passed the complete `inspect_reply` message documented
663 * The callback will be passed the complete `inspect_reply` message documented
660 * [here](http://ipython.org/ipython-doc/dev/development/messaging.html#object-information)
664 * [here](http://ipython.org/ipython-doc/dev/development/messaging.html#object-information)
661 *
665 *
662 * @function inspect
666 * @function inspect
663 * @param code {string}
667 * @param code {string}
664 * @param cursor_pos {integer}
668 * @param cursor_pos {integer}
665 * @param callback {function}
669 * @param callback {function}
666 */
670 */
667 var callbacks;
671 var callbacks;
668 if (callback) {
672 if (callback) {
669 callbacks = { shell : { reply : callback } };
673 callbacks = { shell : { reply : callback } };
670 }
674 }
671
675
672 var content = {
676 var content = {
673 code : code,
677 code : code,
674 cursor_pos : cursor_pos,
678 cursor_pos : cursor_pos,
675 detail_level : 0
679 detail_level : 0
676 };
680 };
677 return this.send_shell_message("inspect_request", content, callbacks);
681 return this.send_shell_message("inspect_request", content, callbacks);
678 };
682 };
679
683
680 Kernel.prototype.execute = function (code, callbacks, options) {
684 Kernel.prototype.execute = function (code, callbacks, options) {
681 /**
685 /**
682 * Execute given code into kernel, and pass result to callback.
686 * Execute given code into kernel, and pass result to callback.
683 *
687 *
684 * @async
688 * @async
685 * @function execute
689 * @function execute
686 * @param {string} code
690 * @param {string} code
687 * @param [callbacks] {Object} With the following keys (all optional)
691 * @param [callbacks] {Object} With the following keys (all optional)
688 * @param callbacks.shell.reply {function}
692 * @param callbacks.shell.reply {function}
689 * @param callbacks.shell.payload.[payload_name] {function}
693 * @param callbacks.shell.payload.[payload_name] {function}
690 * @param callbacks.iopub.output {function}
694 * @param callbacks.iopub.output {function}
691 * @param callbacks.iopub.clear_output {function}
695 * @param callbacks.iopub.clear_output {function}
692 * @param callbacks.input {function}
696 * @param callbacks.input {function}
693 * @param {object} [options]
697 * @param {object} [options]
694 * @param [options.silent=false] {Boolean}
698 * @param [options.silent=false] {Boolean}
695 * @param [options.user_expressions=empty_dict] {Dict}
699 * @param [options.user_expressions=empty_dict] {Dict}
696 * @param [options.allow_stdin=false] {Boolean} true|false
700 * @param [options.allow_stdin=false] {Boolean} true|false
697 *
701 *
698 * @example
702 * @example
699 *
703 *
700 * The options object should contain the options for the execute
704 * The options object should contain the options for the execute
701 * call. Its default values are:
705 * call. Its default values are:
702 *
706 *
703 * options = {
707 * options = {
704 * silent : true,
708 * silent : true,
705 * user_expressions : {},
709 * user_expressions : {},
706 * allow_stdin : false
710 * allow_stdin : false
707 * }
711 * }
708 *
712 *
709 * When calling this method pass a callbacks structure of the
713 * When calling this method pass a callbacks structure of the
710 * form:
714 * form:
711 *
715 *
712 * callbacks = {
716 * callbacks = {
713 * shell : {
717 * shell : {
714 * reply : execute_reply_callback,
718 * reply : execute_reply_callback,
715 * payload : {
719 * payload : {
716 * set_next_input : set_next_input_callback,
720 * set_next_input : set_next_input_callback,
717 * }
721 * }
718 * },
722 * },
719 * iopub : {
723 * iopub : {
720 * output : output_callback,
724 * output : output_callback,
721 * clear_output : clear_output_callback,
725 * clear_output : clear_output_callback,
722 * },
726 * },
723 * input : raw_input_callback
727 * input : raw_input_callback
724 * }
728 * }
725 *
729 *
726 * Each callback will be passed the entire message as a single
730 * Each callback will be passed the entire message as a single
727 * arugment. Payload handlers will be passed the corresponding
731 * arugment. Payload handlers will be passed the corresponding
728 * payload and the execute_reply message.
732 * payload and the execute_reply message.
729 */
733 */
730 var content = {
734 var content = {
731 code : code,
735 code : code,
732 silent : true,
736 silent : true,
733 store_history : false,
737 store_history : false,
734 user_expressions : {},
738 user_expressions : {},
735 allow_stdin : false
739 allow_stdin : false
736 };
740 };
737 callbacks = callbacks || {};
741 callbacks = callbacks || {};
738 if (callbacks.input !== undefined) {
742 if (callbacks.input !== undefined) {
739 content.allow_stdin = true;
743 content.allow_stdin = true;
740 }
744 }
741 $.extend(true, content, options);
745 $.extend(true, content, options);
742 this.events.trigger('execution_request.Kernel', {kernel: this, content: content});
746 this.events.trigger('execution_request.Kernel', {kernel: this, content: content});
743 return this.send_shell_message("execute_request", content, callbacks);
747 return this.send_shell_message("execute_request", content, callbacks);
744 };
748 };
745
749
746 /**
750 /**
747 * When calling this method, pass a function to be called with the
751 * When calling this method, pass a function to be called with the
748 * `complete_reply` message as its only argument when it arrives.
752 * `complete_reply` message as its only argument when it arrives.
749 *
753 *
750 * `complete_reply` is documented
754 * `complete_reply` is documented
751 * [here](http://ipython.org/ipython-doc/dev/development/messaging.html#complete)
755 * [here](http://ipython.org/ipython-doc/dev/development/messaging.html#complete)
752 *
756 *
753 * @function complete
757 * @function complete
754 * @param code {string}
758 * @param code {string}
755 * @param cursor_pos {integer}
759 * @param cursor_pos {integer}
756 * @param callback {function}
760 * @param callback {function}
757 */
761 */
758 Kernel.prototype.complete = function (code, cursor_pos, callback) {
762 Kernel.prototype.complete = function (code, cursor_pos, callback) {
759 var callbacks;
763 var callbacks;
760 if (callback) {
764 if (callback) {
761 callbacks = { shell : { reply : callback } };
765 callbacks = { shell : { reply : callback } };
762 }
766 }
763 var content = {
767 var content = {
764 code : code,
768 code : code,
765 cursor_pos : cursor_pos
769 cursor_pos : cursor_pos
766 };
770 };
767 return this.send_shell_message("complete_request", content, callbacks);
771 return this.send_shell_message("complete_request", content, callbacks);
768 };
772 };
769
773
770 /**
774 /**
771 * @function send_input_reply
775 * @function send_input_reply
772 */
776 */
773 Kernel.prototype.send_input_reply = function (input) {
777 Kernel.prototype.send_input_reply = function (input) {
774 if (!this.is_connected()) {
778 if (!this.is_connected()) {
775 throw new Error("kernel is not connected");
779 throw new Error("kernel is not connected");
776 }
780 }
777 var content = {
781 var content = {
778 value : input
782 value : input
779 };
783 };
780 this.events.trigger('input_reply.Kernel', {kernel: this, content: content});
784 this.events.trigger('input_reply.Kernel', {kernel: this, content: content});
781 var msg = this._get_msg("input_reply", content);
785 var msg = this._get_msg("input_reply", content);
782 this.channels.stdin.send(serialize.serialize(msg));
786 this.channels.stdin.send(serialize.serialize(msg));
783 return msg.header.msg_id;
787 return msg.header.msg_id;
784 };
788 };
785
789
786 /**
790 /**
787 * @function register_iopub_handler
791 * @function register_iopub_handler
788 */
792 */
789 Kernel.prototype.register_iopub_handler = function (msg_type, callback) {
793 Kernel.prototype.register_iopub_handler = function (msg_type, callback) {
790 this._iopub_handlers[msg_type] = callback;
794 this._iopub_handlers[msg_type] = callback;
791 };
795 };
792
796
793 /**
797 /**
794 * Get the iopub handler for a specific message type.
798 * Get the iopub handler for a specific message type.
795 *
799 *
796 * @function get_iopub_handler
800 * @function get_iopub_handler
797 */
801 */
798 Kernel.prototype.get_iopub_handler = function (msg_type) {
802 Kernel.prototype.get_iopub_handler = function (msg_type) {
799 return this._iopub_handlers[msg_type];
803 return this._iopub_handlers[msg_type];
800 };
804 };
801
805
802 /**
806 /**
803 * Get callbacks for a specific message.
807 * Get callbacks for a specific message.
804 *
808 *
805 * @function get_callbacks_for_msg
809 * @function get_callbacks_for_msg
806 */
810 */
807 Kernel.prototype.get_callbacks_for_msg = function (msg_id) {
811 Kernel.prototype.get_callbacks_for_msg = function (msg_id) {
808 if (msg_id == this.last_msg_id) {
812 if (msg_id == this.last_msg_id) {
809 return this.last_msg_callbacks;
813 return this.last_msg_callbacks;
810 } else {
814 } else {
811 return this._msg_callbacks[msg_id];
815 return this._msg_callbacks[msg_id];
812 }
816 }
813 };
817 };
814
818
815 /**
819 /**
816 * Clear callbacks for a specific message.
820 * Clear callbacks for a specific message.
817 *
821 *
818 * @function clear_callbacks_for_msg
822 * @function clear_callbacks_for_msg
819 */
823 */
820 Kernel.prototype.clear_callbacks_for_msg = function (msg_id) {
824 Kernel.prototype.clear_callbacks_for_msg = function (msg_id) {
821 if (this._msg_callbacks[msg_id] !== undefined ) {
825 if (this._msg_callbacks[msg_id] !== undefined ) {
822 delete this._msg_callbacks[msg_id];
826 delete this._msg_callbacks[msg_id];
823 }
827 }
824 };
828 };
825
829
826 /**
830 /**
827 * @function _finish_shell
831 * @function _finish_shell
828 */
832 */
829 Kernel.prototype._finish_shell = function (msg_id) {
833 Kernel.prototype._finish_shell = function (msg_id) {
830 var callbacks = this._msg_callbacks[msg_id];
834 var callbacks = this._msg_callbacks[msg_id];
831 if (callbacks !== undefined) {
835 if (callbacks !== undefined) {
832 callbacks.shell_done = true;
836 callbacks.shell_done = true;
833 if (callbacks.iopub_done) {
837 if (callbacks.iopub_done) {
834 this.clear_callbacks_for_msg(msg_id);
838 this.clear_callbacks_for_msg(msg_id);
835 }
839 }
836 }
840 }
837 };
841 };
838
842
839 /**
843 /**
840 * @function _finish_iopub
844 * @function _finish_iopub
841 */
845 */
842 Kernel.prototype._finish_iopub = function (msg_id) {
846 Kernel.prototype._finish_iopub = function (msg_id) {
843 var callbacks = this._msg_callbacks[msg_id];
847 var callbacks = this._msg_callbacks[msg_id];
844 if (callbacks !== undefined) {
848 if (callbacks !== undefined) {
845 callbacks.iopub_done = true;
849 callbacks.iopub_done = true;
846 if (callbacks.shell_done) {
850 if (callbacks.shell_done) {
847 this.clear_callbacks_for_msg(msg_id);
851 this.clear_callbacks_for_msg(msg_id);
848 }
852 }
849 }
853 }
850 };
854 };
851
855
852 /**
856 /**
853 * Set callbacks for a particular message.
857 * Set callbacks for a particular message.
854 * Callbacks should be a struct of the following form:
858 * Callbacks should be a struct of the following form:
855 * shell : {
859 * shell : {
856 *
860 *
857 * }
861 * }
858 *
862 *
859 * @function set_callbacks_for_msg
863 * @function set_callbacks_for_msg
860 */
864 */
861 Kernel.prototype.set_callbacks_for_msg = function (msg_id, callbacks) {
865 Kernel.prototype.set_callbacks_for_msg = function (msg_id, callbacks) {
862 this.last_msg_id = msg_id;
866 this.last_msg_id = msg_id;
863 if (callbacks) {
867 if (callbacks) {
864 // shallow-copy mapping, because we will modify it at the top level
868 // shallow-copy mapping, because we will modify it at the top level
865 var cbcopy = this._msg_callbacks[msg_id] = this.last_msg_callbacks = {};
869 var cbcopy = this._msg_callbacks[msg_id] = this.last_msg_callbacks = {};
866 cbcopy.shell = callbacks.shell;
870 cbcopy.shell = callbacks.shell;
867 cbcopy.iopub = callbacks.iopub;
871 cbcopy.iopub = callbacks.iopub;
868 cbcopy.input = callbacks.input;
872 cbcopy.input = callbacks.input;
869 cbcopy.shell_done = (!callbacks.shell);
873 cbcopy.shell_done = (!callbacks.shell);
870 cbcopy.iopub_done = (!callbacks.iopub);
874 cbcopy.iopub_done = (!callbacks.iopub);
871 } else {
875 } else {
872 this.last_msg_callbacks = {};
876 this.last_msg_callbacks = {};
873 }
877 }
874 };
878 };
875
879
876 /**
880 /**
877 * @function _handle_shell_reply
881 * @function _handle_shell_reply
878 */
882 */
879 Kernel.prototype._handle_shell_reply = function (e) {
883 Kernel.prototype._handle_shell_reply = function (e) {
880 serialize.deserialize(e.data, $.proxy(this._finish_shell_reply, this));
884 serialize.deserialize(e.data, $.proxy(this._finish_shell_reply, this));
881 };
885 };
882
886
883 Kernel.prototype._finish_shell_reply = function (reply) {
887 Kernel.prototype._finish_shell_reply = function (reply) {
884 this.events.trigger('shell_reply.Kernel', {kernel: this, reply:reply});
888 this.events.trigger('shell_reply.Kernel', {kernel: this, reply:reply});
885 var content = reply.content;
889 var content = reply.content;
886 var metadata = reply.metadata;
890 var metadata = reply.metadata;
887 var parent_id = reply.parent_header.msg_id;
891 var parent_id = reply.parent_header.msg_id;
888 var callbacks = this.get_callbacks_for_msg(parent_id);
892 var callbacks = this.get_callbacks_for_msg(parent_id);
889 if (!callbacks || !callbacks.shell) {
893 if (!callbacks || !callbacks.shell) {
890 return;
894 return;
891 }
895 }
892 var shell_callbacks = callbacks.shell;
896 var shell_callbacks = callbacks.shell;
893
897
894 // signal that shell callbacks are done
898 // signal that shell callbacks are done
895 this._finish_shell(parent_id);
899 this._finish_shell(parent_id);
896
900
897 if (shell_callbacks.reply !== undefined) {
901 if (shell_callbacks.reply !== undefined) {
898 shell_callbacks.reply(reply);
902 shell_callbacks.reply(reply);
899 }
903 }
900 if (content.payload && shell_callbacks.payload) {
904 if (content.payload && shell_callbacks.payload) {
901 this._handle_payloads(content.payload, shell_callbacks.payload, reply);
905 this._handle_payloads(content.payload, shell_callbacks.payload, reply);
902 }
906 }
903 };
907 };
904
908
905 /**
909 /**
906 * @function _handle_payloads
910 * @function _handle_payloads
907 */
911 */
908 Kernel.prototype._handle_payloads = function (payloads, payload_callbacks, msg) {
912 Kernel.prototype._handle_payloads = function (payloads, payload_callbacks, msg) {
909 var l = payloads.length;
913 var l = payloads.length;
910 // Payloads are handled by triggering events because we don't want the Kernel
914 // Payloads are handled by triggering events because we don't want the Kernel
911 // to depend on the Notebook or Pager classes.
915 // to depend on the Notebook or Pager classes.
912 for (var i=0; i<l; i++) {
916 for (var i=0; i<l; i++) {
913 var payload = payloads[i];
917 var payload = payloads[i];
914 var callback = payload_callbacks[payload.source];
918 var callback = payload_callbacks[payload.source];
915 if (callback) {
919 if (callback) {
916 callback(payload, msg);
920 callback(payload, msg);
917 }
921 }
918 }
922 }
919 };
923 };
920
924
921 /**
925 /**
922 * @function _handle_status_message
926 * @function _handle_status_message
923 */
927 */
924 Kernel.prototype._handle_status_message = function (msg) {
928 Kernel.prototype._handle_status_message = function (msg) {
925 var execution_state = msg.content.execution_state;
929 var execution_state = msg.content.execution_state;
926 var parent_id = msg.parent_header.msg_id;
930 var parent_id = msg.parent_header.msg_id;
927
931
928 // dispatch status msg callbacks, if any
932 // dispatch status msg callbacks, if any
929 var callbacks = this.get_callbacks_for_msg(parent_id);
933 var callbacks = this.get_callbacks_for_msg(parent_id);
930 if (callbacks && callbacks.iopub && callbacks.iopub.status) {
934 if (callbacks && callbacks.iopub && callbacks.iopub.status) {
931 try {
935 try {
932 callbacks.iopub.status(msg);
936 callbacks.iopub.status(msg);
933 } catch (e) {
937 } catch (e) {
934 console.log("Exception in status msg handler", e, e.stack);
938 console.log("Exception in status msg handler", e, e.stack);
935 }
939 }
936 }
940 }
937
941
938 if (execution_state === 'busy') {
942 if (execution_state === 'busy') {
939 this.events.trigger('kernel_busy.Kernel', {kernel: this});
943 this.events.trigger('kernel_busy.Kernel', {kernel: this});
940
944
941 } else if (execution_state === 'idle') {
945 } else if (execution_state === 'idle') {
942 // signal that iopub callbacks are (probably) done
946 // signal that iopub callbacks are (probably) done
943 // async output may still arrive,
947 // async output may still arrive,
944 // but only for the most recent request
948 // but only for the most recent request
945 this._finish_iopub(parent_id);
949 this._finish_iopub(parent_id);
946
950
947 // trigger status_idle event
951 // trigger status_idle event
948 this.events.trigger('kernel_idle.Kernel', {kernel: this});
952 this.events.trigger('kernel_idle.Kernel', {kernel: this});
949
953
950 } else if (execution_state === 'starting') {
954 } else if (execution_state === 'starting') {
951 this.events.trigger('kernel_starting.Kernel', {kernel: this});
955 this.events.trigger('kernel_starting.Kernel', {kernel: this});
952 var that = this;
956 var that = this;
953 this.kernel_info(function (reply) {
957 this.kernel_info(function (reply) {
954 that.info_reply = reply.content;
958 that.info_reply = reply.content;
955 that.events.trigger('kernel_ready.Kernel', {kernel: that});
959 that.events.trigger('kernel_ready.Kernel', {kernel: that});
956 });
960 });
957
961
958 } else if (execution_state === 'restarting') {
962 } else if (execution_state === 'restarting') {
959 // autorestarting is distinct from restarting,
963 // autorestarting is distinct from restarting,
960 // in that it means the kernel died and the server is restarting it.
964 // in that it means the kernel died and the server is restarting it.
961 // kernel_restarting sets the notification widget,
965 // kernel_restarting sets the notification widget,
962 // autorestart shows the more prominent dialog.
966 // autorestart shows the more prominent dialog.
963 this._autorestart_attempt = this._autorestart_attempt + 1;
967 this._autorestart_attempt = this._autorestart_attempt + 1;
964 this.events.trigger('kernel_restarting.Kernel', {kernel: this});
968 this.events.trigger('kernel_restarting.Kernel', {kernel: this});
965 this.events.trigger('kernel_autorestarting.Kernel', {kernel: this, attempt: this._autorestart_attempt});
969 this.events.trigger('kernel_autorestarting.Kernel', {kernel: this, attempt: this._autorestart_attempt});
966
970
967 } else if (execution_state === 'dead') {
971 } else if (execution_state === 'dead') {
968 this.events.trigger('kernel_dead.Kernel', {kernel: this});
972 this.events.trigger('kernel_dead.Kernel', {kernel: this});
969 this._kernel_dead();
973 this._kernel_dead();
970 }
974 }
971 };
975 };
972
976
973 /**
977 /**
974 * Handle clear_output message
978 * Handle clear_output message
975 *
979 *
976 * @function _handle_clear_output
980 * @function _handle_clear_output
977 */
981 */
978 Kernel.prototype._handle_clear_output = function (msg) {
982 Kernel.prototype._handle_clear_output = function (msg) {
979 var callbacks = this.get_callbacks_for_msg(msg.parent_header.msg_id);
983 var callbacks = this.get_callbacks_for_msg(msg.parent_header.msg_id);
980 if (!callbacks || !callbacks.iopub) {
984 if (!callbacks || !callbacks.iopub) {
981 return;
985 return;
982 }
986 }
983 var callback = callbacks.iopub.clear_output;
987 var callback = callbacks.iopub.clear_output;
984 if (callback) {
988 if (callback) {
985 callback(msg);
989 callback(msg);
986 }
990 }
987 };
991 };
988
992
989 /**
993 /**
990 * handle an output message (execute_result, display_data, etc.)
994 * handle an output message (execute_result, display_data, etc.)
991 *
995 *
992 * @function _handle_output_message
996 * @function _handle_output_message
993 */
997 */
994 Kernel.prototype._handle_output_message = function (msg) {
998 Kernel.prototype._handle_output_message = function (msg) {
995 var callbacks = this.get_callbacks_for_msg(msg.parent_header.msg_id);
999 var callbacks = this.get_callbacks_for_msg(msg.parent_header.msg_id);
996 if (!callbacks || !callbacks.iopub) {
1000 if (!callbacks || !callbacks.iopub) {
997 return;
1001 return;
998 }
1002 }
999 var callback = callbacks.iopub.output;
1003 var callback = callbacks.iopub.output;
1000 if (callback) {
1004 if (callback) {
1001 callback(msg);
1005 callback(msg);
1002 }
1006 }
1003 };
1007 };
1004
1008
1005 /**
1009 /**
1006 * Dispatch IOPub messages to respective handlers. Each message
1010 * Dispatch IOPub messages to respective handlers. Each message
1007 * type should have a handler.
1011 * type should have a handler.
1008 *
1012 *
1009 * @function _handle_iopub_message
1013 * @function _handle_iopub_message
1010 */
1014 */
1011 Kernel.prototype._handle_iopub_message = function (e) {
1015 Kernel.prototype._handle_iopub_message = function (e) {
1012 serialize.deserialize(e.data, $.proxy(this._finish_iopub_message, this));
1016 serialize.deserialize(e.data, $.proxy(this._finish_iopub_message, this));
1013 };
1017 };
1014
1018
1015
1019
1016 Kernel.prototype._finish_iopub_message = function (msg) {
1020 Kernel.prototype._finish_iopub_message = function (msg) {
1017 var handler = this.get_iopub_handler(msg.header.msg_type);
1021 var handler = this.get_iopub_handler(msg.header.msg_type);
1018 if (handler !== undefined) {
1022 if (handler !== undefined) {
1019 handler(msg);
1023 handler(msg);
1020 }
1024 }
1021 };
1025 };
1022
1026
1023 /**
1027 /**
1024 * @function _handle_input_request
1028 * @function _handle_input_request
1025 */
1029 */
1026 Kernel.prototype._handle_input_request = function (e) {
1030 Kernel.prototype._handle_input_request = function (e) {
1027 serialize.deserialize(e.data, $.proxy(this._finish_input_request, this));
1031 serialize.deserialize(e.data, $.proxy(this._finish_input_request, this));
1028 };
1032 };
1029
1033
1030
1034
1031 Kernel.prototype._finish_input_request = function (request) {
1035 Kernel.prototype._finish_input_request = function (request) {
1032 var header = request.header;
1036 var header = request.header;
1033 var content = request.content;
1037 var content = request.content;
1034 var metadata = request.metadata;
1038 var metadata = request.metadata;
1035 var msg_type = header.msg_type;
1039 var msg_type = header.msg_type;
1036 if (msg_type !== 'input_request') {
1040 if (msg_type !== 'input_request') {
1037 console.log("Invalid input request!", request);
1041 console.log("Invalid input request!", request);
1038 return;
1042 return;
1039 }
1043 }
1040 var callbacks = this.get_callbacks_for_msg(request.parent_header.msg_id);
1044 var callbacks = this.get_callbacks_for_msg(request.parent_header.msg_id);
1041 if (callbacks) {
1045 if (callbacks) {
1042 if (callbacks.input) {
1046 if (callbacks.input) {
1043 callbacks.input(request);
1047 callbacks.input(request);
1044 }
1048 }
1045 }
1049 }
1046 };
1050 };
1047
1051
1048 // Backwards compatability.
1052 // Backwards compatability.
1049 IPython.Kernel = Kernel;
1053 IPython.Kernel = Kernel;
1050
1054
1051 return {'Kernel': Kernel};
1055 return {'Kernel': Kernel};
1052 });
1056 });
@@ -1,114 +1,120 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 'underscore',
5 'underscore',
6 ], function (_) {
6 ], function (_) {
7 "use strict";
7 "use strict";
8
8
9 var _deserialize_array_buffer = function (buf) {
9 var _deserialize_array_buffer = function (buf) {
10 var data = new DataView(buf);
10 var data = new DataView(buf);
11 // read the header: 1 + nbufs 32b integers
11 // read the header: 1 + nbufs 32b integers
12 var nbufs = data.getUint32(0);
12 var nbufs = data.getUint32(0);
13 var offsets = [];
13 var offsets = [];
14 var i;
14 var i;
15 for (i = 1; i <= nbufs; i++) {
15 for (i = 1; i <= nbufs; i++) {
16 offsets.push(data.getUint32(i * 4));
16 offsets.push(data.getUint32(i * 4));
17 }
17 }
18 var json_bytes = new Uint8Array(buf.slice(offsets[0], offsets[1]));
18 var json_bytes = new Uint8Array(buf.slice(offsets[0], offsets[1]));
19 var msg = JSON.parse(
19 var msg = JSON.parse(
20 (new TextDecoder('utf8')).decode(json_bytes)
20 (new TextDecoder('utf8')).decode(json_bytes)
21 );
21 );
22 // the remaining chunks are stored as DataViews in msg.buffers
22 // the remaining chunks are stored as DataViews in msg.buffers
23 msg.buffers = [];
23 msg.buffers = [];
24 var start, stop;
24 var start, stop;
25 for (i = 1; i < nbufs; i++) {
25 for (i = 1; i < nbufs; i++) {
26 start = offsets[i];
26 start = offsets[i];
27 stop = offsets[i+1] || buf.byteLength;
27 stop = offsets[i+1] || buf.byteLength;
28 msg.buffers.push(new DataView(buf.slice(start, stop)));
28 msg.buffers.push(new DataView(buf.slice(start, stop)));
29 }
29 }
30 return msg;
30 return msg;
31 };
31 };
32
32
33 var _deserialize_binary = function(data, callback) {
33 var _deserialize_binary = function(data, callback) {
34 // deserialize the binary message format
34 /**
35 // callback will be called with a message whose buffers attribute
35 * deserialize the binary message format
36 // will be an array of DataViews.
36 * callback will be called with a message whose buffers attribute
37 * will be an array of DataViews.
38 */
37 if (data instanceof Blob) {
39 if (data instanceof Blob) {
38 // data is Blob, have to deserialize from ArrayBuffer in reader callback
40 // data is Blob, have to deserialize from ArrayBuffer in reader callback
39 var reader = new FileReader();
41 var reader = new FileReader();
40 reader.onload = function () {
42 reader.onload = function () {
41 var msg = _deserialize_array_buffer(this.result);
43 var msg = _deserialize_array_buffer(this.result);
42 callback(msg);
44 callback(msg);
43 };
45 };
44 reader.readAsArrayBuffer(data);
46 reader.readAsArrayBuffer(data);
45 } else {
47 } else {
46 // data is ArrayBuffer, can deserialize directly
48 // data is ArrayBuffer, can deserialize directly
47 var msg = _deserialize_array_buffer(data);
49 var msg = _deserialize_array_buffer(data);
48 callback(msg);
50 callback(msg);
49 }
51 }
50 };
52 };
51
53
52 var deserialize = function (data, callback) {
54 var deserialize = function (data, callback) {
53 // deserialize a message and pass the unpacked message object to callback
55 /**
56 * deserialize a message and pass the unpacked message object to callback
57 */
54 if (typeof data === "string") {
58 if (typeof data === "string") {
55 // text JSON message
59 // text JSON message
56 callback(JSON.parse(data));
60 callback(JSON.parse(data));
57 } else {
61 } else {
58 // binary message
62 // binary message
59 _deserialize_binary(data, callback);
63 _deserialize_binary(data, callback);
60 }
64 }
61 };
65 };
62
66
63 var _serialize_binary = function (msg) {
67 var _serialize_binary = function (msg) {
64 // implement the binary serialization protocol
68 /**
65 // serializes JSON message to ArrayBuffer
69 * implement the binary serialization protocol
70 * serializes JSON message to ArrayBuffer
71 */
66 msg = _.clone(msg);
72 msg = _.clone(msg);
67 var offsets = [];
73 var offsets = [];
68 var buffers = [];
74 var buffers = [];
69 msg.buffers.map(function (buf) {
75 msg.buffers.map(function (buf) {
70 buffers.push(buf);
76 buffers.push(buf);
71 });
77 });
72 delete msg.buffers;
78 delete msg.buffers;
73 var json_utf8 = (new TextEncoder('utf8')).encode(JSON.stringify(msg));
79 var json_utf8 = (new TextEncoder('utf8')).encode(JSON.stringify(msg));
74 buffers.unshift(json_utf8);
80 buffers.unshift(json_utf8);
75 var nbufs = buffers.length;
81 var nbufs = buffers.length;
76 offsets.push(4 * (nbufs + 1));
82 offsets.push(4 * (nbufs + 1));
77 var i;
83 var i;
78 for (i = 0; i + 1 < buffers.length; i++) {
84 for (i = 0; i + 1 < buffers.length; i++) {
79 offsets.push(offsets[offsets.length-1] + buffers[i].byteLength);
85 offsets.push(offsets[offsets.length-1] + buffers[i].byteLength);
80 }
86 }
81 var msg_buf = new Uint8Array(
87 var msg_buf = new Uint8Array(
82 offsets[offsets.length-1] + buffers[buffers.length-1].byteLength
88 offsets[offsets.length-1] + buffers[buffers.length-1].byteLength
83 );
89 );
84 // use DataView.setUint32 for network byte-order
90 // use DataView.setUint32 for network byte-order
85 var view = new DataView(msg_buf.buffer);
91 var view = new DataView(msg_buf.buffer);
86 // write nbufs to first 4 bytes
92 // write nbufs to first 4 bytes
87 view.setUint32(0, nbufs);
93 view.setUint32(0, nbufs);
88 // write offsets to next 4 * nbufs bytes
94 // write offsets to next 4 * nbufs bytes
89 for (i = 0; i < offsets.length; i++) {
95 for (i = 0; i < offsets.length; i++) {
90 view.setUint32(4 * (i+1), offsets[i]);
96 view.setUint32(4 * (i+1), offsets[i]);
91 }
97 }
92 // write all the buffers at their respective offsets
98 // write all the buffers at their respective offsets
93 for (i = 0; i < buffers.length; i++) {
99 for (i = 0; i < buffers.length; i++) {
94 msg_buf.set(new Uint8Array(buffers[i].buffer), offsets[i]);
100 msg_buf.set(new Uint8Array(buffers[i].buffer), offsets[i]);
95 }
101 }
96
102
97 // return raw ArrayBuffer
103 // return raw ArrayBuffer
98 return msg_buf.buffer;
104 return msg_buf.buffer;
99 };
105 };
100
106
101 var serialize = function (msg) {
107 var serialize = function (msg) {
102 if (msg.buffers && msg.buffers.length) {
108 if (msg.buffers && msg.buffers.length) {
103 return _serialize_binary(msg);
109 return _serialize_binary(msg);
104 } else {
110 } else {
105 return JSON.stringify(msg);
111 return JSON.stringify(msg);
106 }
112 }
107 };
113 };
108
114
109 var exports = {
115 var exports = {
110 deserialize : deserialize,
116 deserialize : deserialize,
111 serialize: serialize
117 serialize: serialize
112 };
118 };
113 return exports;
119 return exports;
114 }); No newline at end of file
120 });
@@ -1,55 +1,59 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) {
11 var KernelList = function (selector, options) {
12 // Constructor
12 /**
13 //
13 * Constructor
14 // Parameters:
14 *
15 // selector: string
15 * Parameters:
16 // options: dictionary
16 * selector: string
17 // Dictionary of keyword arguments.
17 * options: dictionary
18 // session_list: SessionList instance
18 * Dictionary of keyword arguments.
19 // base_url: string
19 * session_list: SessionList instance
20 // notebook_path: string
20 * base_url: string
21 * notebook_path: string
22 */
21 notebooklist.NotebookList.call(this, selector, $.extend({
23 notebooklist.NotebookList.call(this, selector, $.extend({
22 element_name: 'running'},
24 element_name: 'running'},
23 options));
25 options));
24 };
26 };
25
27
26 KernelList.prototype = Object.create(notebooklist.NotebookList.prototype);
28 KernelList.prototype = Object.create(notebooklist.NotebookList.prototype);
27
29
28 KernelList.prototype.add_duplicate_button = function () {
30 KernelList.prototype.add_duplicate_button = function () {
29 // do nothing
31 /**
32 * do nothing
33 */
30 };
34 };
31
35
32 KernelList.prototype.sessions_loaded = function (d) {
36 KernelList.prototype.sessions_loaded = function (d) {
33 this.sessions = d;
37 this.sessions = d;
34 this.clear_list();
38 this.clear_list();
35 var item, path;
39 var item, path;
36 for (path in d) {
40 for (path in d) {
37 if (!d.hasOwnProperty(path)) {
41 if (!d.hasOwnProperty(path)) {
38 // nothing is safe in javascript
42 // nothing is safe in javascript
39 continue;
43 continue;
40 }
44 }
41 item = this.new_item(-1);
45 item = this.new_item(-1);
42 this.add_link({
46 this.add_link({
43 name: path,
47 name: path,
44 path: path,
48 path: path,
45 type: 'notebook',
49 type: 'notebook',
46 }, item);
50 }, item);
47 }
51 }
48 $('#running_list_header').toggle($.isEmptyObject(d));
52 $('#running_list_header').toggle($.isEmptyObject(d));
49 };
53 };
50
54
51 // Backwards compatability.
55 // Backwards compatability.
52 IPython.KernelList = KernelList;
56 IPython.KernelList = KernelList;
53
57
54 return {'KernelList': KernelList};
58 return {'KernelList': KernelList};
55 });
59 });
@@ -1,152 +1,154 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 require([
4 require([
5 'jquery',
5 'jquery',
6 'base/js/namespace',
6 'base/js/namespace',
7 'base/js/dialog',
7 'base/js/dialog',
8 'base/js/events',
8 'base/js/events',
9 'base/js/page',
9 'base/js/page',
10 'base/js/utils',
10 'base/js/utils',
11 'contents',
11 'contents',
12 'tree/js/notebooklist',
12 'tree/js/notebooklist',
13 'tree/js/clusterlist',
13 'tree/js/clusterlist',
14 'tree/js/sessionlist',
14 'tree/js/sessionlist',
15 'tree/js/kernellist',
15 'tree/js/kernellist',
16 'tree/js/terminallist',
16 'tree/js/terminallist',
17 'auth/js/loginwidget',
17 'auth/js/loginwidget',
18 // only loaded, not used:
18 // only loaded, not used:
19 'jqueryui',
19 'jqueryui',
20 'bootstrap',
20 'bootstrap',
21 'custom/custom',
21 'custom/custom',
22 ], function(
22 ], function(
23 $,
23 $,
24 IPython,
24 IPython,
25 dialog,
25 dialog,
26 events,
26 events,
27 page,
27 page,
28 utils,
28 utils,
29 contents_service,
29 contents_service,
30 notebooklist,
30 notebooklist,
31 clusterlist,
31 clusterlist,
32 sesssionlist,
32 sesssionlist,
33 kernellist,
33 kernellist,
34 terminallist,
34 terminallist,
35 loginwidget){
35 loginwidget){
36 "use strict";
36 "use strict";
37
37
38 page = new page.Page();
38 page = new page.Page();
39
39
40 var common_options = {
40 var common_options = {
41 base_url: utils.get_body_data("baseUrl"),
41 base_url: utils.get_body_data("baseUrl"),
42 notebook_path: utils.get_body_data("notebookPath"),
42 notebook_path: utils.get_body_data("notebookPath"),
43 };
43 };
44 var session_list = new sesssionlist.SesssionList($.extend({
44 var session_list = new sesssionlist.SesssionList($.extend({
45 events: events},
45 events: events},
46 common_options));
46 common_options));
47 var contents = new contents_service.Contents($.extend({
47 var contents = new contents_service.Contents($.extend({
48 events: events},
48 events: events},
49 common_options));
49 common_options));
50 var notebook_list = new notebooklist.NotebookList('#notebook_list', $.extend({
50 var notebook_list = new notebooklist.NotebookList('#notebook_list', $.extend({
51 contents: contents,
51 contents: contents,
52 session_list: session_list},
52 session_list: session_list},
53 common_options));
53 common_options));
54 var cluster_list = new clusterlist.ClusterList('#cluster_list', common_options);
54 var cluster_list = new clusterlist.ClusterList('#cluster_list', common_options);
55 var kernel_list = new kernellist.KernelList('#running_list', $.extend({
55 var kernel_list = new kernellist.KernelList('#running_list', $.extend({
56 session_list: session_list},
56 session_list: session_list},
57 common_options));
57 common_options));
58
58
59 var terminal_list;
59 var terminal_list;
60 if (utils.get_body_data("terminalsAvailable") === "True") {
60 if (utils.get_body_data("terminalsAvailable") === "True") {
61 terminal_list = new terminallist.TerminalList('#terminal_list', common_options);
61 terminal_list = new terminallist.TerminalList('#terminal_list', common_options);
62 }
62 }
63
63
64 var login_widget = new loginwidget.LoginWidget('#login_widget', common_options);
64 var login_widget = new loginwidget.LoginWidget('#login_widget', common_options);
65
65
66 $('#new_notebook').click(function (e) {
66 $('#new_notebook').click(function (e) {
67 var w = window.open();
67 var w = window.open();
68 contents.new_untitled(common_options.notebook_path, {type: "notebook"}).then(
68 contents.new_untitled(common_options.notebook_path, {type: "notebook"}).then(
69 function (data) {
69 function (data) {
70 w.location = utils.url_join_encode(
70 w.location = utils.url_join_encode(
71 common_options.base_url, 'notebooks', data.path
71 common_options.base_url, 'notebooks', data.path
72 );
72 );
73 },
73 },
74 function(error) {
74 function(error) {
75 w.close();
75 w.close();
76 dialog.modal({
76 dialog.modal({
77 title : 'Creating Notebook Failed',
77 title : 'Creating Notebook Failed',
78 body : "The error was: " + error.message,
78 body : "The error was: " + error.message,
79 buttons : {'OK' : {'class' : 'btn-primary'}}
79 buttons : {'OK' : {'class' : 'btn-primary'}}
80 });
80 });
81 }
81 }
82 );
82 );
83 });
83 });
84
84
85 var interval_id=0;
85 var interval_id=0;
86 // auto refresh every xx secondes, no need to be fast,
86 // auto refresh every xx secondes, no need to be fast,
87 // update is done at least when page get focus
87 // update is done at least when page get focus
88 var time_refresh = 60; // in sec
88 var time_refresh = 60; // in sec
89
89
90 var enable_autorefresh = function(){
90 var enable_autorefresh = function(){
91 //refresh immediately , then start interval
91 /**
92 *refresh immediately , then start interval
93 */
92 session_list.load_sessions();
94 session_list.load_sessions();
93 cluster_list.load_list();
95 cluster_list.load_list();
94 if (terminal_list) {
96 if (terminal_list) {
95 terminal_list.load_terminals();
97 terminal_list.load_terminals();
96 }
98 }
97 if (!interval_id){
99 if (!interval_id){
98 interval_id = setInterval(function(){
100 interval_id = setInterval(function(){
99 session_list.load_sessions();
101 session_list.load_sessions();
100 cluster_list.load_list();
102 cluster_list.load_list();
101 if (terminal_list) {
103 if (terminal_list) {
102 terminal_list.load_terminals();
104 terminal_list.load_terminals();
103 }
105 }
104 }, time_refresh*1000);
106 }, time_refresh*1000);
105 }
107 }
106 };
108 };
107
109
108 var disable_autorefresh = function(){
110 var disable_autorefresh = function(){
109 clearInterval(interval_id);
111 clearInterval(interval_id);
110 interval_id = 0;
112 interval_id = 0;
111 };
113 };
112
114
113 // stop autorefresh when page lose focus
115 // stop autorefresh when page lose focus
114 $(window).blur(function() {
116 $(window).blur(function() {
115 disable_autorefresh();
117 disable_autorefresh();
116 });
118 });
117
119
118 //re-enable when page get focus back
120 //re-enable when page get focus back
119 $(window).focus(function() {
121 $(window).focus(function() {
120 enable_autorefresh();
122 enable_autorefresh();
121 });
123 });
122
124
123 // finally start it, it will refresh immediately
125 // finally start it, it will refresh immediately
124 enable_autorefresh();
126 enable_autorefresh();
125
127
126 page.show();
128 page.show();
127
129
128 // For backwards compatability.
130 // For backwards compatability.
129 IPython.page = page;
131 IPython.page = page;
130 IPython.notebook_list = notebook_list;
132 IPython.notebook_list = notebook_list;
131 IPython.cluster_list = cluster_list;
133 IPython.cluster_list = cluster_list;
132 IPython.session_list = session_list;
134 IPython.session_list = session_list;
133 IPython.kernel_list = kernel_list;
135 IPython.kernel_list = kernel_list;
134 IPython.login_widget = login_widget;
136 IPython.login_widget = login_widget;
135
137
136 events.trigger('app_initialized.DashboardApp');
138 events.trigger('app_initialized.DashboardApp');
137
139
138 // bound the upload method to the on change of the file select list
140 // bound the upload method to the on change of the file select list
139 $("#alternate_upload").change(function (event){
141 $("#alternate_upload").change(function (event){
140 notebook_list.handleFilesUpload(event,'form');
142 notebook_list.handleFilesUpload(event,'form');
141 });
143 });
142
144
143 // set hash on tab click
145 // set hash on tab click
144 $("#tabs").find("a").click(function() {
146 $("#tabs").find("a").click(function() {
145 window.location.hash = $(this).attr("href");
147 window.location.hash = $(this).attr("href");
146 });
148 });
147
149
148 // load tab if url hash
150 // load tab if url hash
149 if (window.location.hash) {
151 if (window.location.hash) {
150 $("#tabs").find("a[href=" + window.location.hash + "]").click();
152 $("#tabs").find("a[href=" + window.location.hash + "]").click();
151 }
153 }
152 });
154 });
@@ -1,502 +1,508 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/events',
9 'base/js/events',
10 ], function(IPython, $, utils, dialog, events) {
10 ], function(IPython, $, utils, dialog, events) {
11 "use strict";
11 "use strict";
12
12
13 var NotebookList = function (selector, options) {
13 var NotebookList = function (selector, options) {
14 // Constructor
14 /**
15 //
15 * Constructor
16 // Parameters:
16 *
17 // selector: string
17 * Parameters:
18 // options: dictionary
18 * selector: string
19 // Dictionary of keyword arguments.
19 * options: dictionary
20 // session_list: SessionList instance
20 * Dictionary of keyword arguments.
21 // element_name: string
21 * session_list: SessionList instance
22 // base_url: string
22 * element_name: string
23 // notebook_path: string
23 * base_url: string
24 // contents: Contents instance
24 * notebook_path: string
25 * contents: Contents instance
26 */
25 var that = this;
27 var that = this;
26 this.session_list = options.session_list;
28 this.session_list = options.session_list;
27 // allow code re-use by just changing element_name in kernellist.js
29 // allow code re-use by just changing element_name in kernellist.js
28 this.element_name = options.element_name || 'notebook';
30 this.element_name = options.element_name || 'notebook';
29 this.selector = selector;
31 this.selector = selector;
30 if (this.selector !== undefined) {
32 if (this.selector !== undefined) {
31 this.element = $(selector);
33 this.element = $(selector);
32 this.style();
34 this.style();
33 this.bind_events();
35 this.bind_events();
34 }
36 }
35 this.notebooks_list = [];
37 this.notebooks_list = [];
36 this.sessions = {};
38 this.sessions = {};
37 this.base_url = options.base_url || utils.get_body_data("baseUrl");
39 this.base_url = options.base_url || utils.get_body_data("baseUrl");
38 this.notebook_path = options.notebook_path || utils.get_body_data("notebookPath");
40 this.notebook_path = options.notebook_path || utils.get_body_data("notebookPath");
39 this.contents = options.contents;
41 this.contents = options.contents;
40 if (this.session_list && this.session_list.events) {
42 if (this.session_list && this.session_list.events) {
41 this.session_list.events.on('sessions_loaded.Dashboard',
43 this.session_list.events.on('sessions_loaded.Dashboard',
42 function(e, d) { that.sessions_loaded(d); });
44 function(e, d) { that.sessions_loaded(d); });
43 }
45 }
44 };
46 };
45
47
46 NotebookList.prototype.style = function () {
48 NotebookList.prototype.style = function () {
47 var prefix = '#' + this.element_name;
49 var prefix = '#' + this.element_name;
48 $(prefix + '_toolbar').addClass('list_toolbar');
50 $(prefix + '_toolbar').addClass('list_toolbar');
49 $(prefix + '_list_info').addClass('toolbar_info');
51 $(prefix + '_list_info').addClass('toolbar_info');
50 $(prefix + '_buttons').addClass('toolbar_buttons');
52 $(prefix + '_buttons').addClass('toolbar_buttons');
51 $(prefix + '_list_header').addClass('list_header');
53 $(prefix + '_list_header').addClass('list_header');
52 this.element.addClass("list_container");
54 this.element.addClass("list_container");
53 };
55 };
54
56
55
57
56 NotebookList.prototype.bind_events = function () {
58 NotebookList.prototype.bind_events = function () {
57 var that = this;
59 var that = this;
58 $('#refresh_' + this.element_name + '_list').click(function () {
60 $('#refresh_' + this.element_name + '_list').click(function () {
59 that.load_sessions();
61 that.load_sessions();
60 });
62 });
61 this.element.bind('dragover', function () {
63 this.element.bind('dragover', function () {
62 return false;
64 return false;
63 });
65 });
64 this.element.bind('drop', function(event){
66 this.element.bind('drop', function(event){
65 that.handleFilesUpload(event,'drop');
67 that.handleFilesUpload(event,'drop');
66 return false;
68 return false;
67 });
69 });
68 };
70 };
69
71
70 NotebookList.prototype.handleFilesUpload = function(event, dropOrForm) {
72 NotebookList.prototype.handleFilesUpload = function(event, dropOrForm) {
71 var that = this;
73 var that = this;
72 var files;
74 var files;
73 if(dropOrForm =='drop'){
75 if(dropOrForm =='drop'){
74 files = event.originalEvent.dataTransfer.files;
76 files = event.originalEvent.dataTransfer.files;
75 } else
77 } else
76 {
78 {
77 files = event.originalEvent.target.files;
79 files = event.originalEvent.target.files;
78 }
80 }
79 for (var i = 0; i < files.length; i++) {
81 for (var i = 0; i < files.length; i++) {
80 var f = files[i];
82 var f = files[i];
81 var name_and_ext = utils.splitext(f.name);
83 var name_and_ext = utils.splitext(f.name);
82 var file_ext = name_and_ext[1];
84 var file_ext = name_and_ext[1];
83
85
84 var reader = new FileReader();
86 var reader = new FileReader();
85 if (file_ext === '.ipynb') {
87 if (file_ext === '.ipynb') {
86 reader.readAsText(f);
88 reader.readAsText(f);
87 } else {
89 } else {
88 // read non-notebook files as binary
90 // read non-notebook files as binary
89 reader.readAsArrayBuffer(f);
91 reader.readAsArrayBuffer(f);
90 }
92 }
91 var item = that.new_item(0);
93 var item = that.new_item(0);
92 item.addClass('new-file');
94 item.addClass('new-file');
93 that.add_name_input(f.name, item, file_ext == '.ipynb' ? 'notebook' : 'file');
95 that.add_name_input(f.name, item, file_ext == '.ipynb' ? 'notebook' : 'file');
94 // Store the list item in the reader so we can use it later
96 // Store the list item in the reader so we can use it later
95 // to know which item it belongs to.
97 // to know which item it belongs to.
96 $(reader).data('item', item);
98 $(reader).data('item', item);
97 reader.onload = function (event) {
99 reader.onload = function (event) {
98 var item = $(event.target).data('item');
100 var item = $(event.target).data('item');
99 that.add_file_data(event.target.result, item);
101 that.add_file_data(event.target.result, item);
100 that.add_upload_button(item);
102 that.add_upload_button(item);
101 };
103 };
102 reader.onerror = function (event) {
104 reader.onerror = function (event) {
103 var item = $(event.target).data('item');
105 var item = $(event.target).data('item');
104 var name = item.data('name');
106 var name = item.data('name');
105 item.remove();
107 item.remove();
106 dialog.modal({
108 dialog.modal({
107 title : 'Failed to read file',
109 title : 'Failed to read file',
108 body : "Failed to read file '" + name + "'",
110 body : "Failed to read file '" + name + "'",
109 buttons : {'OK' : { 'class' : 'btn-primary' }}
111 buttons : {'OK' : { 'class' : 'btn-primary' }}
110 });
112 });
111 };
113 };
112 }
114 }
113 // Replace the file input form wth a clone of itself. This is required to
115 // Replace the file input form wth a clone of itself. This is required to
114 // reset the form. Otherwise, if you upload a file, delete it and try to
116 // reset the form. Otherwise, if you upload a file, delete it and try to
115 // upload it again, the changed event won't fire.
117 // upload it again, the changed event won't fire.
116 var form = $('input.fileinput');
118 var form = $('input.fileinput');
117 form.replaceWith(form.clone(true));
119 form.replaceWith(form.clone(true));
118 return false;
120 return false;
119 };
121 };
120
122
121 NotebookList.prototype.clear_list = function (remove_uploads) {
123 NotebookList.prototype.clear_list = function (remove_uploads) {
122 // Clears the navigation tree.
124 /**
123 //
125 * Clears the navigation tree.
124 // Parameters
126 *
125 // remove_uploads: bool=False
127 * Parameters
126 // Should upload prompts also be removed from the tree.
128 * remove_uploads: bool=False
129 * Should upload prompts also be removed from the tree.
130 */
127 if (remove_uploads) {
131 if (remove_uploads) {
128 this.element.children('.list_item').remove();
132 this.element.children('.list_item').remove();
129 } else {
133 } else {
130 this.element.children('.list_item:not(.new-file)').remove();
134 this.element.children('.list_item:not(.new-file)').remove();
131 }
135 }
132 };
136 };
133
137
134 NotebookList.prototype.load_sessions = function(){
138 NotebookList.prototype.load_sessions = function(){
135 this.session_list.load_sessions();
139 this.session_list.load_sessions();
136 };
140 };
137
141
138
142
139 NotebookList.prototype.sessions_loaded = function(data){
143 NotebookList.prototype.sessions_loaded = function(data){
140 this.sessions = data;
144 this.sessions = data;
141 this.load_list();
145 this.load_list();
142 };
146 };
143
147
144 NotebookList.prototype.load_list = function () {
148 NotebookList.prototype.load_list = function () {
145 var that = this;
149 var that = this;
146 this.contents.list_contents(that.notebook_path).then(
150 this.contents.list_contents(that.notebook_path).then(
147 $.proxy(this.draw_notebook_list, this),
151 $.proxy(this.draw_notebook_list, this),
148 function(error) {
152 function(error) {
149 that.draw_notebook_list({content: []}, "Server error: " + error.message);
153 that.draw_notebook_list({content: []}, "Server error: " + error.message);
150 }
154 }
151 );
155 );
152 };
156 };
153
157
154 /**
158 /**
155 * Draw the list of notebooks
159 * Draw the list of notebooks
156 * @method draw_notebook_list
160 * @method draw_notebook_list
157 * @param {Array} list An array of dictionaries representing files or
161 * @param {Array} list An array of dictionaries representing files or
158 * directories.
162 * directories.
159 * @param {String} error_msg An error message
163 * @param {String} error_msg An error message
160 */
164 */
161 NotebookList.prototype.draw_notebook_list = function (list, error_msg) {
165 NotebookList.prototype.draw_notebook_list = function (list, error_msg) {
162 var message = error_msg || 'Notebook list empty.';
166 var message = error_msg || 'Notebook list empty.';
163 var item = null;
167 var item = null;
164 var model = null;
168 var model = null;
165 var len = list.content.length;
169 var len = list.content.length;
166 this.clear_list();
170 this.clear_list();
167 var n_uploads = this.element.children('.list_item').length;
171 var n_uploads = this.element.children('.list_item').length;
168 if (len === 0) {
172 if (len === 0) {
169 item = this.new_item(0);
173 item = this.new_item(0);
170 var span12 = item.children().first();
174 var span12 = item.children().first();
171 span12.empty();
175 span12.empty();
172 span12.append($('<div style="margin:auto;text-align:center;color:grey"/>').text(message));
176 span12.append($('<div style="margin:auto;text-align:center;color:grey"/>').text(message));
173 }
177 }
174 var path = this.notebook_path;
178 var path = this.notebook_path;
175 var offset = n_uploads;
179 var offset = n_uploads;
176 if (path !== '') {
180 if (path !== '') {
177 item = this.new_item(offset);
181 item = this.new_item(offset);
178 model = {
182 model = {
179 type: 'directory',
183 type: 'directory',
180 name: '..',
184 name: '..',
181 path: utils.url_path_split(path)[0],
185 path: utils.url_path_split(path)[0],
182 };
186 };
183 this.add_link(model, item);
187 this.add_link(model, item);
184 offset += 1;
188 offset += 1;
185 }
189 }
186 for (var i=0; i<len; i++) {
190 for (var i=0; i<len; i++) {
187 model = list.content[i];
191 model = list.content[i];
188 item = this.new_item(i+offset);
192 item = this.new_item(i+offset);
189 this.add_link(model, item);
193 this.add_link(model, item);
190 }
194 }
191 // Trigger an event when we've finished drawing the notebook list.
195 // Trigger an event when we've finished drawing the notebook list.
192 events.trigger('draw_notebook_list.NotebookList');
196 events.trigger('draw_notebook_list.NotebookList');
193 };
197 };
194
198
195
199
196 NotebookList.prototype.new_item = function (index) {
200 NotebookList.prototype.new_item = function (index) {
197 var item = $('<div/>').addClass("list_item").addClass("row");
201 var item = $('<div/>').addClass("list_item").addClass("row");
198 // item.addClass('list_item ui-widget ui-widget-content ui-helper-clearfix');
202 // item.addClass('list_item ui-widget ui-widget-content ui-helper-clearfix');
199 // item.css('border-top-style','none');
203 // item.css('border-top-style','none');
200 item.append($("<div/>").addClass("col-md-12").append(
204 item.append($("<div/>").addClass("col-md-12").append(
201 $('<i/>').addClass('item_icon')
205 $('<i/>').addClass('item_icon')
202 ).append(
206 ).append(
203 $("<a/>").addClass("item_link").append(
207 $("<a/>").addClass("item_link").append(
204 $("<span/>").addClass("item_name")
208 $("<span/>").addClass("item_name")
205 )
209 )
206 ).append(
210 ).append(
207 $('<div/>').addClass("item_buttons btn-group pull-right")
211 $('<div/>').addClass("item_buttons btn-group pull-right")
208 ));
212 ));
209
213
210 if (index === -1) {
214 if (index === -1) {
211 this.element.append(item);
215 this.element.append(item);
212 } else {
216 } else {
213 this.element.children().eq(index).after(item);
217 this.element.children().eq(index).after(item);
214 }
218 }
215 return item;
219 return item;
216 };
220 };
217
221
218
222
219 NotebookList.icons = {
223 NotebookList.icons = {
220 directory: 'folder_icon',
224 directory: 'folder_icon',
221 notebook: 'notebook_icon',
225 notebook: 'notebook_icon',
222 file: 'file_icon',
226 file: 'file_icon',
223 };
227 };
224
228
225 NotebookList.uri_prefixes = {
229 NotebookList.uri_prefixes = {
226 directory: 'tree',
230 directory: 'tree',
227 notebook: 'notebooks',
231 notebook: 'notebooks',
228 file: 'files',
232 file: 'files',
229 };
233 };
230
234
231
235
232 NotebookList.prototype.add_link = function (model, item) {
236 NotebookList.prototype.add_link = function (model, item) {
233 var path = model.path,
237 var path = model.path,
234 name = model.name;
238 name = model.name;
235 item.data('name', name);
239 item.data('name', name);
236 item.data('path', path);
240 item.data('path', path);
237 item.find(".item_name").text(name);
241 item.find(".item_name").text(name);
238 var icon = NotebookList.icons[model.type];
242 var icon = NotebookList.icons[model.type];
239 var uri_prefix = NotebookList.uri_prefixes[model.type];
243 var uri_prefix = NotebookList.uri_prefixes[model.type];
240 item.find(".item_icon").addClass(icon).addClass('icon-fixed-width');
244 item.find(".item_icon").addClass(icon).addClass('icon-fixed-width');
241 var link = item.find("a.item_link")
245 var link = item.find("a.item_link")
242 .attr('href',
246 .attr('href',
243 utils.url_join_encode(
247 utils.url_join_encode(
244 this.base_url,
248 this.base_url,
245 uri_prefix,
249 uri_prefix,
246 path
250 path
247 )
251 )
248 );
252 );
249 // directory nav doesn't open new tabs
253 // directory nav doesn't open new tabs
250 // files, notebooks do
254 // files, notebooks do
251 if (model.type !== "directory") {
255 if (model.type !== "directory") {
252 link.attr('target','_blank');
256 link.attr('target','_blank');
253 }
257 }
254 if (model.type !== 'directory') {
258 if (model.type !== 'directory') {
255 this.add_duplicate_button(item);
259 this.add_duplicate_button(item);
256 }
260 }
257 if (model.type == 'file') {
261 if (model.type == 'file') {
258 this.add_delete_button(item);
262 this.add_delete_button(item);
259 } else if (model.type == 'notebook') {
263 } else if (model.type == 'notebook') {
260 if (this.sessions[path] === undefined){
264 if (this.sessions[path] === undefined){
261 this.add_delete_button(item);
265 this.add_delete_button(item);
262 } else {
266 } else {
263 this.add_shutdown_button(item, this.sessions[path]);
267 this.add_shutdown_button(item, this.sessions[path]);
264 }
268 }
265 }
269 }
266 };
270 };
267
271
268
272
269 NotebookList.prototype.add_name_input = function (name, item, icon_type) {
273 NotebookList.prototype.add_name_input = function (name, item, icon_type) {
270 item.data('name', name);
274 item.data('name', name);
271 item.find(".item_icon").addClass(NotebookList.icons[icon_type]).addClass('icon-fixed-width');
275 item.find(".item_icon").addClass(NotebookList.icons[icon_type]).addClass('icon-fixed-width');
272 item.find(".item_name").empty().append(
276 item.find(".item_name").empty().append(
273 $('<input/>')
277 $('<input/>')
274 .addClass("filename_input")
278 .addClass("filename_input")
275 .attr('value', name)
279 .attr('value', name)
276 .attr('size', '30')
280 .attr('size', '30')
277 .attr('type', 'text')
281 .attr('type', 'text')
278 .keyup(function(event){
282 .keyup(function(event){
279 if(event.keyCode == 13){item.find('.upload_button').click();}
283 if(event.keyCode == 13){item.find('.upload_button').click();}
280 else if(event.keyCode == 27){item.remove();}
284 else if(event.keyCode == 27){item.remove();}
281 })
285 })
282 );
286 );
283 };
287 };
284
288
285
289
286 NotebookList.prototype.add_file_data = function (data, item) {
290 NotebookList.prototype.add_file_data = function (data, item) {
287 item.data('filedata', data);
291 item.data('filedata', data);
288 };
292 };
289
293
290
294
291 NotebookList.prototype.add_shutdown_button = function (item, session) {
295 NotebookList.prototype.add_shutdown_button = function (item, session) {
292 var that = this;
296 var that = this;
293 var shutdown_button = $("<button/>").text("Shutdown").addClass("btn btn-xs btn-danger").
297 var shutdown_button = $("<button/>").text("Shutdown").addClass("btn btn-xs btn-danger").
294 click(function (e) {
298 click(function (e) {
295 var settings = {
299 var settings = {
296 processData : false,
300 processData : false,
297 cache : false,
301 cache : false,
298 type : "DELETE",
302 type : "DELETE",
299 dataType : "json",
303 dataType : "json",
300 success : function () {
304 success : function () {
301 that.load_sessions();
305 that.load_sessions();
302 },
306 },
303 error : utils.log_ajax_error,
307 error : utils.log_ajax_error,
304 };
308 };
305 var url = utils.url_join_encode(
309 var url = utils.url_join_encode(
306 that.base_url,
310 that.base_url,
307 'api/sessions',
311 'api/sessions',
308 session
312 session
309 );
313 );
310 $.ajax(url, settings);
314 $.ajax(url, settings);
311 return false;
315 return false;
312 });
316 });
313 item.find(".item_buttons").append(shutdown_button);
317 item.find(".item_buttons").append(shutdown_button);
314 };
318 };
315
319
316 NotebookList.prototype.add_duplicate_button = function (item) {
320 NotebookList.prototype.add_duplicate_button = function (item) {
317 var notebooklist = this;
321 var notebooklist = this;
318 var duplicate_button = $("<button/>").text("Duplicate").addClass("btn btn-default btn-xs").
322 var duplicate_button = $("<button/>").text("Duplicate").addClass("btn btn-default btn-xs").
319 click(function (e) {
323 click(function (e) {
320 // $(this) is the button that was clicked.
324 // $(this) is the button that was clicked.
321 var that = $(this);
325 var that = $(this);
322 var name = item.data('name');
326 var name = item.data('name');
323 var path = item.data('path');
327 var path = item.data('path');
324 var message = 'Are you sure you want to duplicate ' + name + '?';
328 var message = 'Are you sure you want to duplicate ' + name + '?';
325 var copy_from = {copy_from : path};
329 var copy_from = {copy_from : path};
326 IPython.dialog.modal({
330 IPython.dialog.modal({
327 title : "Duplicate " + name,
331 title : "Duplicate " + name,
328 body : message,
332 body : message,
329 buttons : {
333 buttons : {
330 Duplicate : {
334 Duplicate : {
331 class: "btn-primary",
335 class: "btn-primary",
332 click: function() {
336 click: function() {
333 notebooklist.contents.copy(path, notebooklist.notebook_path).then(function () {
337 notebooklist.contents.copy(path, notebooklist.notebook_path).then(function () {
334 notebooklist.load_list();
338 notebooklist.load_list();
335 });
339 });
336 }
340 }
337 },
341 },
338 Cancel : {}
342 Cancel : {}
339 }
343 }
340 });
344 });
341 return false;
345 return false;
342 });
346 });
343 item.find(".item_buttons").append(duplicate_button);
347 item.find(".item_buttons").append(duplicate_button);
344 };
348 };
345
349
346 NotebookList.prototype.add_delete_button = function (item) {
350 NotebookList.prototype.add_delete_button = function (item) {
347 var notebooklist = this;
351 var notebooklist = this;
348 var delete_button = $("<button/>").text("Delete").addClass("btn btn-default btn-xs").
352 var delete_button = $("<button/>").text("Delete").addClass("btn btn-default btn-xs").
349 click(function (e) {
353 click(function (e) {
350 // $(this) is the button that was clicked.
354 // $(this) is the button that was clicked.
351 var that = $(this);
355 var that = $(this);
352 // We use the filename from the parent list_item element's
356 // We use the filename from the parent list_item element's
353 // data because the outer scope's values change as we iterate through the loop.
357 // data because the outer scope's values change as we iterate through the loop.
354 var parent_item = that.parents('div.list_item');
358 var parent_item = that.parents('div.list_item');
355 var name = parent_item.data('name');
359 var name = parent_item.data('name');
356 var path = parent_item.data('path');
360 var path = parent_item.data('path');
357 var message = 'Are you sure you want to permanently delete the file: ' + name + '?';
361 var message = 'Are you sure you want to permanently delete the file: ' + name + '?';
358 dialog.modal({
362 dialog.modal({
359 title : "Delete file",
363 title : "Delete file",
360 body : message,
364 body : message,
361 buttons : {
365 buttons : {
362 Delete : {
366 Delete : {
363 class: "btn-danger",
367 class: "btn-danger",
364 click: function() {
368 click: function() {
365 notebooklist.contents.delete(path).then(
369 notebooklist.contents.delete(path).then(
366 function() {
370 function() {
367 notebooklist.notebook_deleted(path);
371 notebooklist.notebook_deleted(path);
368 }
372 }
369 );
373 );
370 }
374 }
371 },
375 },
372 Cancel : {}
376 Cancel : {}
373 }
377 }
374 });
378 });
375 return false;
379 return false;
376 });
380 });
377 item.find(".item_buttons").append(delete_button);
381 item.find(".item_buttons").append(delete_button);
378 };
382 };
379
383
380 NotebookList.prototype.notebook_deleted = function(path) {
384 NotebookList.prototype.notebook_deleted = function(path) {
381 // Remove the deleted notebook.
385 /**
386 * Remove the deleted notebook.
387 */
382 $( ":data(path)" ).each(function() {
388 $( ":data(path)" ).each(function() {
383 var element = $(this);
389 var element = $(this);
384 if (element.data("path") == path) {
390 if (element.data("path") == path) {
385 element.remove();
391 element.remove();
386 events.trigger('notebook_deleted.NotebookList');
392 events.trigger('notebook_deleted.NotebookList');
387 }
393 }
388 });
394 });
389 };
395 };
390
396
391
397
392 NotebookList.prototype.add_upload_button = function (item) {
398 NotebookList.prototype.add_upload_button = function (item) {
393 var that = this;
399 var that = this;
394 var upload_button = $('<button/>').text("Upload")
400 var upload_button = $('<button/>').text("Upload")
395 .addClass('btn btn-primary btn-xs upload_button')
401 .addClass('btn btn-primary btn-xs upload_button')
396 .click(function (e) {
402 .click(function (e) {
397 var filename = item.find('.item_name > input').val();
403 var filename = item.find('.item_name > input').val();
398 var path = utils.url_path_join(that.notebook_path, filename);
404 var path = utils.url_path_join(that.notebook_path, filename);
399 var filedata = item.data('filedata');
405 var filedata = item.data('filedata');
400 var format = 'text';
406 var format = 'text';
401 if (filename.length === 0 || filename[0] === '.') {
407 if (filename.length === 0 || filename[0] === '.') {
402 dialog.modal({
408 dialog.modal({
403 title : 'Invalid file name',
409 title : 'Invalid file name',
404 body : "File names must be at least one character and not start with a dot",
410 body : "File names must be at least one character and not start with a dot",
405 buttons : {'OK' : { 'class' : 'btn-primary' }}
411 buttons : {'OK' : { 'class' : 'btn-primary' }}
406 });
412 });
407 return false;
413 return false;
408 }
414 }
409 if (filedata instanceof ArrayBuffer) {
415 if (filedata instanceof ArrayBuffer) {
410 // base64-encode binary file data
416 // base64-encode binary file data
411 var bytes = '';
417 var bytes = '';
412 var buf = new Uint8Array(filedata);
418 var buf = new Uint8Array(filedata);
413 var nbytes = buf.byteLength;
419 var nbytes = buf.byteLength;
414 for (var i=0; i<nbytes; i++) {
420 for (var i=0; i<nbytes; i++) {
415 bytes += String.fromCharCode(buf[i]);
421 bytes += String.fromCharCode(buf[i]);
416 }
422 }
417 filedata = btoa(bytes);
423 filedata = btoa(bytes);
418 format = 'base64';
424 format = 'base64';
419 }
425 }
420 var model = {};
426 var model = {};
421
427
422 var name_and_ext = utils.splitext(filename);
428 var name_and_ext = utils.splitext(filename);
423 var file_ext = name_and_ext[1];
429 var file_ext = name_and_ext[1];
424 var content_type;
430 var content_type;
425 if (file_ext === '.ipynb') {
431 if (file_ext === '.ipynb') {
426 model.type = 'notebook';
432 model.type = 'notebook';
427 model.format = 'json';
433 model.format = 'json';
428 try {
434 try {
429 model.content = JSON.parse(filedata);
435 model.content = JSON.parse(filedata);
430 } catch (e) {
436 } catch (e) {
431 dialog.modal({
437 dialog.modal({
432 title : 'Cannot upload invalid Notebook',
438 title : 'Cannot upload invalid Notebook',
433 body : "The error was: " + e,
439 body : "The error was: " + e,
434 buttons : {'OK' : {
440 buttons : {'OK' : {
435 'class' : 'btn-primary',
441 'class' : 'btn-primary',
436 click: function () {
442 click: function () {
437 item.remove();
443 item.remove();
438 }
444 }
439 }}
445 }}
440 });
446 });
441 return false;
447 return false;
442 }
448 }
443 content_type = 'application/json';
449 content_type = 'application/json';
444 } else {
450 } else {
445 model.type = 'file';
451 model.type = 'file';
446 model.format = format;
452 model.format = format;
447 model.content = filedata;
453 model.content = filedata;
448 content_type = 'application/octet-stream';
454 content_type = 'application/octet-stream';
449 }
455 }
450 filedata = item.data('filedata');
456 filedata = item.data('filedata');
451
457
452 var on_success = function () {
458 var on_success = function () {
453 item.removeClass('new-file');
459 item.removeClass('new-file');
454 that.add_link(model, item);
460 that.add_link(model, item);
455 that.add_delete_button(item);
461 that.add_delete_button(item);
456 that.session_list.load_sessions();
462 that.session_list.load_sessions();
457 };
463 };
458
464
459 var exists = false;
465 var exists = false;
460 $.each(that.element.find('.list_item:not(.new-file)'), function(k,v){
466 $.each(that.element.find('.list_item:not(.new-file)'), function(k,v){
461 if ($(v).data('name') === filename) { exists = true; return false; }
467 if ($(v).data('name') === filename) { exists = true; return false; }
462 });
468 });
463
469
464 if (exists) {
470 if (exists) {
465 dialog.modal({
471 dialog.modal({
466 title : "Replace file",
472 title : "Replace file",
467 body : 'There is already a file named ' + filename + ', do you want to replace it?',
473 body : 'There is already a file named ' + filename + ', do you want to replace it?',
468 buttons : {
474 buttons : {
469 Overwrite : {
475 Overwrite : {
470 class: "btn-danger",
476 class: "btn-danger",
471 click: function () {
477 click: function () {
472 that.contents.save(path, model).then(on_success);
478 that.contents.save(path, model).then(on_success);
473 }
479 }
474 },
480 },
475 Cancel : {
481 Cancel : {
476 click: function() { item.remove(); }
482 click: function() { item.remove(); }
477 }
483 }
478 }
484 }
479 });
485 });
480 } else {
486 } else {
481 that.contents.save(path, model).then(on_success);
487 that.contents.save(path, model).then(on_success);
482 }
488 }
483
489
484 return false;
490 return false;
485 });
491 });
486 var cancel_button = $('<button/>').text("Cancel")
492 var cancel_button = $('<button/>').text("Cancel")
487 .addClass("btn btn-default btn-xs")
493 .addClass("btn btn-default btn-xs")
488 .click(function (e) {
494 .click(function (e) {
489 item.remove();
495 item.remove();
490 return false;
496 return false;
491 });
497 });
492 item.find(".item_buttons").empty()
498 item.find(".item_buttons").empty()
493 .append(upload_button)
499 .append(upload_button)
494 .append(cancel_button);
500 .append(cancel_button);
495 };
501 };
496
502
497
503
498 // Backwards compatability.
504 // Backwards compatability.
499 IPython.NotebookList = NotebookList;
505 IPython.NotebookList = NotebookList;
500
506
501 return {'NotebookList': NotebookList};
507 return {'NotebookList': NotebookList};
502 });
508 });
@@ -1,53 +1,55 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) {
11 var SesssionList = function (options) {
12 // Constructor
12 /**
13 //
13 * Constructor
14 // Parameters:
14 *
15 // options: dictionary
15 * Parameters:
16 // Dictionary of keyword arguments.
16 * options: dictionary
17 // events: $(Events) instance
17 * Dictionary of keyword arguments.
18 // base_url : string
18 * events: $(Events) instance
19 * base_url : string
20 */
19 this.events = options.events;
21 this.events = options.events;
20 this.sessions = {};
22 this.sessions = {};
21 this.base_url = options.base_url || utils.get_body_data("baseUrl");
23 this.base_url = options.base_url || utils.get_body_data("baseUrl");
22 };
24 };
23
25
24 SesssionList.prototype.load_sessions = function(){
26 SesssionList.prototype.load_sessions = function(){
25 var that = this;
27 var that = this;
26 var settings = {
28 var settings = {
27 processData : false,
29 processData : false,
28 cache : false,
30 cache : false,
29 type : "GET",
31 type : "GET",
30 dataType : "json",
32 dataType : "json",
31 success : $.proxy(that.sessions_loaded, this),
33 success : $.proxy(that.sessions_loaded, this),
32 error : utils.log_ajax_error,
34 error : utils.log_ajax_error,
33 };
35 };
34 var url = utils.url_join_encode(this.base_url, 'api/sessions');
36 var url = utils.url_join_encode(this.base_url, 'api/sessions');
35 $.ajax(url, settings);
37 $.ajax(url, settings);
36 };
38 };
37
39
38 SesssionList.prototype.sessions_loaded = function(data){
40 SesssionList.prototype.sessions_loaded = function(data){
39 this.sessions = {};
41 this.sessions = {};
40 var len = data.length;
42 var len = data.length;
41 var nb_path;
43 var nb_path;
42 for (var i=0; i<len; i++) {
44 for (var i=0; i<len; i++) {
43 nb_path = data[i].notebook.path;
45 nb_path = data[i].notebook.path;
44 this.sessions[nb_path] = data[i].id;
46 this.sessions[nb_path] = data[i].id;
45 }
47 }
46 this.events.trigger('sessions_loaded.Dashboard', this.sessions);
48 this.events.trigger('sessions_loaded.Dashboard', this.sessions);
47 };
49 };
48
50
49 // Backwards compatability.
51 // Backwards compatability.
50 IPython.SesssionList = SesssionList;
52 IPython.SesssionList = SesssionList;
51
53
52 return {'SesssionList': SesssionList};
54 return {'SesssionList': SesssionList};
53 });
55 });
@@ -1,120 +1,122 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 'tree/js/notebooklist',
8 'tree/js/notebooklist',
9 ], function(IPython, utils, $, notebooklist) {
9 ], function(IPython, utils, $, notebooklist) {
10 "use strict";
10 "use strict";
11
11
12 var TerminalList = function (selector, options) {
12 var TerminalList = function (selector, options) {
13 // Constructor
13 /**
14 //
14 * Constructor
15 // Parameters:
15 *
16 // selector: string
16 * Parameters:
17 // options: dictionary
17 * selector: string
18 // Dictionary of keyword arguments.
18 * options: dictionary
19 // base_url: string
19 * Dictionary of keyword arguments.
20 * base_url: string
21 */
20 this.base_url = options.base_url || utils.get_body_data("baseUrl");
22 this.base_url = options.base_url || utils.get_body_data("baseUrl");
21 this.element_name = options.element_name || 'terminal';
23 this.element_name = options.element_name || 'terminal';
22 this.selector = selector;
24 this.selector = selector;
23 this.terminals = [];
25 this.terminals = [];
24 if (this.selector !== undefined) {
26 if (this.selector !== undefined) {
25 this.element = $(selector);
27 this.element = $(selector);
26 this.style();
28 this.style();
27 this.bind_events();
29 this.bind_events();
28 this.load_terminals();
30 this.load_terminals();
29 }
31 }
30 };
32 };
31
33
32 TerminalList.prototype = Object.create(notebooklist.NotebookList.prototype);
34 TerminalList.prototype = Object.create(notebooklist.NotebookList.prototype);
33
35
34 TerminalList.prototype.bind_events = function () {
36 TerminalList.prototype.bind_events = function () {
35 var that = this;
37 var that = this;
36 $('#refresh_' + this.element_name + '_list').click(function () {
38 $('#refresh_' + this.element_name + '_list').click(function () {
37 that.load_terminals();
39 that.load_terminals();
38 });
40 });
39 $('#new_terminal').click($.proxy(this.new_terminal, this));
41 $('#new_terminal').click($.proxy(this.new_terminal, this));
40 };
42 };
41
43
42 TerminalList.prototype.new_terminal = function () {
44 TerminalList.prototype.new_terminal = function () {
43 var w = window.open();
45 var w = window.open();
44 var base_url = this.base_url;
46 var base_url = this.base_url;
45 var settings = {
47 var settings = {
46 type : "POST",
48 type : "POST",
47 dataType: "json",
49 dataType: "json",
48 success : function (data, status, xhr) {
50 success : function (data, status, xhr) {
49 var name = data.name;
51 var name = data.name;
50 w.location = utils.url_join_encode(base_url, 'terminals', name);
52 w.location = utils.url_join_encode(base_url, 'terminals', name);
51 },
53 },
52 error : function(jqXHR, status, error){
54 error : function(jqXHR, status, error){
53 w.close();
55 w.close();
54 utils.log_ajax_error(jqXHR, status, error);
56 utils.log_ajax_error(jqXHR, status, error);
55 },
57 },
56 };
58 };
57 var url = utils.url_join_encode(
59 var url = utils.url_join_encode(
58 this.base_url,
60 this.base_url,
59 'api/terminals'
61 'api/terminals'
60 );
62 );
61 $.ajax(url, settings);
63 $.ajax(url, settings);
62 };
64 };
63
65
64 TerminalList.prototype.load_terminals = function() {
66 TerminalList.prototype.load_terminals = function() {
65 var that = this;
67 var that = this;
66 var url = utils.url_join_encode(this.base_url, 'api/terminals');
68 var url = utils.url_join_encode(this.base_url, 'api/terminals');
67 $.ajax(url, {
69 $.ajax(url, {
68 type: "GET",
70 type: "GET",
69 cache: false,
71 cache: false,
70 dataType: "json",
72 dataType: "json",
71 success: $.proxy(this.terminals_loaded, this),
73 success: $.proxy(this.terminals_loaded, this),
72 error : utils.log_ajax_error
74 error : utils.log_ajax_error
73 });
75 });
74 };
76 };
75
77
76 TerminalList.prototype.terminals_loaded = function (data) {
78 TerminalList.prototype.terminals_loaded = function (data) {
77 this.terminals = data;
79 this.terminals = data;
78 this.clear_list();
80 this.clear_list();
79 var item, path_name, term;
81 var item, path_name, term;
80 for (var i=0; i < this.terminals.length; i++) {
82 for (var i=0; i < this.terminals.length; i++) {
81 term = this.terminals[i];
83 term = this.terminals[i];
82 item = this.new_item(-1);
84 item = this.new_item(-1);
83 this.add_link(term.name, item);
85 this.add_link(term.name, item);
84 this.add_shutdown_button(term.name, item);
86 this.add_shutdown_button(term.name, item);
85 }
87 }
86 $('#terminal_list_header').toggle(data.length === 0);
88 $('#terminal_list_header').toggle(data.length === 0);
87 };
89 };
88
90
89 TerminalList.prototype.add_link = function(name, item) {
91 TerminalList.prototype.add_link = function(name, item) {
90 item.data('term-name', name);
92 item.data('term-name', name);
91 item.find(".item_name").text("terminals/" + name);
93 item.find(".item_name").text("terminals/" + name);
92 item.find(".item_icon").addClass("fa fa-terminal");
94 item.find(".item_icon").addClass("fa fa-terminal");
93 var link = item.find("a.item_link")
95 var link = item.find("a.item_link")
94 .attr('href', utils.url_join_encode(this.base_url, "terminals", name));
96 .attr('href', utils.url_join_encode(this.base_url, "terminals", name));
95 link.attr('target', '_blank');
97 link.attr('target', '_blank');
96 this.add_shutdown_button(name, item);
98 this.add_shutdown_button(name, item);
97 };
99 };
98
100
99 TerminalList.prototype.add_shutdown_button = function(name, item) {
101 TerminalList.prototype.add_shutdown_button = function(name, item) {
100 var that = this;
102 var that = this;
101 var shutdown_button = $("<button/>").text("Shutdown").addClass("btn btn-xs btn-danger").
103 var shutdown_button = $("<button/>").text("Shutdown").addClass("btn btn-xs btn-danger").
102 click(function (e) {
104 click(function (e) {
103 var settings = {
105 var settings = {
104 processData : false,
106 processData : false,
105 type : "DELETE",
107 type : "DELETE",
106 dataType : "json",
108 dataType : "json",
107 success : function () {
109 success : function () {
108 that.load_terminals();
110 that.load_terminals();
109 },
111 },
110 error : utils.log_ajax_error,
112 error : utils.log_ajax_error,
111 };
113 };
112 var url = utils.url_join_encode(that.base_url, 'api/terminals', name);
114 var url = utils.url_join_encode(that.base_url, 'api/terminals', name);
113 $.ajax(url, settings);
115 $.ajax(url, settings);
114 return false;
116 return false;
115 });
117 });
116 item.find(".item_buttons").text("").append(shutdown_button);
118 item.find(".item_buttons").text("").append(shutdown_button);
117 };
119 };
118
120
119 return {TerminalList: TerminalList};
121 return {TerminalList: TerminalList};
120 });
122 });
@@ -1,239 +1,255 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 "underscore",
5 "underscore",
6 "backbone",
6 "backbone",
7 "jquery",
7 "jquery",
8 "base/js/utils",
8 "base/js/utils",
9 "base/js/namespace",
9 "base/js/namespace",
10 ], function (_, Backbone, $, utils, IPython) {
10 ], function (_, Backbone, $, utils, IPython) {
11 "use strict";
11 "use strict";
12 //--------------------------------------------------------------------
12 //--------------------------------------------------------------------
13 // WidgetManager class
13 // WidgetManager class
14 //--------------------------------------------------------------------
14 //--------------------------------------------------------------------
15 var WidgetManager = function (comm_manager, notebook) {
15 var WidgetManager = function (comm_manager, notebook) {
16 // Public constructor
16 /**
17 * Public constructor
18 */
17 WidgetManager._managers.push(this);
19 WidgetManager._managers.push(this);
18
20
19 // Attach a comm manager to the
21 // Attach a comm manager to the
20 this.keyboard_manager = notebook.keyboard_manager;
22 this.keyboard_manager = notebook.keyboard_manager;
21 this.notebook = notebook;
23 this.notebook = notebook;
22 this.comm_manager = comm_manager;
24 this.comm_manager = comm_manager;
23 this._models = {}; /* Dictionary of model ids and model instances */
25 this._models = {}; /* Dictionary of model ids and model instances */
24
26
25 // Register with the comm manager.
27 // Register with the comm manager.
26 this.comm_manager.register_target('ipython.widget', $.proxy(this._handle_comm_open, this));
28 this.comm_manager.register_target('ipython.widget', $.proxy(this._handle_comm_open, this));
27 };
29 };
28
30
29 //--------------------------------------------------------------------
31 //--------------------------------------------------------------------
30 // Class level
32 // Class level
31 //--------------------------------------------------------------------
33 //--------------------------------------------------------------------
32 WidgetManager._model_types = {}; /* Dictionary of model type names (target_name) and model types. */
34 WidgetManager._model_types = {}; /* Dictionary of model type names (target_name) and model types. */
33 WidgetManager._view_types = {}; /* Dictionary of view names and view types. */
35 WidgetManager._view_types = {}; /* Dictionary of view names and view types. */
34 WidgetManager._managers = []; /* List of widget managers */
36 WidgetManager._managers = []; /* List of widget managers */
35
37
36 WidgetManager.register_widget_model = function (model_name, model_type) {
38 WidgetManager.register_widget_model = function (model_name, model_type) {
37 // Registers a widget model by name.
39 // Registers a widget model by name.
38 WidgetManager._model_types[model_name] = model_type;
40 WidgetManager._model_types[model_name] = model_type;
39 };
41 };
40
42
41 WidgetManager.register_widget_view = function (view_name, view_type) {
43 WidgetManager.register_widget_view = function (view_name, view_type) {
42 // Registers a widget view by name.
44 // Registers a widget view by name.
43 WidgetManager._view_types[view_name] = view_type;
45 WidgetManager._view_types[view_name] = view_type;
44 };
46 };
45
47
46 //--------------------------------------------------------------------
48 //--------------------------------------------------------------------
47 // Instance level
49 // Instance level
48 //--------------------------------------------------------------------
50 //--------------------------------------------------------------------
49 WidgetManager.prototype.display_view = function(msg, model) {
51 WidgetManager.prototype.display_view = function(msg, model) {
50 // Displays a view for a particular model.
52 /**
53 * Displays a view for a particular model.
54 */
51 var that = this;
55 var that = this;
52 var cell = this.get_msg_cell(msg.parent_header.msg_id);
56 var cell = this.get_msg_cell(msg.parent_header.msg_id);
53 if (cell === null) {
57 if (cell === null) {
54 return Promise.reject(new Error("Could not determine where the display" +
58 return Promise.reject(new Error("Could not determine where the display" +
55 " message was from. Widget will not be displayed"));
59 " message was from. Widget will not be displayed"));
56 } else if (cell.widget_subarea) {
60 } else if (cell.widget_subarea) {
57 var dummy = $('<div />');
61 var dummy = $('<div />');
58 cell.widget_subarea.append(dummy);
62 cell.widget_subarea.append(dummy);
59 return this.create_view(model, {cell: cell}).then(
63 return this.create_view(model, {cell: cell}).then(
60 function(view) {
64 function(view) {
61 that._handle_display_view(view);
65 that._handle_display_view(view);
62 dummy.replaceWith(view.$el);
66 dummy.replaceWith(view.$el);
63 view.trigger('displayed');
67 view.trigger('displayed');
64 return view;
68 return view;
65 }).catch(utils.reject('Could not display view', true));
69 }).catch(utils.reject('Could not display view', true));
66 }
70 }
67 };
71 };
68
72
69 WidgetManager.prototype._handle_display_view = function (view) {
73 WidgetManager.prototype._handle_display_view = function (view) {
70 // Have the IPython keyboard manager disable its event
74 /**
71 // handling so the widget can capture keyboard input.
75 * Have the IPython keyboard manager disable its event
72 // Note, this is only done on the outer most widgets.
76 * handling so the widget can capture keyboard input.
77 * Note, this is only done on the outer most widgets.
78 */
73 if (this.keyboard_manager) {
79 if (this.keyboard_manager) {
74 this.keyboard_manager.register_events(view.$el);
80 this.keyboard_manager.register_events(view.$el);
75
81
76 if (view.additional_elements) {
82 if (view.additional_elements) {
77 for (var i = 0; i < view.additional_elements.length; i++) {
83 for (var i = 0; i < view.additional_elements.length; i++) {
78 this.keyboard_manager.register_events(view.additional_elements[i]);
84 this.keyboard_manager.register_events(view.additional_elements[i]);
79 }
85 }
80 }
86 }
81 }
87 }
82 };
88 };
83
89
84 WidgetManager.prototype.create_view = function(model, options) {
90 WidgetManager.prototype.create_view = function(model, options) {
85 // Creates a promise for a view of a given model
91 /**
86
92 * Creates a promise for a view of a given model
87 // Make sure the view creation is not out of order with
93 *
88 // any state updates.
94 * Make sure the view creation is not out of order with
95 * any state updates.
96 */
89 model.state_change = model.state_change.then(function() {
97 model.state_change = model.state_change.then(function() {
90
98
91 return utils.load_class(model.get('_view_name'), model.get('_view_module'),
99 return utils.load_class(model.get('_view_name'), model.get('_view_module'),
92 WidgetManager._view_types).then(function(ViewType) {
100 WidgetManager._view_types).then(function(ViewType) {
93
101
94 // If a view is passed into the method, use that view's cell as
102 // If a view is passed into the method, use that view's cell as
95 // the cell for the view that is created.
103 // the cell for the view that is created.
96 options = options || {};
104 options = options || {};
97 if (options.parent !== undefined) {
105 if (options.parent !== undefined) {
98 options.cell = options.parent.options.cell;
106 options.cell = options.parent.options.cell;
99 }
107 }
100 // Create and render the view...
108 // Create and render the view...
101 var parameters = {model: model, options: options};
109 var parameters = {model: model, options: options};
102 var view = new ViewType(parameters);
110 var view = new ViewType(parameters);
103 view.listenTo(model, 'destroy', view.remove);
111 view.listenTo(model, 'destroy', view.remove);
104 return Promise.resolve(view.render()).then(function() {return view;});
112 return Promise.resolve(view.render()).then(function() {return view;});
105 }).catch(utils.reject("Couldn't create a view for model id '" + String(model.id) + "'", true));
113 }).catch(utils.reject("Couldn't create a view for model id '" + String(model.id) + "'", true));
106 });
114 });
107 return model.state_change;
115 return model.state_change;
108 };
116 };
109
117
110 WidgetManager.prototype.get_msg_cell = function (msg_id) {
118 WidgetManager.prototype.get_msg_cell = function (msg_id) {
111 var cell = null;
119 var cell = null;
112 // First, check to see if the msg was triggered by cell execution.
120 // First, check to see if the msg was triggered by cell execution.
113 if (this.notebook) {
121 if (this.notebook) {
114 cell = this.notebook.get_msg_cell(msg_id);
122 cell = this.notebook.get_msg_cell(msg_id);
115 }
123 }
116 if (cell !== null) {
124 if (cell !== null) {
117 return cell;
125 return cell;
118 }
126 }
119 // Second, check to see if a get_cell callback was defined
127 // Second, check to see if a get_cell callback was defined
120 // for the message. get_cell callbacks are registered for
128 // for the message. get_cell callbacks are registered for
121 // widget messages, so this block is actually checking to see if the
129 // widget messages, so this block is actually checking to see if the
122 // message was triggered by a widget.
130 // message was triggered by a widget.
123 var kernel = this.comm_manager.kernel;
131 var kernel = this.comm_manager.kernel;
124 if (kernel) {
132 if (kernel) {
125 var callbacks = kernel.get_callbacks_for_msg(msg_id);
133 var callbacks = kernel.get_callbacks_for_msg(msg_id);
126 if (callbacks && callbacks.iopub &&
134 if (callbacks && callbacks.iopub &&
127 callbacks.iopub.get_cell !== undefined) {
135 callbacks.iopub.get_cell !== undefined) {
128 return callbacks.iopub.get_cell();
136 return callbacks.iopub.get_cell();
129 }
137 }
130 }
138 }
131
139
132 // Not triggered by a cell or widget (no get_cell callback
140 // Not triggered by a cell or widget (no get_cell callback
133 // exists).
141 // exists).
134 return null;
142 return null;
135 };
143 };
136
144
137 WidgetManager.prototype.callbacks = function (view) {
145 WidgetManager.prototype.callbacks = function (view) {
138 // callback handlers specific a view
146 /**
147 * callback handlers specific a view
148 */
139 var callbacks = {};
149 var callbacks = {};
140 if (view && view.options.cell) {
150 if (view && view.options.cell) {
141
151
142 // Try to get output handlers
152 // Try to get output handlers
143 var cell = view.options.cell;
153 var cell = view.options.cell;
144 var handle_output = null;
154 var handle_output = null;
145 var handle_clear_output = null;
155 var handle_clear_output = null;
146 if (cell.output_area) {
156 if (cell.output_area) {
147 handle_output = $.proxy(cell.output_area.handle_output, cell.output_area);
157 handle_output = $.proxy(cell.output_area.handle_output, cell.output_area);
148 handle_clear_output = $.proxy(cell.output_area.handle_clear_output, cell.output_area);
158 handle_clear_output = $.proxy(cell.output_area.handle_clear_output, cell.output_area);
149 }
159 }
150
160
151 // Create callback dictionary using what is known
161 // Create callback dictionary using what is known
152 var that = this;
162 var that = this;
153 callbacks = {
163 callbacks = {
154 iopub : {
164 iopub : {
155 output : handle_output,
165 output : handle_output,
156 clear_output : handle_clear_output,
166 clear_output : handle_clear_output,
157
167
158 // Special function only registered by widget messages.
168 // Special function only registered by widget messages.
159 // Allows us to get the cell for a message so we know
169 // Allows us to get the cell for a message so we know
160 // where to add widgets if the code requires it.
170 // where to add widgets if the code requires it.
161 get_cell : function () {
171 get_cell : function () {
162 return cell;
172 return cell;
163 },
173 },
164 },
174 },
165 };
175 };
166 }
176 }
167 return callbacks;
177 return callbacks;
168 };
178 };
169
179
170 WidgetManager.prototype.get_model = function (model_id) {
180 WidgetManager.prototype.get_model = function (model_id) {
171 // Get a promise for a model by model id.
181 /**
182 * Get a promise for a model by model id.
183 */
172 return this._models[model_id];
184 return this._models[model_id];
173 };
185 };
174
186
175 WidgetManager.prototype._handle_comm_open = function (comm, msg) {
187 WidgetManager.prototype._handle_comm_open = function (comm, msg) {
176 // Handle when a comm is opened.
188 /**
189 * Handle when a comm is opened.
190 */
177 return this.create_model({
191 return this.create_model({
178 model_name: msg.content.data.model_name,
192 model_name: msg.content.data.model_name,
179 model_module: msg.content.data.model_module,
193 model_module: msg.content.data.model_module,
180 comm: comm}).catch(utils.reject("Couldn't create a model.", true));
194 comm: comm}).catch(utils.reject("Couldn't create a model.", true));
181 };
195 };
182
196
183 WidgetManager.prototype.create_model = function (options) {
197 WidgetManager.prototype.create_model = function (options) {
184 // Create and return a promise for a new widget model
198 /**
185 //
199 * Create and return a promise for a new widget model
186 // Minimally, one must provide the model_name and widget_class
200 *
187 // parameters to create a model from Javascript.
201 * Minimally, one must provide the model_name and widget_class
188 //
202 * parameters to create a model from Javascript.
189 // Example
203 *
190 // --------
204 * Example
191 // JS:
205 * --------
192 // IPython.notebook.kernel.widget_manager.create_model({
206 * JS:
193 // model_name: 'WidgetModel',
207 * IPython.notebook.kernel.widget_manager.create_model({
194 // widget_class: 'IPython.html.widgets.widget_int.IntSlider'})
208 * model_name: 'WidgetModel',
195 // .then(function(model) { console.log('Create success!', model); },
209 * widget_class: 'IPython.html.widgets.widget_int.IntSlider'})
196 // $.proxy(console.error, console));
210 * .then(function(model) { console.log('Create success!', model); },
197 //
211 * $.proxy(console.error, console));
198 // Parameters
212 *
199 // ----------
213 * Parameters
200 // options: dictionary
214 * ----------
201 // Dictionary of options with the following contents:
215 * options: dictionary
202 // model_name: string
216 * Dictionary of options with the following contents:
203 // Target name of the widget model to create.
217 * model_name: string
204 // model_module: (optional) string
218 * Target name of the widget model to create.
205 // Module name of the widget model to create.
219 * model_module: (optional) string
206 // widget_class: (optional) string
220 * Module name of the widget model to create.
207 // Target name of the widget in the back-end.
221 * widget_class: (optional) string
208 // comm: (optional) Comm
222 * Target name of the widget in the back-end.
209
223 * comm: (optional) Comm
210 // Create a comm if it wasn't provided.
224 *
225 * Create a comm if it wasn't provided.
226 */
211 var comm = options.comm;
227 var comm = options.comm;
212 if (!comm) {
228 if (!comm) {
213 comm = this.comm_manager.new_comm('ipython.widget', {'widget_class': options.widget_class});
229 comm = this.comm_manager.new_comm('ipython.widget', {'widget_class': options.widget_class});
214 }
230 }
215
231
216 var that = this;
232 var that = this;
217 var model_id = comm.comm_id;
233 var model_id = comm.comm_id;
218 var model_promise = utils.load_class(options.model_name, options.model_module, WidgetManager._model_types)
234 var model_promise = utils.load_class(options.model_name, options.model_module, WidgetManager._model_types)
219 .then(function(ModelType) {
235 .then(function(ModelType) {
220 var widget_model = new ModelType(that, model_id, comm);
236 var widget_model = new ModelType(that, model_id, comm);
221 widget_model.once('comm:close', function () {
237 widget_model.once('comm:close', function () {
222 delete that._models[model_id];
238 delete that._models[model_id];
223 });
239 });
224 return widget_model;
240 return widget_model;
225
241
226 }, function(error) {
242 }, function(error) {
227 delete that._models[model_id];
243 delete that._models[model_id];
228 var wrapped_error = new utils.WrappedError("Couldn't create model", error);
244 var wrapped_error = new utils.WrappedError("Couldn't create model", error);
229 return Promise.reject(wrapped_error);
245 return Promise.reject(wrapped_error);
230 });
246 });
231 this._models[model_id] = model_promise;
247 this._models[model_id] = model_promise;
232 return model_promise;
248 return model_promise;
233 };
249 };
234
250
235 // Backwards compatibility.
251 // Backwards compatibility.
236 IPython.WidgetManager = WidgetManager;
252 IPython.WidgetManager = WidgetManager;
237
253
238 return {'WidgetManager': WidgetManager};
254 return {'WidgetManager': WidgetManager};
239 });
255 });
@@ -1,617 +1,679 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(["widgets/js/manager",
4 define(["widgets/js/manager",
5 "underscore",
5 "underscore",
6 "backbone",
6 "backbone",
7 "jquery",
7 "jquery",
8 "base/js/utils",
8 "base/js/utils",
9 "base/js/namespace",
9 "base/js/namespace",
10 ], function(widgetmanager, _, Backbone, $, utils, IPython){
10 ], function(widgetmanager, _, Backbone, $, utils, IPython){
11
11
12 var WidgetModel = Backbone.Model.extend({
12 var WidgetModel = Backbone.Model.extend({
13 constructor: function (widget_manager, model_id, comm) {
13 constructor: function (widget_manager, model_id, comm) {
14 // Constructor
14 /**
15 //
15 * Constructor
16 // Creates a WidgetModel instance.
16 *
17 //
17 * Creates a WidgetModel instance.
18 // Parameters
18 *
19 // ----------
19 * Parameters
20 // widget_manager : WidgetManager instance
20 * ----------
21 // model_id : string
21 * widget_manager : WidgetManager instance
22 // An ID unique to this model.
22 * model_id : string
23 // comm : Comm instance (optional)
23 * An ID unique to this model.
24 * comm : Comm instance (optional)
25 */
24 this.widget_manager = widget_manager;
26 this.widget_manager = widget_manager;
25 this.state_change = Promise.resolve();
27 this.state_change = Promise.resolve();
26 this._buffered_state_diff = {};
28 this._buffered_state_diff = {};
27 this.pending_msgs = 0;
29 this.pending_msgs = 0;
28 this.msg_buffer = null;
30 this.msg_buffer = null;
29 this.state_lock = null;
31 this.state_lock = null;
30 this.id = model_id;
32 this.id = model_id;
31 this.views = {};
33 this.views = {};
32
34
33 if (comm !== undefined) {
35 if (comm !== undefined) {
34 // Remember comm associated with the model.
36 // Remember comm associated with the model.
35 this.comm = comm;
37 this.comm = comm;
36 comm.model = this;
38 comm.model = this;
37
39
38 // Hook comm messages up to model.
40 // Hook comm messages up to model.
39 comm.on_close($.proxy(this._handle_comm_closed, this));
41 comm.on_close($.proxy(this._handle_comm_closed, this));
40 comm.on_msg($.proxy(this._handle_comm_msg, this));
42 comm.on_msg($.proxy(this._handle_comm_msg, this));
41 }
43 }
42 return Backbone.Model.apply(this);
44 return Backbone.Model.apply(this);
43 },
45 },
44
46
45 send: function (content, callbacks) {
47 send: function (content, callbacks) {
46 // Send a custom msg over the comm.
48 /**
49 * Send a custom msg over the comm.
50 */
47 if (this.comm !== undefined) {
51 if (this.comm !== undefined) {
48 var data = {method: 'custom', content: content};
52 var data = {method: 'custom', content: content};
49 this.comm.send(data, callbacks);
53 this.comm.send(data, callbacks);
50 this.pending_msgs++;
54 this.pending_msgs++;
51 }
55 }
52 },
56 },
53
57
54 _handle_comm_closed: function (msg) {
58 _handle_comm_closed: function (msg) {
55 // Handle when a widget is closed.
59 /**
60 * Handle when a widget is closed.
61 */
56 this.trigger('comm:close');
62 this.trigger('comm:close');
57 this.stopListening();
63 this.stopListening();
58 this.trigger('destroy', this);
64 this.trigger('destroy', this);
59 delete this.comm.model; // Delete ref so GC will collect widget model.
65 delete this.comm.model; // Delete ref so GC will collect widget model.
60 delete this.comm;
66 delete this.comm;
61 delete this.model_id; // Delete id from model so widget manager cleans up.
67 delete this.model_id; // Delete id from model so widget manager cleans up.
62 for (var id in this.views) {
68 for (var id in this.views) {
63 if (this.views.hasOwnProperty(id)) {
69 if (this.views.hasOwnProperty(id)) {
64 this.views[id].remove();
70 this.views[id].remove();
65 }
71 }
66 }
72 }
67 },
73 },
68
74
69 _handle_comm_msg: function (msg) {
75 _handle_comm_msg: function (msg) {
70 // Handle incoming comm msg.
76 /**
77 * Handle incoming comm msg.
78 */
71 var method = msg.content.data.method;
79 var method = msg.content.data.method;
72 var that = this;
80 var that = this;
73 switch (method) {
81 switch (method) {
74 case 'update':
82 case 'update':
75 this.state_change = this.state_change.then(function() {
83 this.state_change = this.state_change.then(function() {
76 return that.set_state(msg.content.data.state);
84 return that.set_state(msg.content.data.state);
77 }).catch(utils.reject("Couldn't process update msg for model id '" + String(that.id) + "'", true));
85 }).catch(utils.reject("Couldn't process update msg for model id '" + String(that.id) + "'", true));
78 break;
86 break;
79 case 'custom':
87 case 'custom':
80 this.trigger('msg:custom', msg.content.data.content);
88 this.trigger('msg:custom', msg.content.data.content);
81 break;
89 break;
82 case 'display':
90 case 'display':
83 this.widget_manager.display_view(msg, this);
91 this.widget_manager.display_view(msg, this);
84 break;
92 break;
85 }
93 }
86 },
94 },
87
95
88 set_state: function (state) {
96 set_state: function (state) {
89 var that = this;
97 var that = this;
90 // Handle when a widget is updated via the python side.
98 // Handle when a widget is updated via the python side.
91 return this._unpack_models(state).then(function(state) {
99 return this._unpack_models(state).then(function(state) {
92 that.state_lock = state;
100 that.state_lock = state;
93 try {
101 try {
94 WidgetModel.__super__.set.call(that, state);
102 WidgetModel.__super__.set.call(that, state);
95 } finally {
103 } finally {
96 that.state_lock = null;
104 that.state_lock = null;
97 }
105 }
98 }).catch(utils.reject("Couldn't set model state", true));
106 }).catch(utils.reject("Couldn't set model state", true));
99 },
107 },
100
108
101 _handle_status: function (msg, callbacks) {
109 _handle_status: function (msg, callbacks) {
102 // Handle status msgs.
110 /**
103
111 * Handle status msgs.
104 // execution_state : ('busy', 'idle', 'starting')
112 *
113 * execution_state : ('busy', 'idle', 'starting')
114 */
105 if (this.comm !== undefined) {
115 if (this.comm !== undefined) {
106 if (msg.content.execution_state ==='idle') {
116 if (msg.content.execution_state ==='idle') {
107 // Send buffer if this message caused another message to be
117 // Send buffer if this message caused another message to be
108 // throttled.
118 // throttled.
109 if (this.msg_buffer !== null &&
119 if (this.msg_buffer !== null &&
110 (this.get('msg_throttle') || 3) === this.pending_msgs) {
120 (this.get('msg_throttle') || 3) === this.pending_msgs) {
111 var data = {method: 'backbone', sync_method: 'update', sync_data: this.msg_buffer};
121 var data = {method: 'backbone', sync_method: 'update', sync_data: this.msg_buffer};
112 this.comm.send(data, callbacks);
122 this.comm.send(data, callbacks);
113 this.msg_buffer = null;
123 this.msg_buffer = null;
114 } else {
124 } else {
115 --this.pending_msgs;
125 --this.pending_msgs;
116 }
126 }
117 }
127 }
118 }
128 }
119 },
129 },
120
130
121 callbacks: function(view) {
131 callbacks: function(view) {
122 // Create msg callbacks for a comm msg.
132 /**
133 * Create msg callbacks for a comm msg.
134 */
123 var callbacks = this.widget_manager.callbacks(view);
135 var callbacks = this.widget_manager.callbacks(view);
124
136
125 if (callbacks.iopub === undefined) {
137 if (callbacks.iopub === undefined) {
126 callbacks.iopub = {};
138 callbacks.iopub = {};
127 }
139 }
128
140
129 var that = this;
141 var that = this;
130 callbacks.iopub.status = function (msg) {
142 callbacks.iopub.status = function (msg) {
131 that._handle_status(msg, callbacks);
143 that._handle_status(msg, callbacks);
132 };
144 };
133 return callbacks;
145 return callbacks;
134 },
146 },
135
147
136 set: function(key, val, options) {
148 set: function(key, val, options) {
137 // Set a value.
149 /**
150 * Set a value.
151 */
138 var return_value = WidgetModel.__super__.set.apply(this, arguments);
152 var return_value = WidgetModel.__super__.set.apply(this, arguments);
139
153
140 // Backbone only remembers the diff of the most recent set()
154 // Backbone only remembers the diff of the most recent set()
141 // operation. Calling set multiple times in a row results in a
155 // operation. Calling set multiple times in a row results in a
142 // loss of diff information. Here we keep our own running diff.
156 // loss of diff information. Here we keep our own running diff.
143 this._buffered_state_diff = $.extend(this._buffered_state_diff, this.changedAttributes() || {});
157 this._buffered_state_diff = $.extend(this._buffered_state_diff, this.changedAttributes() || {});
144 return return_value;
158 return return_value;
145 },
159 },
146
160
147 sync: function (method, model, options) {
161 sync: function (method, model, options) {
148 // Handle sync to the back-end. Called when a model.save() is called.
162 /**
149
163 * Handle sync to the back-end. Called when a model.save() is called.
150 // Make sure a comm exists.
164 *
165 * Make sure a comm exists.
166 */
151 var error = options.error || function() {
167 var error = options.error || function() {
152 console.error('Backbone sync error:', arguments);
168 console.error('Backbone sync error:', arguments);
153 };
169 };
154 if (this.comm === undefined) {
170 if (this.comm === undefined) {
155 error();
171 error();
156 return false;
172 return false;
157 }
173 }
158
174
159 // Delete any key value pairs that the back-end already knows about.
175 // Delete any key value pairs that the back-end already knows about.
160 var attrs = (method === 'patch') ? options.attrs : model.toJSON(options);
176 var attrs = (method === 'patch') ? options.attrs : model.toJSON(options);
161 if (this.state_lock !== null) {
177 if (this.state_lock !== null) {
162 var keys = Object.keys(this.state_lock);
178 var keys = Object.keys(this.state_lock);
163 for (var i=0; i<keys.length; i++) {
179 for (var i=0; i<keys.length; i++) {
164 var key = keys[i];
180 var key = keys[i];
165 if (attrs[key] === this.state_lock[key]) {
181 if (attrs[key] === this.state_lock[key]) {
166 delete attrs[key];
182 delete attrs[key];
167 }
183 }
168 }
184 }
169 }
185 }
170
186
171 // Only sync if there are attributes to send to the back-end.
187 // Only sync if there are attributes to send to the back-end.
172 attrs = this._pack_models(attrs);
188 attrs = this._pack_models(attrs);
173 if (_.size(attrs) > 0) {
189 if (_.size(attrs) > 0) {
174
190
175 // If this message was sent via backbone itself, it will not
191 // If this message was sent via backbone itself, it will not
176 // have any callbacks. It's important that we create callbacks
192 // have any callbacks. It's important that we create callbacks
177 // so we can listen for status messages, etc...
193 // so we can listen for status messages, etc...
178 var callbacks = options.callbacks || this.callbacks();
194 var callbacks = options.callbacks || this.callbacks();
179
195
180 // Check throttle.
196 // Check throttle.
181 if (this.pending_msgs >= (this.get('msg_throttle') || 3)) {
197 if (this.pending_msgs >= (this.get('msg_throttle') || 3)) {
182 // The throttle has been exceeded, buffer the current msg so
198 // The throttle has been exceeded, buffer the current msg so
183 // it can be sent once the kernel has finished processing
199 // it can be sent once the kernel has finished processing
184 // some of the existing messages.
200 // some of the existing messages.
185
201
186 // Combine updates if it is a 'patch' sync, otherwise replace updates
202 // Combine updates if it is a 'patch' sync, otherwise replace updates
187 switch (method) {
203 switch (method) {
188 case 'patch':
204 case 'patch':
189 this.msg_buffer = $.extend(this.msg_buffer || {}, attrs);
205 this.msg_buffer = $.extend(this.msg_buffer || {}, attrs);
190 break;
206 break;
191 case 'update':
207 case 'update':
192 case 'create':
208 case 'create':
193 this.msg_buffer = attrs;
209 this.msg_buffer = attrs;
194 break;
210 break;
195 default:
211 default:
196 error();
212 error();
197 return false;
213 return false;
198 }
214 }
199 this.msg_buffer_callbacks = callbacks;
215 this.msg_buffer_callbacks = callbacks;
200
216
201 } else {
217 } else {
202 // We haven't exceeded the throttle, send the message like
218 // We haven't exceeded the throttle, send the message like
203 // normal.
219 // normal.
204 var data = {method: 'backbone', sync_data: attrs};
220 var data = {method: 'backbone', sync_data: attrs};
205 this.comm.send(data, callbacks);
221 this.comm.send(data, callbacks);
206 this.pending_msgs++;
222 this.pending_msgs++;
207 }
223 }
208 }
224 }
209 // Since the comm is a one-way communication, assume the message
225 // Since the comm is a one-way communication, assume the message
210 // arrived. Don't call success since we don't have a model back from the server
226 // arrived. Don't call success since we don't have a model back from the server
211 // this means we miss out on the 'sync' event.
227 // this means we miss out on the 'sync' event.
212 this._buffered_state_diff = {};
228 this._buffered_state_diff = {};
213 },
229 },
214
230
215 save_changes: function(callbacks) {
231 save_changes: function(callbacks) {
216 // Push this model's state to the back-end
232 /**
217 //
233 * Push this model's state to the back-end
218 // This invokes a Backbone.Sync.
234 *
235 * This invokes a Backbone.Sync.
236 */
219 this.save(this._buffered_state_diff, {patch: true, callbacks: callbacks});
237 this.save(this._buffered_state_diff, {patch: true, callbacks: callbacks});
220 },
238 },
221
239
222 _pack_models: function(value) {
240 _pack_models: function(value) {
223 // Replace models with model ids recursively.
241 /**
242 * Replace models with model ids recursively.
243 */
224 var that = this;
244 var that = this;
225 var packed;
245 var packed;
226 if (value instanceof Backbone.Model) {
246 if (value instanceof Backbone.Model) {
227 return "IPY_MODEL_" + value.id;
247 return "IPY_MODEL_" + value.id;
228
248
229 } else if ($.isArray(value)) {
249 } else if ($.isArray(value)) {
230 packed = [];
250 packed = [];
231 _.each(value, function(sub_value, key) {
251 _.each(value, function(sub_value, key) {
232 packed.push(that._pack_models(sub_value));
252 packed.push(that._pack_models(sub_value));
233 });
253 });
234 return packed;
254 return packed;
235 } else if (value instanceof Date || value instanceof String) {
255 } else if (value instanceof Date || value instanceof String) {
236 return value;
256 return value;
237 } else if (value instanceof Object) {
257 } else if (value instanceof Object) {
238 packed = {};
258 packed = {};
239 _.each(value, function(sub_value, key) {
259 _.each(value, function(sub_value, key) {
240 packed[key] = that._pack_models(sub_value);
260 packed[key] = that._pack_models(sub_value);
241 });
261 });
242 return packed;
262 return packed;
243
263
244 } else {
264 } else {
245 return value;
265 return value;
246 }
266 }
247 },
267 },
248
268
249 _unpack_models: function(value) {
269 _unpack_models: function(value) {
250 // Replace model ids with models recursively.
270 /**
271 * Replace model ids with models recursively.
272 */
251 var that = this;
273 var that = this;
252 var unpacked;
274 var unpacked;
253 if ($.isArray(value)) {
275 if ($.isArray(value)) {
254 unpacked = [];
276 unpacked = [];
255 _.each(value, function(sub_value, key) {
277 _.each(value, function(sub_value, key) {
256 unpacked.push(that._unpack_models(sub_value));
278 unpacked.push(that._unpack_models(sub_value));
257 });
279 });
258 return Promise.all(unpacked);
280 return Promise.all(unpacked);
259 } else if (value instanceof Object) {
281 } else if (value instanceof Object) {
260 unpacked = {};
282 unpacked = {};
261 _.each(value, function(sub_value, key) {
283 _.each(value, function(sub_value, key) {
262 unpacked[key] = that._unpack_models(sub_value);
284 unpacked[key] = that._unpack_models(sub_value);
263 });
285 });
264 return utils.resolve_promises_dict(unpacked);
286 return utils.resolve_promises_dict(unpacked);
265 } else if (typeof value === 'string' && value.slice(0,10) === "IPY_MODEL_") {
287 } else if (typeof value === 'string' && value.slice(0,10) === "IPY_MODEL_") {
266 // get_model returns a promise already
288 // get_model returns a promise already
267 return this.widget_manager.get_model(value.slice(10, value.length));
289 return this.widget_manager.get_model(value.slice(10, value.length));
268 } else {
290 } else {
269 return Promise.resolve(value);
291 return Promise.resolve(value);
270 }
292 }
271 },
293 },
272
294
273 on_some_change: function(keys, callback, context) {
295 on_some_change: function(keys, callback, context) {
274 // on_some_change(["key1", "key2"], foo, context) differs from
296 /**
275 // on("change:key1 change:key2", foo, context).
297 * on_some_change(["key1", "key2"], foo, context) differs from
276 // If the widget attributes key1 and key2 are both modified,
298 * on("change:key1 change:key2", foo, context).
277 // the second form will result in foo being called twice
299 * If the widget attributes key1 and key2 are both modified,
278 // while the first will call foo only once.
300 * the second form will result in foo being called twice
301 * while the first will call foo only once.
302 */
279 this.on('change', function() {
303 this.on('change', function() {
280 if (keys.some(this.hasChanged, this)) {
304 if (keys.some(this.hasChanged, this)) {
281 callback.apply(context);
305 callback.apply(context);
282 }
306 }
283 }, this);
307 }, this);
284
308
285 },
309 },
286 });
310 });
287 widgetmanager.WidgetManager.register_widget_model('WidgetModel', WidgetModel);
311 widgetmanager.WidgetManager.register_widget_model('WidgetModel', WidgetModel);
288
312
289
313
290 var WidgetView = Backbone.View.extend({
314 var WidgetView = Backbone.View.extend({
291 initialize: function(parameters) {
315 initialize: function(parameters) {
292 // Public constructor.
316 /**
317 * Public constructor.
318 */
293 this.model.on('change',this.update,this);
319 this.model.on('change',this.update,this);
294 this.options = parameters.options;
320 this.options = parameters.options;
295 this.id = this.id || utils.uuid();
321 this.id = this.id || utils.uuid();
296 this.model.views[this.id] = this;
322 this.model.views[this.id] = this;
297 this.on('displayed', function() {
323 this.on('displayed', function() {
298 this.is_displayed = true;
324 this.is_displayed = true;
299 }, this);
325 }, this);
300 },
326 },
301
327
302 update: function(){
328 update: function(){
303 // Triggered on model change.
329 /**
304 //
330 * Triggered on model change.
305 // Update view to be consistent with this.model
331 *
332 * Update view to be consistent with this.model
333 */
306 },
334 },
307
335
308 create_child_view: function(child_model, options) {
336 create_child_view: function(child_model, options) {
309 // Create and promise that resolves to a child view of a given model
337 /**
338 * Create and promise that resolves to a child view of a given model
339 */
310 var that = this;
340 var that = this;
311 options = $.extend({ parent: this }, options || {});
341 options = $.extend({ parent: this }, options || {});
312 return this.model.widget_manager.create_view(child_model, options).catch(utils.reject("Couldn't create child view"), true);
342 return this.model.widget_manager.create_view(child_model, options).catch(utils.reject("Couldn't create child view"), true);
313 },
343 },
314
344
315 callbacks: function(){
345 callbacks: function(){
316 // Create msg callbacks for a comm msg.
346 /**
347 * Create msg callbacks for a comm msg.
348 */
317 return this.model.callbacks(this);
349 return this.model.callbacks(this);
318 },
350 },
319
351
320 render: function(){
352 render: function(){
321 // Render the view.
353 /**
322 //
354 * Render the view.
323 // By default, this is only called the first time the view is created
355 *
356 * By default, this is only called the first time the view is created
357 */
324 },
358 },
325
359
326 show: function(){
360 show: function(){
327 // Show the widget-area
361 /**
362 * Show the widget-area
363 */
328 if (this.options && this.options.cell &&
364 if (this.options && this.options.cell &&
329 this.options.cell.widget_area !== undefined) {
365 this.options.cell.widget_area !== undefined) {
330 this.options.cell.widget_area.show();
366 this.options.cell.widget_area.show();
331 }
367 }
332 },
368 },
333
369
334 send: function (content) {
370 send: function (content) {
335 // Send a custom msg associated with this view.
371 /**
372 * Send a custom msg associated with this view.
373 */
336 this.model.send(content, this.callbacks());
374 this.model.send(content, this.callbacks());
337 },
375 },
338
376
339 touch: function () {
377 touch: function () {
340 this.model.save_changes(this.callbacks());
378 this.model.save_changes(this.callbacks());
341 },
379 },
342
380
343 after_displayed: function (callback, context) {
381 after_displayed: function (callback, context) {
344 // Calls the callback right away is the view is already displayed
382 /**
345 // otherwise, register the callback to the 'displayed' event.
383 * Calls the callback right away is the view is already displayed
384 * otherwise, register the callback to the 'displayed' event.
385 */
346 if (this.is_displayed) {
386 if (this.is_displayed) {
347 callback.apply(context);
387 callback.apply(context);
348 } else {
388 } else {
349 this.on('displayed', callback, context);
389 this.on('displayed', callback, context);
350 }
390 }
351 },
391 },
352 });
392 });
353
393
354
394
355 var DOMWidgetView = WidgetView.extend({
395 var DOMWidgetView = WidgetView.extend({
356 initialize: function (parameters) {
396 initialize: function (parameters) {
357 // Public constructor
397 /**
398 * Public constructor
399 */
358 DOMWidgetView.__super__.initialize.apply(this, [parameters]);
400 DOMWidgetView.__super__.initialize.apply(this, [parameters]);
359 this.on('displayed', this.show, this);
401 this.on('displayed', this.show, this);
360 this.model.on('change:visible', this.update_visible, this);
402 this.model.on('change:visible', this.update_visible, this);
361 this.model.on('change:_css', this.update_css, this);
403 this.model.on('change:_css', this.update_css, this);
362
404
363 this.model.on('change:_dom_classes', function(model, new_classes) {
405 this.model.on('change:_dom_classes', function(model, new_classes) {
364 var old_classes = model.previous('_dom_classes');
406 var old_classes = model.previous('_dom_classes');
365 this.update_classes(old_classes, new_classes);
407 this.update_classes(old_classes, new_classes);
366 }, this);
408 }, this);
367
409
368 this.model.on('change:color', function (model, value) {
410 this.model.on('change:color', function (model, value) {
369 this.update_attr('color', value); }, this);
411 this.update_attr('color', value); }, this);
370
412
371 this.model.on('change:background_color', function (model, value) {
413 this.model.on('change:background_color', function (model, value) {
372 this.update_attr('background', value); }, this);
414 this.update_attr('background', value); }, this);
373
415
374 this.model.on('change:width', function (model, value) {
416 this.model.on('change:width', function (model, value) {
375 this.update_attr('width', value); }, this);
417 this.update_attr('width', value); }, this);
376
418
377 this.model.on('change:height', function (model, value) {
419 this.model.on('change:height', function (model, value) {
378 this.update_attr('height', value); }, this);
420 this.update_attr('height', value); }, this);
379
421
380 this.model.on('change:border_color', function (model, value) {
422 this.model.on('change:border_color', function (model, value) {
381 this.update_attr('border-color', value); }, this);
423 this.update_attr('border-color', value); }, this);
382
424
383 this.model.on('change:border_width', function (model, value) {
425 this.model.on('change:border_width', function (model, value) {
384 this.update_attr('border-width', value); }, this);
426 this.update_attr('border-width', value); }, this);
385
427
386 this.model.on('change:border_style', function (model, value) {
428 this.model.on('change:border_style', function (model, value) {
387 this.update_attr('border-style', value); }, this);
429 this.update_attr('border-style', value); }, this);
388
430
389 this.model.on('change:font_style', function (model, value) {
431 this.model.on('change:font_style', function (model, value) {
390 this.update_attr('font-style', value); }, this);
432 this.update_attr('font-style', value); }, this);
391
433
392 this.model.on('change:font_weight', function (model, value) {
434 this.model.on('change:font_weight', function (model, value) {
393 this.update_attr('font-weight', value); }, this);
435 this.update_attr('font-weight', value); }, this);
394
436
395 this.model.on('change:font_size', function (model, value) {
437 this.model.on('change:font_size', function (model, value) {
396 this.update_attr('font-size', this._default_px(value)); }, this);
438 this.update_attr('font-size', this._default_px(value)); }, this);
397
439
398 this.model.on('change:font_family', function (model, value) {
440 this.model.on('change:font_family', function (model, value) {
399 this.update_attr('font-family', value); }, this);
441 this.update_attr('font-family', value); }, this);
400
442
401 this.model.on('change:padding', function (model, value) {
443 this.model.on('change:padding', function (model, value) {
402 this.update_attr('padding', value); }, this);
444 this.update_attr('padding', value); }, this);
403
445
404 this.model.on('change:margin', function (model, value) {
446 this.model.on('change:margin', function (model, value) {
405 this.update_attr('margin', this._default_px(value)); }, this);
447 this.update_attr('margin', this._default_px(value)); }, this);
406
448
407 this.model.on('change:border_radius', function (model, value) {
449 this.model.on('change:border_radius', function (model, value) {
408 this.update_attr('border-radius', this._default_px(value)); }, this);
450 this.update_attr('border-radius', this._default_px(value)); }, this);
409
451
410 this.after_displayed(function() {
452 this.after_displayed(function() {
411 this.update_visible(this.model, this.model.get("visible"));
453 this.update_visible(this.model, this.model.get("visible"));
412 this.update_classes([], this.model.get('_dom_classes'));
454 this.update_classes([], this.model.get('_dom_classes'));
413
455
414 this.update_attr('color', this.model.get('color'));
456 this.update_attr('color', this.model.get('color'));
415 this.update_attr('background', this.model.get('background_color'));
457 this.update_attr('background', this.model.get('background_color'));
416 this.update_attr('width', this.model.get('width'));
458 this.update_attr('width', this.model.get('width'));
417 this.update_attr('height', this.model.get('height'));
459 this.update_attr('height', this.model.get('height'));
418 this.update_attr('border-color', this.model.get('border_color'));
460 this.update_attr('border-color', this.model.get('border_color'));
419 this.update_attr('border-width', this.model.get('border_width'));
461 this.update_attr('border-width', this.model.get('border_width'));
420 this.update_attr('border-style', this.model.get('border_style'));
462 this.update_attr('border-style', this.model.get('border_style'));
421 this.update_attr('font-style', this.model.get('font_style'));
463 this.update_attr('font-style', this.model.get('font_style'));
422 this.update_attr('font-weight', this.model.get('font_weight'));
464 this.update_attr('font-weight', this.model.get('font_weight'));
423 this.update_attr('font-size', this.model.get('font_size'));
465 this.update_attr('font-size', this.model.get('font_size'));
424 this.update_attr('font-family', this.model.get('font_family'));
466 this.update_attr('font-family', this.model.get('font_family'));
425 this.update_attr('padding', this.model.get('padding'));
467 this.update_attr('padding', this.model.get('padding'));
426 this.update_attr('margin', this.model.get('margin'));
468 this.update_attr('margin', this.model.get('margin'));
427 this.update_attr('border-radius', this.model.get('border_radius'));
469 this.update_attr('border-radius', this.model.get('border_radius'));
428
470
429 this.update_css(this.model, this.model.get("_css"));
471 this.update_css(this.model, this.model.get("_css"));
430 }, this);
472 }, this);
431 },
473 },
432
474
433 _default_px: function(value) {
475 _default_px: function(value) {
434 // Makes browser interpret a numerical string as a pixel value.
476 /**
477 * Makes browser interpret a numerical string as a pixel value.
478 */
435 if (/^\d+\.?(\d+)?$/.test(value.trim())) {
479 if (/^\d+\.?(\d+)?$/.test(value.trim())) {
436 return value.trim() + 'px';
480 return value.trim() + 'px';
437 }
481 }
438 return value;
482 return value;
439 },
483 },
440
484
441 update_attr: function(name, value) {
485 update_attr: function(name, value) {
442 // Set a css attr of the widget view.
486 /**
487 * Set a css attr of the widget view.
488 */
443 this.$el.css(name, value);
489 this.$el.css(name, value);
444 },
490 },
445
491
446 update_visible: function(model, value) {
492 update_visible: function(model, value) {
447 // Update visibility
493 /**
494 * Update visibility
495 */
448 this.$el.toggle(value);
496 this.$el.toggle(value);
449 },
497 },
450
498
451 update_css: function (model, css) {
499 update_css: function (model, css) {
452 // Update the css styling of this view.
500 /**
501 * Update the css styling of this view.
502 */
453 var e = this.$el;
503 var e = this.$el;
454 if (css === undefined) {return;}
504 if (css === undefined) {return;}
455 for (var i = 0; i < css.length; i++) {
505 for (var i = 0; i < css.length; i++) {
456 // Apply the css traits to all elements that match the selector.
506 // Apply the css traits to all elements that match the selector.
457 var selector = css[i][0];
507 var selector = css[i][0];
458 var elements = this._get_selector_element(selector);
508 var elements = this._get_selector_element(selector);
459 if (elements.length > 0) {
509 if (elements.length > 0) {
460 var trait_key = css[i][1];
510 var trait_key = css[i][1];
461 var trait_value = css[i][2];
511 var trait_value = css[i][2];
462 elements.css(trait_key ,trait_value);
512 elements.css(trait_key ,trait_value);
463 }
513 }
464 }
514 }
465 },
515 },
466
516
467 update_classes: function (old_classes, new_classes, $el) {
517 update_classes: function (old_classes, new_classes, $el) {
468 // Update the DOM classes applied to an element, default to this.$el.
518 /**
519 * Update the DOM classes applied to an element, default to this.$el.
520 */
469 if ($el===undefined) {
521 if ($el===undefined) {
470 $el = this.$el;
522 $el = this.$el;
471 }
523 }
472 _.difference(old_classes, new_classes).map(function(c) {$el.removeClass(c);})
524 _.difference(old_classes, new_classes).map(function(c) {$el.removeClass(c);})
473 _.difference(new_classes, old_classes).map(function(c) {$el.addClass(c);})
525 _.difference(new_classes, old_classes).map(function(c) {$el.addClass(c);})
474 },
526 },
475
527
476 update_mapped_classes: function(class_map, trait_name, previous_trait_value, $el) {
528 update_mapped_classes: function(class_map, trait_name, previous_trait_value, $el) {
477 // Update the DOM classes applied to the widget based on a single
529 /**
478 // trait's value.
530 * Update the DOM classes applied to the widget based on a single
479 //
531 * trait's value.
480 // Given a trait value classes map, this function automatically
532 *
481 // handles applying the appropriate classes to the widget element
533 * Given a trait value classes map, this function automatically
482 // and removing classes that are no longer valid.
534 * handles applying the appropriate classes to the widget element
483 //
535 * and removing classes that are no longer valid.
484 // Parameters
536 *
485 // ----------
537 * Parameters
486 // class_map: dictionary
538 * ----------
487 // Dictionary of trait values to class lists.
539 * class_map: dictionary
488 // Example:
540 * Dictionary of trait values to class lists.
489 // {
541 * Example:
490 // success: ['alert', 'alert-success'],
542 * {
491 // info: ['alert', 'alert-info'],
543 * success: ['alert', 'alert-success'],
492 // warning: ['alert', 'alert-warning'],
544 * info: ['alert', 'alert-info'],
493 // danger: ['alert', 'alert-danger']
545 * warning: ['alert', 'alert-warning'],
494 // };
546 * danger: ['alert', 'alert-danger']
495 // trait_name: string
547 * };
496 // Name of the trait to check the value of.
548 * trait_name: string
497 // previous_trait_value: optional string, default ''
549 * Name of the trait to check the value of.
498 // Last trait value
550 * previous_trait_value: optional string, default ''
499 // $el: optional jQuery element handle, defaults to this.$el
551 * Last trait value
500 // Element that the classes are applied to.
552 * $el: optional jQuery element handle, defaults to this.$el
553 * Element that the classes are applied to.
554 */
501 var key = previous_trait_value;
555 var key = previous_trait_value;
502 if (key === undefined) {
556 if (key === undefined) {
503 key = this.model.previous(trait_name);
557 key = this.model.previous(trait_name);
504 }
558 }
505 var old_classes = class_map[key] ? class_map[key] : [];
559 var old_classes = class_map[key] ? class_map[key] : [];
506 key = this.model.get(trait_name);
560 key = this.model.get(trait_name);
507 var new_classes = class_map[key] ? class_map[key] : [];
561 var new_classes = class_map[key] ? class_map[key] : [];
508
562
509 this.update_classes(old_classes, new_classes, $el || this.$el);
563 this.update_classes(old_classes, new_classes, $el || this.$el);
510 },
564 },
511
565
512 _get_selector_element: function (selector) {
566 _get_selector_element: function (selector) {
513 // Get the elements via the css selector.
567 /**
568 * Get the elements via the css selector.
569 */
514 var elements;
570 var elements;
515 if (!selector) {
571 if (!selector) {
516 elements = this.$el;
572 elements = this.$el;
517 } else {
573 } else {
518 elements = this.$el.find(selector).addBack(selector);
574 elements = this.$el.find(selector).addBack(selector);
519 }
575 }
520 return elements;
576 return elements;
521 },
577 },
522 });
578 });
523
579
524
580
525 var ViewList = function(create_view, remove_view, context) {
581 var ViewList = function(create_view, remove_view, context) {
526 // * create_view and remove_view are default functions called when adding or removing views
582 /**
527 // * create_view takes a model and returns a view or a promise for a view for that model
583 * * create_view and remove_view are default functions called when adding or removing views
528 // * remove_view takes a view and destroys it (including calling `view.remove()`)
584 * * create_view takes a model and returns a view or a promise for a view for that model
529 // * each time the update() function is called with a new list, the create and remove
585 * * remove_view takes a view and destroys it (including calling `view.remove()`)
530 // callbacks will be called in an order so that if you append the views created in the
586 * * each time the update() function is called with a new list, the create and remove
531 // create callback and remove the views in the remove callback, you will duplicate
587 * callbacks will be called in an order so that if you append the views created in the
532 // the order of the list.
588 * create callback and remove the views in the remove callback, you will duplicate
533 // * the remove callback defaults to just removing the view (e.g., pass in null for the second parameter)
589 * the order of the list.
534 // * the context defaults to the created ViewList. If you pass another context, the create and remove
590 * * the remove callback defaults to just removing the view (e.g., pass in null for the second parameter)
535 // will be called in that context.
591 * * the context defaults to the created ViewList. If you pass another context, the create and remove
592 * will be called in that context.
593 */
536
594
537 this.initialize.apply(this, arguments);
595 this.initialize.apply(this, arguments);
538 };
596 };
539
597
540 _.extend(ViewList.prototype, {
598 _.extend(ViewList.prototype, {
541 initialize: function(create_view, remove_view, context) {
599 initialize: function(create_view, remove_view, context) {
542 this.state_change = Promise.resolve();
600 this.state_change = Promise.resolve();
543 this._handler_context = context || this;
601 this._handler_context = context || this;
544 this._models = [];
602 this._models = [];
545 this.views = [];
603 this.views = [];
546 this._create_view = create_view;
604 this._create_view = create_view;
547 this._remove_view = remove_view || function(view) {view.remove();};
605 this._remove_view = remove_view || function(view) {view.remove();};
548 },
606 },
549
607
550 update: function(new_models, create_view, remove_view, context) {
608 update: function(new_models, create_view, remove_view, context) {
551 // the create_view, remove_view, and context arguments override the defaults
609 /**
552 // specified when the list is created.
610 * the create_view, remove_view, and context arguments override the defaults
553 // returns a promise that resolves after this update is done
611 * specified when the list is created.
612 * returns a promise that resolves after this update is done
613 */
554 var remove = remove_view || this._remove_view;
614 var remove = remove_view || this._remove_view;
555 var create = create_view || this._create_view;
615 var create = create_view || this._create_view;
556 if (create === undefined || remove === undefined){
616 if (create === undefined || remove === undefined){
557 console.error("Must define a create a remove function");
617 console.error("Must define a create a remove function");
558 }
618 }
559 var context = context || this._handler_context;
619 var context = context || this._handler_context;
560 var added_views = [];
620 var added_views = [];
561 var that = this;
621 var that = this;
562 this.state_change = this.state_change.then(function() {
622 this.state_change = this.state_change.then(function() {
563 var i;
623 var i;
564 // first, skip past the beginning of the lists if they are identical
624 // first, skip past the beginning of the lists if they are identical
565 for (i = 0; i < new_models.length; i++) {
625 for (i = 0; i < new_models.length; i++) {
566 if (i >= that._models.length || new_models[i] !== that._models[i]) {
626 if (i >= that._models.length || new_models[i] !== that._models[i]) {
567 break;
627 break;
568 }
628 }
569 }
629 }
570 var first_removed = i;
630 var first_removed = i;
571 // Remove the non-matching items from the old list.
631 // Remove the non-matching items from the old list.
572 for (var j = first_removed; j < that._models.length; j++) {
632 for (var j = first_removed; j < that._models.length; j++) {
573 remove.call(context, that.views[j]);
633 remove.call(context, that.views[j]);
574 }
634 }
575
635
576 // Add the rest of the new list items.
636 // Add the rest of the new list items.
577 for (; i < new_models.length; i++) {
637 for (; i < new_models.length; i++) {
578 added_views.push(create.call(context, new_models[i]));
638 added_views.push(create.call(context, new_models[i]));
579 }
639 }
580 // make a copy of the input array
640 // make a copy of the input array
581 that._models = new_models.slice();
641 that._models = new_models.slice();
582 return Promise.all(added_views).then(function(added) {
642 return Promise.all(added_views).then(function(added) {
583 Array.prototype.splice.apply(that.views, [first_removed, that.views.length].concat(added));
643 Array.prototype.splice.apply(that.views, [first_removed, that.views.length].concat(added));
584 return that.views;
644 return that.views;
585 });
645 });
586 });
646 });
587 return this.state_change;
647 return this.state_change;
588 },
648 },
589
649
590 remove: function() {
650 remove: function() {
591 // removes every view in the list; convenience function for `.update([])`
651 /**
592 // that should be faster
652 * removes every view in the list; convenience function for `.update([])`
593 // returns a promise that resolves after this removal is done
653 * that should be faster
654 * returns a promise that resolves after this removal is done
655 */
594 var that = this;
656 var that = this;
595 this.state_change = this.state_change.then(function() {
657 this.state_change = this.state_change.then(function() {
596 for (var i = 0; i < that.views.length; i++) {
658 for (var i = 0; i < that.views.length; i++) {
597 that._remove_view.call(that._handler_context, that.views[i]);
659 that._remove_view.call(that._handler_context, that.views[i]);
598 }
660 }
599 that._models = [];
661 that._models = [];
600 that.views = [];
662 that.views = [];
601 });
663 });
602 return this.state_change;
664 return this.state_change;
603 },
665 },
604 });
666 });
605
667
606 var widget = {
668 var widget = {
607 'WidgetModel': WidgetModel,
669 'WidgetModel': WidgetModel,
608 'WidgetView': WidgetView,
670 'WidgetView': WidgetView,
609 'DOMWidgetView': DOMWidgetView,
671 'DOMWidgetView': DOMWidgetView,
610 'ViewList': ViewList,
672 'ViewList': ViewList,
611 };
673 };
612
674
613 // For backwards compatability.
675 // For backwards compatability.
614 $.extend(IPython, widget);
676 $.extend(IPython, widget);
615
677
616 return widget;
678 return widget;
617 });
679 });
@@ -1,141 +1,155 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 "widgets/js/widget",
5 "widgets/js/widget",
6 "jquery",
6 "jquery",
7 "bootstrap",
7 "bootstrap",
8 ], function(widget, $){
8 ], function(widget, $){
9
9
10 var CheckboxView = widget.DOMWidgetView.extend({
10 var CheckboxView = widget.DOMWidgetView.extend({
11 render : function(){
11 render : function(){
12 // Called when view is rendered.
12 /**
13 * Called when view is rendered.
14 */
13 this.$el
15 this.$el
14 .addClass('widget-hbox widget-checkbox');
16 .addClass('widget-hbox widget-checkbox');
15 this.$label = $('<div />')
17 this.$label = $('<div />')
16 .addClass('widget-label')
18 .addClass('widget-label')
17 .appendTo(this.$el)
19 .appendTo(this.$el)
18 .hide();
20 .hide();
19 this.$checkbox = $('<input />')
21 this.$checkbox = $('<input />')
20 .attr('type', 'checkbox')
22 .attr('type', 'checkbox')
21 .appendTo(this.$el)
23 .appendTo(this.$el)
22 .click($.proxy(this.handle_click, this));
24 .click($.proxy(this.handle_click, this));
23
25
24 this.update(); // Set defaults.
26 this.update(); // Set defaults.
25 },
27 },
26
28
27 update_attr: function(name, value) {
29 update_attr: function(name, value) {
28 // Set a css attr of the widget view.
30 /**
31 * Set a css attr of the widget view.
32 */
29 this.$checkbox.css(name, value);
33 this.$checkbox.css(name, value);
30 },
34 },
31
35
32 handle_click: function() {
36 handle_click: function() {
33 // Handles when the checkbox is clicked.
37 /**
34
38 * Handles when the checkbox is clicked.
35 // Calling model.set will trigger all of the other views of the
39 *
36 // model to update.
40 * Calling model.set will trigger all of the other views of the
41 * model to update.
42 */
37 var value = this.model.get('value');
43 var value = this.model.get('value');
38 this.model.set('value', ! value, {updated_view: this});
44 this.model.set('value', ! value, {updated_view: this});
39 this.touch();
45 this.touch();
40 },
46 },
41
47
42 update : function(options){
48 update : function(options){
43 // Update the contents of this view
49 /**
44 //
50 * Update the contents of this view
45 // Called when the model is changed. The model may have been
51 *
46 // changed by another view or by a state update from the back-end.
52 * Called when the model is changed. The model may have been
53 * changed by another view or by a state update from the back-end.
54 */
47 this.$checkbox.prop('checked', this.model.get('value'));
55 this.$checkbox.prop('checked', this.model.get('value'));
48
56
49 if (options === undefined || options.updated_view != this) {
57 if (options === undefined || options.updated_view != this) {
50 var disabled = this.model.get('disabled');
58 var disabled = this.model.get('disabled');
51 this.$checkbox.prop('disabled', disabled);
59 this.$checkbox.prop('disabled', disabled);
52
60
53 var description = this.model.get('description');
61 var description = this.model.get('description');
54 if (description.trim().length === 0) {
62 if (description.trim().length === 0) {
55 this.$label.hide();
63 this.$label.hide();
56 } else {
64 } else {
57 this.$label.text(description);
65 this.$label.text(description);
58 MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.$label.get(0)]);
66 MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.$label.get(0)]);
59 this.$label.show();
67 this.$label.show();
60 }
68 }
61 }
69 }
62 return CheckboxView.__super__.update.apply(this);
70 return CheckboxView.__super__.update.apply(this);
63 },
71 },
64
72
65 });
73 });
66
74
67
75
68 var ToggleButtonView = widget.DOMWidgetView.extend({
76 var ToggleButtonView = widget.DOMWidgetView.extend({
69 render : function() {
77 render : function() {
70 // Called when view is rendered.
78 /**
79 * Called when view is rendered.
80 */
71 var that = this;
81 var that = this;
72 this.setElement($('<button />')
82 this.setElement($('<button />')
73 .addClass('btn btn-default')
83 .addClass('btn btn-default')
74 .attr('type', 'button')
84 .attr('type', 'button')
75 .on('click', function (e) {
85 .on('click', function (e) {
76 e.preventDefault();
86 e.preventDefault();
77 that.handle_click();
87 that.handle_click();
78 }));
88 }));
79 this.$el.attr("data-toggle", "tooltip");
89 this.$el.attr("data-toggle", "tooltip");
80 this.model.on('change:button_style', function(model, value) {
90 this.model.on('change:button_style', function(model, value) {
81 this.update_button_style();
91 this.update_button_style();
82 }, this);
92 }, this);
83 this.update_button_style('');
93 this.update_button_style('');
84
94
85 this.update(); // Set defaults.
95 this.update(); // Set defaults.
86 },
96 },
87
97
88 update_button_style: function(previous_trait_value) {
98 update_button_style: function(previous_trait_value) {
89 var class_map = {
99 var class_map = {
90 primary: ['btn-primary'],
100 primary: ['btn-primary'],
91 success: ['btn-success'],
101 success: ['btn-success'],
92 info: ['btn-info'],
102 info: ['btn-info'],
93 warning: ['btn-warning'],
103 warning: ['btn-warning'],
94 danger: ['btn-danger']
104 danger: ['btn-danger']
95 };
105 };
96 this.update_mapped_classes(class_map, 'button_style', previous_trait_value);
106 this.update_mapped_classes(class_map, 'button_style', previous_trait_value);
97 },
107 },
98
108
99 update : function(options){
109 update : function(options){
100 // Update the contents of this view
110 /**
101 //
111 * Update the contents of this view
102 // Called when the model is changed. The model may have been
112 *
103 // changed by another view or by a state update from the back-end.
113 * Called when the model is changed. The model may have been
114 * changed by another view or by a state update from the back-end.
115 */
104 if (this.model.get('value')) {
116 if (this.model.get('value')) {
105 this.$el.addClass('active');
117 this.$el.addClass('active');
106 } else {
118 } else {
107 this.$el.removeClass('active');
119 this.$el.removeClass('active');
108 }
120 }
109
121
110 if (options === undefined || options.updated_view != this) {
122 if (options === undefined || options.updated_view != this) {
111
123
112 var disabled = this.model.get('disabled');
124 var disabled = this.model.get('disabled');
113 this.$el.prop('disabled', disabled);
125 this.$el.prop('disabled', disabled);
114
126
115 var description = this.model.get('description');
127 var description = this.model.get('description');
116 this.$el.attr("title", this.model.get("tooltip"));
128 this.$el.attr("title", this.model.get("tooltip"));
117 if (description.trim().length === 0) {
129 if (description.trim().length === 0) {
118 this.$el.html("&nbsp;"); // Preserve button height
130 this.$el.html("&nbsp;"); // Preserve button height
119 } else {
131 } else {
120 this.$el.text(description);
132 this.$el.text(description);
121 }
133 }
122 }
134 }
123 return ToggleButtonView.__super__.update.apply(this);
135 return ToggleButtonView.__super__.update.apply(this);
124 },
136 },
125
137
126 handle_click: function(e) {
138 handle_click: function(e) {
127 // Handles and validates user input.
139 /**
128
140 * Handles and validates user input.
129 // Calling model.set will trigger all of the other views of the
141 *
130 // model to update.
142 * Calling model.set will trigger all of the other views of the
143 * model to update.
144 */
131 var value = this.model.get('value');
145 var value = this.model.get('value');
132 this.model.set('value', ! value, {updated_view: this});
146 this.model.set('value', ! value, {updated_view: this});
133 this.touch();
147 this.touch();
134 },
148 },
135 });
149 });
136
150
137 return {
151 return {
138 'CheckboxView': CheckboxView,
152 'CheckboxView': CheckboxView,
139 'ToggleButtonView': ToggleButtonView,
153 'ToggleButtonView': ToggleButtonView,
140 };
154 };
141 });
155 });
@@ -1,344 +1,370 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 "widgets/js/widget",
5 "widgets/js/widget",
6 "jqueryui",
6 "jqueryui",
7 "base/js/utils",
7 "base/js/utils",
8 "bootstrap",
8 "bootstrap",
9 ], function(widget, $, utils){
9 ], function(widget, $, utils){
10
10
11 var BoxView = widget.DOMWidgetView.extend({
11 var BoxView = widget.DOMWidgetView.extend({
12 initialize: function(){
12 initialize: function(){
13 // Public constructor
13 /**
14 * Public constructor
15 */
14 BoxView.__super__.initialize.apply(this, arguments);
16 BoxView.__super__.initialize.apply(this, arguments);
15 this.children_views = new widget.ViewList(this.add_child_model, null, this);
17 this.children_views = new widget.ViewList(this.add_child_model, null, this);
16 this.listenTo(this.model, 'change:children', function(model, value) {
18 this.listenTo(this.model, 'change:children', function(model, value) {
17 this.children_views.update(value);
19 this.children_views.update(value);
18 }, this);
20 }, this);
19 this.listenTo(this.model, 'change:overflow_x', function(model, value) {
21 this.listenTo(this.model, 'change:overflow_x', function(model, value) {
20 this.update_overflow_x();
22 this.update_overflow_x();
21 }, this);
23 }, this);
22 this.listenTo(this.model, 'change:overflow_y', function(model, value) {
24 this.listenTo(this.model, 'change:overflow_y', function(model, value) {
23 this.update_overflow_y();
25 this.update_overflow_y();
24 }, this);
26 }, this);
25 this.listenTo(this.model, 'change:box_style', function(model, value) {
27 this.listenTo(this.model, 'change:box_style', function(model, value) {
26 this.update_box_style();
28 this.update_box_style();
27 }, this);
29 }, this);
28 },
30 },
29
31
30 update_attr: function(name, value) {
32 update_attr: function(name, value) {
31 // Set a css attr of the widget view.
33 /**
34 * Set a css attr of the widget view.
35 */
32 this.$box.css(name, value);
36 this.$box.css(name, value);
33 },
37 },
34
38
35 render: function(){
39 render: function(){
36 // Called when view is rendered.
40 /**
41 * Called when view is rendered.
42 */
37 this.$box = this.$el;
43 this.$box = this.$el;
38 this.$box.addClass('widget-box');
44 this.$box.addClass('widget-box');
39 this.children_views.update(this.model.get('children'));
45 this.children_views.update(this.model.get('children'));
40 this.update_overflow_x();
46 this.update_overflow_x();
41 this.update_overflow_y();
47 this.update_overflow_y();
42 this.update_box_style('');
48 this.update_box_style('');
43 },
49 },
44
50
45 update_overflow_x: function() {
51 update_overflow_x: function() {
46 // Called when the x-axis overflow setting is changed.
52 /**
53 * Called when the x-axis overflow setting is changed.
54 */
47 this.$box.css('overflow-x', this.model.get('overflow_x'));
55 this.$box.css('overflow-x', this.model.get('overflow_x'));
48 },
56 },
49
57
50 update_overflow_y: function() {
58 update_overflow_y: function() {
51 // Called when the y-axis overflow setting is changed.
59 /**
60 * Called when the y-axis overflow setting is changed.
61 */
52 this.$box.css('overflow-y', this.model.get('overflow_y'));
62 this.$box.css('overflow-y', this.model.get('overflow_y'));
53 },
63 },
54
64
55 update_box_style: function(previous_trait_value) {
65 update_box_style: function(previous_trait_value) {
56 var class_map = {
66 var class_map = {
57 success: ['alert', 'alert-success'],
67 success: ['alert', 'alert-success'],
58 info: ['alert', 'alert-info'],
68 info: ['alert', 'alert-info'],
59 warning: ['alert', 'alert-warning'],
69 warning: ['alert', 'alert-warning'],
60 danger: ['alert', 'alert-danger']
70 danger: ['alert', 'alert-danger']
61 };
71 };
62 this.update_mapped_classes(class_map, 'box_style', previous_trait_value, this.$box);
72 this.update_mapped_classes(class_map, 'box_style', previous_trait_value, this.$box);
63 },
73 },
64
74
65 add_child_model: function(model) {
75 add_child_model: function(model) {
66 // Called when a model is added to the children list.
76 /**
77 * Called when a model is added to the children list.
78 */
67 var that = this;
79 var that = this;
68 var dummy = $('<div/>');
80 var dummy = $('<div/>');
69 that.$box.append(dummy);
81 that.$box.append(dummy);
70 return this.create_child_view(model).then(function(view) {
82 return this.create_child_view(model).then(function(view) {
71 dummy.replaceWith(view.el);
83 dummy.replaceWith(view.el);
72
84
73 // Trigger the displayed event of the child view.
85 // Trigger the displayed event of the child view.
74 that.after_displayed(function() {
86 that.after_displayed(function() {
75 view.trigger('displayed');
87 view.trigger('displayed');
76 });
88 });
77 return view;
89 return view;
78 }).catch(utils.reject("Couldn't add child view to box", true));
90 }).catch(utils.reject("Couldn't add child view to box", true));
79 },
91 },
80
92
81 remove: function() {
93 remove: function() {
82 // We remove this widget before removing the children as an optimization
94 /**
83 // we want to remove the entire container from the DOM first before
95 * We remove this widget before removing the children as an optimization
84 // removing each individual child separately.
96 * we want to remove the entire container from the DOM first before
97 * removing each individual child separately.
98 */
85 BoxView.__super__.remove.apply(this, arguments);
99 BoxView.__super__.remove.apply(this, arguments);
86 this.children_views.remove();
100 this.children_views.remove();
87 },
101 },
88 });
102 });
89
103
90
104
91 var FlexBoxView = BoxView.extend({
105 var FlexBoxView = BoxView.extend({
92 render: function(){
106 render: function(){
93 FlexBoxView.__super__.render.apply(this);
107 FlexBoxView.__super__.render.apply(this);
94 this.listenTo(this.model, 'change:orientation', this.update_orientation, this);
108 this.listenTo(this.model, 'change:orientation', this.update_orientation, this);
95 this.listenTo(this.model, 'change:flex', this._flex_changed, this);
109 this.listenTo(this.model, 'change:flex', this._flex_changed, this);
96 this.listenTo(this.model, 'change:pack', this._pack_changed, this);
110 this.listenTo(this.model, 'change:pack', this._pack_changed, this);
97 this.listenTo(this.model, 'change:align', this._align_changed, this);
111 this.listenTo(this.model, 'change:align', this._align_changed, this);
98 this._flex_changed();
112 this._flex_changed();
99 this._pack_changed();
113 this._pack_changed();
100 this._align_changed();
114 this._align_changed();
101 this.update_orientation();
115 this.update_orientation();
102 },
116 },
103
117
104 update_orientation: function(){
118 update_orientation: function(){
105 var orientation = this.model.get("orientation");
119 var orientation = this.model.get("orientation");
106 if (orientation == "vertical") {
120 if (orientation == "vertical") {
107 this.$box.removeClass("hbox").addClass("vbox");
121 this.$box.removeClass("hbox").addClass("vbox");
108 } else {
122 } else {
109 this.$box.removeClass("vbox").addClass("hbox");
123 this.$box.removeClass("vbox").addClass("hbox");
110 }
124 }
111 },
125 },
112
126
113 _flex_changed: function(){
127 _flex_changed: function(){
114 if (this.model.previous('flex')) {
128 if (this.model.previous('flex')) {
115 this.$box.removeClass('box-flex' + this.model.previous('flex'));
129 this.$box.removeClass('box-flex' + this.model.previous('flex'));
116 }
130 }
117 this.$box.addClass('box-flex' + this.model.get('flex'));
131 this.$box.addClass('box-flex' + this.model.get('flex'));
118 },
132 },
119
133
120 _pack_changed: function(){
134 _pack_changed: function(){
121 if (this.model.previous('pack')) {
135 if (this.model.previous('pack')) {
122 this.$box.removeClass(this.model.previous('pack'));
136 this.$box.removeClass(this.model.previous('pack'));
123 }
137 }
124 this.$box.addClass(this.model.get('pack'));
138 this.$box.addClass(this.model.get('pack'));
125 },
139 },
126
140
127 _align_changed: function(){
141 _align_changed: function(){
128 if (this.model.previous('align')) {
142 if (this.model.previous('align')) {
129 this.$box.removeClass('align-' + this.model.previous('align'));
143 this.$box.removeClass('align-' + this.model.previous('align'));
130 }
144 }
131 this.$box.addClass('align-' + this.model.get('align'));
145 this.$box.addClass('align-' + this.model.get('align'));
132 },
146 },
133 });
147 });
134
148
135 var PopupView = BoxView.extend({
149 var PopupView = BoxView.extend({
136
150
137 render: function(){
151 render: function(){
138 // Called when view is rendered.
152 /**
153 * Called when view is rendered.
154 */
139 var that = this;
155 var that = this;
140
156
141 this.$el.on("remove", function(){
157 this.$el.on("remove", function(){
142 that.$backdrop.remove();
158 that.$backdrop.remove();
143 });
159 });
144 this.$backdrop = $('<div />')
160 this.$backdrop = $('<div />')
145 .appendTo($('#notebook-container'))
161 .appendTo($('#notebook-container'))
146 .addClass('modal-dialog')
162 .addClass('modal-dialog')
147 .css('position', 'absolute')
163 .css('position', 'absolute')
148 .css('left', '0px')
164 .css('left', '0px')
149 .css('top', '0px');
165 .css('top', '0px');
150 this.$window = $('<div />')
166 this.$window = $('<div />')
151 .appendTo(this.$backdrop)
167 .appendTo(this.$backdrop)
152 .addClass('modal-content widget-modal')
168 .addClass('modal-content widget-modal')
153 .mousedown(function(){
169 .mousedown(function(){
154 that.bring_to_front();
170 that.bring_to_front();
155 });
171 });
156
172
157 // Set the elements array since the this.$window element is not child
173 // Set the elements array since the this.$window element is not child
158 // of this.$el and the parent widget manager or other widgets may
174 // of this.$el and the parent widget manager or other widgets may
159 // need to know about all of the top-level widgets. The IPython
175 // need to know about all of the top-level widgets. The IPython
160 // widget manager uses this to register the elements with the
176 // widget manager uses this to register the elements with the
161 // keyboard manager.
177 // keyboard manager.
162 this.additional_elements = [this.$window];
178 this.additional_elements = [this.$window];
163
179
164 this.$title_bar = $('<div />')
180 this.$title_bar = $('<div />')
165 .addClass('popover-title')
181 .addClass('popover-title')
166 .appendTo(this.$window)
182 .appendTo(this.$window)
167 .mousedown(function(){
183 .mousedown(function(){
168 that.bring_to_front();
184 that.bring_to_front();
169 });
185 });
170 this.$close = $('<button />')
186 this.$close = $('<button />')
171 .addClass('close fa fa-remove')
187 .addClass('close fa fa-remove')
172 .css('margin-left', '5px')
188 .css('margin-left', '5px')
173 .appendTo(this.$title_bar)
189 .appendTo(this.$title_bar)
174 .click(function(){
190 .click(function(){
175 that.hide();
191 that.hide();
176 event.stopPropagation();
192 event.stopPropagation();
177 });
193 });
178 this.$minimize = $('<button />')
194 this.$minimize = $('<button />')
179 .addClass('close fa fa-arrow-down')
195 .addClass('close fa fa-arrow-down')
180 .appendTo(this.$title_bar)
196 .appendTo(this.$title_bar)
181 .click(function(){
197 .click(function(){
182 that.popped_out = !that.popped_out;
198 that.popped_out = !that.popped_out;
183 if (!that.popped_out) {
199 if (!that.popped_out) {
184 that.$minimize
200 that.$minimize
185 .removeClass('fa-arrow-down')
201 .removeClass('fa-arrow-down')
186 .addClass('fa-arrow-up');
202 .addClass('fa-arrow-up');
187
203
188 that.$window
204 that.$window
189 .draggable('destroy')
205 .draggable('destroy')
190 .resizable('destroy')
206 .resizable('destroy')
191 .removeClass('widget-modal modal-content')
207 .removeClass('widget-modal modal-content')
192 .addClass('docked-widget-modal')
208 .addClass('docked-widget-modal')
193 .detach()
209 .detach()
194 .insertBefore(that.$show_button);
210 .insertBefore(that.$show_button);
195 that.$show_button.hide();
211 that.$show_button.hide();
196 that.$close.hide();
212 that.$close.hide();
197 } else {
213 } else {
198 that.$minimize
214 that.$minimize
199 .addClass('fa-arrow-down')
215 .addClass('fa-arrow-down')
200 .removeClass('fa-arrow-up');
216 .removeClass('fa-arrow-up');
201
217
202 that.$window
218 that.$window
203 .removeClass('docked-widget-modal')
219 .removeClass('docked-widget-modal')
204 .addClass('widget-modal modal-content')
220 .addClass('widget-modal modal-content')
205 .detach()
221 .detach()
206 .appendTo(that.$backdrop)
222 .appendTo(that.$backdrop)
207 .draggable({handle: '.popover-title', snap: '#notebook, .modal', snapMode: 'both'})
223 .draggable({handle: '.popover-title', snap: '#notebook, .modal', snapMode: 'both'})
208 .resizable()
224 .resizable()
209 .children('.ui-resizable-handle').show();
225 .children('.ui-resizable-handle').show();
210 that.show();
226 that.show();
211 that.$show_button.show();
227 that.$show_button.show();
212 that.$close.show();
228 that.$close.show();
213 }
229 }
214 event.stopPropagation();
230 event.stopPropagation();
215 });
231 });
216 this.$title = $('<div />')
232 this.$title = $('<div />')
217 .addClass('widget-modal-title')
233 .addClass('widget-modal-title')
218 .html("&nbsp;")
234 .html("&nbsp;")
219 .appendTo(this.$title_bar);
235 .appendTo(this.$title_bar);
220 this.$box = $('<div />')
236 this.$box = $('<div />')
221 .addClass('modal-body')
237 .addClass('modal-body')
222 .addClass('widget-modal-body')
238 .addClass('widget-modal-body')
223 .addClass('widget-box')
239 .addClass('widget-box')
224 .addClass('vbox')
240 .addClass('vbox')
225 .appendTo(this.$window);
241 .appendTo(this.$window);
226
242
227 this.$show_button = $('<button />')
243 this.$show_button = $('<button />')
228 .html("&nbsp;")
244 .html("&nbsp;")
229 .addClass('btn btn-info widget-modal-show')
245 .addClass('btn btn-info widget-modal-show')
230 .appendTo(this.$el)
246 .appendTo(this.$el)
231 .click(function(){
247 .click(function(){
232 that.show();
248 that.show();
233 });
249 });
234
250
235 this.$window.draggable({handle: '.popover-title', snap: '#notebook, .modal', snapMode: 'both'});
251 this.$window.draggable({handle: '.popover-title', snap: '#notebook, .modal', snapMode: 'both'});
236 this.$window.resizable();
252 this.$window.resizable();
237 this.$window.on('resize', function(){
253 this.$window.on('resize', function(){
238 that.$box.outerHeight(that.$window.innerHeight() - that.$title_bar.outerHeight());
254 that.$box.outerHeight(that.$window.innerHeight() - that.$title_bar.outerHeight());
239 });
255 });
240
256
241 this._shown_once = false;
257 this._shown_once = false;
242 this.popped_out = true;
258 this.popped_out = true;
243
259
244 this.children_views.update(this.model.get('children'))
260 this.children_views.update(this.model.get('children'))
245 },
261 },
246
262
247 hide: function() {
263 hide: function() {
248 // Called when the modal hide button is clicked.
264 /**
265 * Called when the modal hide button is clicked.
266 */
249 this.$window.hide();
267 this.$window.hide();
250 this.$show_button.removeClass('btn-info');
268 this.$show_button.removeClass('btn-info');
251 },
269 },
252
270
253 show: function() {
271 show: function() {
254 // Called when the modal show button is clicked.
272 /**
273 * Called when the modal show button is clicked.
274 */
255 this.$show_button.addClass('btn-info');
275 this.$show_button.addClass('btn-info');
256 this.$window.show();
276 this.$window.show();
257 if (this.popped_out) {
277 if (this.popped_out) {
258 this.$window.css("positon", "absolute");
278 this.$window.css("positon", "absolute");
259 this.$window.css("top", "0px");
279 this.$window.css("top", "0px");
260 this.$window.css("left", Math.max(0, (($('body').outerWidth() - this.$window.outerWidth()) / 2) +
280 this.$window.css("left", Math.max(0, (($('body').outerWidth() - this.$window.outerWidth()) / 2) +
261 $(window).scrollLeft()) + "px");
281 $(window).scrollLeft()) + "px");
262 this.bring_to_front();
282 this.bring_to_front();
263 }
283 }
264 },
284 },
265
285
266 bring_to_front: function() {
286 bring_to_front: function() {
267 // Make the modal top-most, z-ordered about the other modals.
287 /**
288 * Make the modal top-most, z-ordered about the other modals.
289 */
268 var $widget_modals = $(".widget-modal");
290 var $widget_modals = $(".widget-modal");
269 var max_zindex = 0;
291 var max_zindex = 0;
270 $widget_modals.each(function (index, el){
292 $widget_modals.each(function (index, el){
271 var zindex = parseInt($(el).css('z-index'));
293 var zindex = parseInt($(el).css('z-index'));
272 if (!isNaN(zindex)) {
294 if (!isNaN(zindex)) {
273 max_zindex = Math.max(max_zindex, zindex);
295 max_zindex = Math.max(max_zindex, zindex);
274 }
296 }
275 });
297 });
276
298
277 // Start z-index of widget modals at 2000
299 // Start z-index of widget modals at 2000
278 max_zindex = Math.max(max_zindex, 2000);
300 max_zindex = Math.max(max_zindex, 2000);
279
301
280 $widget_modals.each(function (index, el){
302 $widget_modals.each(function (index, el){
281 $el = $(el);
303 $el = $(el);
282 if (max_zindex == parseInt($el.css('z-index'))) {
304 if (max_zindex == parseInt($el.css('z-index'))) {
283 $el.css('z-index', max_zindex - 1);
305 $el.css('z-index', max_zindex - 1);
284 }
306 }
285 });
307 });
286 this.$window.css('z-index', max_zindex);
308 this.$window.css('z-index', max_zindex);
287 },
309 },
288
310
289 update: function(){
311 update: function(){
290 // Update the contents of this view
312 /**
291 //
313 * Update the contents of this view
292 // Called when the model is changed. The model may have been
314 *
293 // changed by another view or by a state update from the back-end.
315 * Called when the model is changed. The model may have been
316 * changed by another view or by a state update from the back-end.
317 */
294 var description = this.model.get('description');
318 var description = this.model.get('description');
295 if (description.trim().length === 0) {
319 if (description.trim().length === 0) {
296 this.$title.html("&nbsp;"); // Preserve title height
320 this.$title.html("&nbsp;"); // Preserve title height
297 } else {
321 } else {
298 this.$title.text(description);
322 this.$title.text(description);
299 MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.$title.get(0)]);
323 MathJax.Hub.Queue(["Typeset",MathJax.Hub,this.$title.get(0)]);
300 }
324 }
301
325
302 var button_text = this.model.get('button_text');
326 var button_text = this.model.get('button_text');
303 if (button_text.trim().length === 0) {
327 if (button_text.trim().length === 0) {
304 this.$show_button.html("&nbsp;"); // Preserve button height
328 this.$show_button.html("&nbsp;"); // Preserve button height
305 } else {
329 } else {
306 this.$show_button.text(button_text);
330 this.$show_button.text(button_text);
307 }
331 }
308
332
309 if (!this._shown_once) {
333 if (!this._shown_once) {
310 this._shown_once = true;
334 this._shown_once = true;
311 this.show();
335 this.show();
312 }
336 }
313
337
314 return PopupView.__super__.update.apply(this);
338 return PopupView.__super__.update.apply(this);
315 },
339 },
316
340
317 _get_selector_element: function(selector) {
341 _get_selector_element: function(selector) {
318 // Get an element view a 'special' jquery selector. (see widget.js)
342 /**
319 //
343 * Get an element view a 'special' jquery selector. (see widget.js)
320 // Since the modal actually isn't within the $el in the DOM, we need to extend
344 *
321 // the selector logic to allow the user to set css on the modal if need be.
345 * Since the modal actually isn't within the $el in the DOM, we need to extend
322 // The convention used is:
346 * the selector logic to allow the user to set css on the modal if need be.
323 // "modal" - select the modal div
347 * The convention used is:
324 // "modal [selector]" - select element(s) within the modal div.
348 * "modal" - select the modal div
325 // "[selector]" - select elements within $el
349 * "modal [selector]" - select element(s) within the modal div.
326 // "" - select the $el
350 * "[selector]" - select elements within $el
351 * "" - select the $el
352 */
327 if (selector.substring(0, 5) == 'modal') {
353 if (selector.substring(0, 5) == 'modal') {
328 if (selector == 'modal') {
354 if (selector == 'modal') {
329 return this.$window;
355 return this.$window;
330 } else {
356 } else {
331 return this.$window.find(selector.substring(6));
357 return this.$window.find(selector.substring(6));
332 }
358 }
333 } else {
359 } else {
334 return PopupView.__super__._get_selector_element.apply(this, [selector]);
360 return PopupView.__super__._get_selector_element.apply(this, [selector]);
335 }
361 }
336 },
362 },
337 });
363 });
338
364
339 return {
365 return {
340 'BoxView': BoxView,
366 'BoxView': BoxView,
341 'PopupView': PopupView,
367 'PopupView': PopupView,
342 'FlexBoxView': FlexBoxView,
368 'FlexBoxView': FlexBoxView,
343 };
369 };
344 });
370 });
@@ -1,71 +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 "widgets/js/widget",
5 "widgets/js/widget",
6 "jquery",
6 "jquery",
7 "bootstrap",
7 "bootstrap",
8 ], function(widget, $){
8 ], function(widget, $){
9
9
10 var ButtonView = widget.DOMWidgetView.extend({
10 var ButtonView = widget.DOMWidgetView.extend({
11 render : function(){
11 render : function(){
12 // Called when view is rendered.
12 /**
13 * Called when view is rendered.
14 */
13 this.setElement($("<button />")
15 this.setElement($("<button />")
14 .addClass('btn btn-default'));
16 .addClass('btn btn-default'));
15 this.$el.attr("data-toggle", "tooltip");
17 this.$el.attr("data-toggle", "tooltip");
16 this.model.on('change:button_style', function(model, value) {
18 this.model.on('change:button_style', function(model, value) {
17 this.update_button_style();
19 this.update_button_style();
18 }, this);
20 }, this);
19 this.update_button_style('');
21 this.update_button_style('');
20
22
21 this.update(); // Set defaults.
23 this.update(); // Set defaults.
22 },
24 },
23
25
24 update : function(){
26 update : function(){
25 // Update the contents of this view
27 /**
26 //
28 * Update the contents of this view
27 // Called when the model is changed. The model may have been
29 *
28 // changed by another view or by a state update from the back-end.
30 * Called when the model is changed. The model may have been
31 * changed by another view or by a state update from the back-end.
32 */
29 var description = this.model.get('description');
33 var description = this.model.get('description');
30 this.$el.attr("title", this.model.get("tooltip"));
34 this.$el.attr("title", this.model.get("tooltip"));
31 if (description.length === 0) {
35 if (description.length === 0) {
32 this.$el.html("&nbsp;"); // Preserve button height
36 this.$el.html("&nbsp;"); // Preserve button height
33 } else {
37 } else {
34 this.$el.text(description);
38 this.$el.text(description);
35 }
39 }
36
40
37 if (this.model.get('disabled')) {
41 if (this.model.get('disabled')) {
38 this.$el.attr('disabled','disabled');
42 this.$el.attr('disabled','disabled');
39 } else {
43 } else {
40 this.$el.removeAttr('disabled');
44 this.$el.removeAttr('disabled');
41 }
45 }
42
46
43 return ButtonView.__super__.update.apply(this);
47 return ButtonView.__super__.update.apply(this);
44 },
48 },
45
49
46 update_button_style: function(previous_trait_value) {
50 update_button_style: function(previous_trait_value) {
47 var class_map = {
51 var class_map = {
48 primary: ['btn-primary'],
52 primary: ['btn-primary'],
49 success: ['btn-success'],
53 success: ['btn-success'],
50 info: ['btn-info'],
54 info: ['btn-info'],
51 warning: ['btn-warning'],
55 warning: ['btn-warning'],
52 danger: ['btn-danger']
56 danger: ['btn-danger']
53 };
57 };
54 this.update_mapped_classes(class_map, 'button_style', previous_trait_value);
58 this.update_mapped_classes(class_map, 'button_style', previous_trait_value);
55 },
59 },
56
60
57 events: {
61 events: {
58 // Dictionary of events and their handlers.
62 // Dictionary of events and their handlers.
59 'click': '_handle_click',
63 'click': '_handle_click',
60 },
64 },
61
65
62 _handle_click: function(){
66 _handle_click: function(){
63 // Handles when the button is clicked.
67 /**
68 * Handles when the button is clicked.
69 */
64 this.send({event: 'click'});
70 this.send({event: 'click'});
65 },
71 },
66 });
72 });
67
73
68 return {
74 return {
69 'ButtonView': ButtonView,
75 'ButtonView': ButtonView,
70 };
76 };
71 });
77 });
@@ -1,32 +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 "widgets/js/widget",
5 "widgets/js/widget",
6 "widgets/js/widget_int",
6 "widgets/js/widget_int",
7 ], function(widget, int_widgets){
7 ], function(widget, int_widgets){
8 var IntSliderView = int_widgets.IntSliderView;
8 var IntSliderView = int_widgets.IntSliderView;
9 var IntTextView = int_widgets.IntTextView;
9 var IntTextView = int_widgets.IntTextView;
10
10
11 var FloatSliderView = IntSliderView.extend({
11 var FloatSliderView = IntSliderView.extend({
12 _parse_value: parseFloat,
12 _parse_value: parseFloat,
13
13
14 // matches: whitespace?, float, whitespace?, [-:], whitespace?, float
14 // matches: whitespace?, float, whitespace?, [-:], whitespace?, float
15 _range_regex: /^\s*([+-]?(?:\d*\.?\d+|\d+\.)(?:[eE][+-]?\d+)?)\s*[-:]\s*([+-]?(?:\d*\.?\d+|\d+\.)(?:[eE][+-]?\d+)?)/,
15 _range_regex: /^\s*([+-]?(?:\d*\.?\d+|\d+\.)(?:[eE][+-]?\d+)?)\s*[-:]\s*([+-]?(?:\d*\.?\d+|\d+\.)(?:[eE][+-]?\d+)?)/,
16
16
17 _validate_slide_value: function(x) {
17 _validate_slide_value: function(x) {
18 // Validate the value of the slider before sending it to the back-end
18 /**
19 // and applying it to the other views on the page.
19 * Validate the value of the slider before sending it to the back-end
20 * and applying it to the other views on the page.
21 */
20 return x;
22 return x;
21 },
23 },
22 });
24 });
23
25
24 var FloatTextView = IntTextView.extend({
26 var FloatTextView = IntTextView.extend({
25 _parse_value: parseFloat
27 _parse_value: parseFloat
26 });
28 });
27
29
28 return {
30 return {
29 'FloatSliderView': FloatSliderView,
31 'FloatSliderView': FloatSliderView,
30 'FloatTextView': FloatTextView,
32 'FloatTextView': FloatTextView,
31 };
33 };
32 });
34 });
@@ -1,44 +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 "widgets/js/widget",
5 "widgets/js/widget",
6 "jquery",
6 "jquery",
7 ], function(widget, $){
7 ], function(widget, $){
8
8
9 var ImageView = widget.DOMWidgetView.extend({
9 var ImageView = widget.DOMWidgetView.extend({
10 render : function(){
10 render : function(){
11 // Called when view is rendered.
11 /**
12 * Called when view is rendered.
13 */
12 this.setElement($("<img />"));
14 this.setElement($("<img />"));
13 this.update(); // Set defaults.
15 this.update(); // Set defaults.
14 },
16 },
15
17
16 update : function(){
18 update : function(){
17 // Update the contents of this view
19 /**
18 //
20 * Update the contents of this view
19 // Called when the model is changed. The model may have been
21 *
20 // changed by another view or by a state update from the back-end.
22 * Called when the model is changed. The model may have been
23 * changed by another view or by a state update from the back-end.
24 */
21 var image_src = 'data:image/' + this.model.get('format') + ';base64,' + this.model.get('_b64value');
25 var image_src = 'data:image/' + this.model.get('format') + ';base64,' + this.model.get('_b64value');
22 this.$el.attr('src', image_src);
26 this.$el.attr('src', image_src);
23
27
24 var width = this.model.get('width');
28 var width = this.model.get('width');
25 if (width !== undefined && width.length > 0) {
29 if (width !== undefined && width.length > 0) {
26 this.$el.attr('width', width);
30 this.$el.attr('width', width);
27 } else {
31 } else {
28 this.$el.removeAttr('width');
32 this.$el.removeAttr('width');
29 }
33 }
30
34
31 var height = this.model.get('height');
35 var height = this.model.get('height');
32 if (height !== undefined && height.length > 0) {
36 if (height !== undefined && height.length > 0) {
33 this.$el.attr('height', height);
37 this.$el.attr('height', height);
34 } else {
38 } else {
35 this.$el.removeAttr('height');
39 this.$el.removeAttr('height');
36 }
40 }
37 return ImageView.__super__.update.apply(this);
41 return ImageView.__super__.update.apply(this);
38 },
42 },
39 });
43 });
40
44
41 return {
45 return {
42 'ImageView': ImageView,
46 'ImageView': ImageView,
43 };
47 };
44 });
48 });
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
General Comments 0
You need to be logged in to leave comments. Login now