##// END OF EJS Templates
Some JS test fixes
Jonathan Frederic -
Show More
@@ -1,158 +1,161
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 define([
4 define([
5 'base/js/namespace',
5 'base/js/namespace',
6 'jquery',
6 'jquery',
7 ], function(IPython, $) {
7 ], function(IPython, $) {
8 "use strict";
8 "use strict";
9
9
10 var modal = function (options, keyboard_manager, notebook) {
10 var modal = function (options) {
11
11 var modal = $("<div/>")
12 var modal = $("<div/>")
12 .addClass("modal")
13 .addClass("modal")
13 .addClass("fade")
14 .addClass("fade")
14 .attr("role", "dialog");
15 .attr("role", "dialog");
15 var dialog = $("<div/>")
16 var dialog = $("<div/>")
16 .addClass("modal-dialog")
17 .addClass("modal-dialog")
17 .appendTo(modal);
18 .appendTo(modal);
18 var dialog_content = $("<div/>")
19 var dialog_content = $("<div/>")
19 .addClass("modal-content")
20 .addClass("modal-content")
20 .appendTo(dialog);
21 .appendTo(dialog);
21 dialog_content.append(
22 dialog_content.append(
22 $("<div/>")
23 $("<div/>")
23 .addClass("modal-header")
24 .addClass("modal-header")
24 .append($("<button>")
25 .append($("<button>")
25 .attr("type", "button")
26 .attr("type", "button")
26 .addClass("close")
27 .addClass("close")
27 .attr("data-dismiss", "modal")
28 .attr("data-dismiss", "modal")
28 .attr("aria-hidden", "true")
29 .attr("aria-hidden", "true")
29 .html("&times;")
30 .html("&times;")
30 ).append(
31 ).append(
31 $("<h4/>")
32 $("<h4/>")
32 .addClass('modal-title')
33 .addClass('modal-title')
33 .text(options.title || "")
34 .text(options.title || "")
34 )
35 )
35 ).append(
36 ).append(
36 $("<div/>").addClass("modal-body").append(
37 $("<div/>").addClass("modal-body").append(
37 options.body || $("<p/>")
38 options.body || $("<p/>")
38 )
39 )
39 );
40 );
40
41
41 var footer = $("<div/>").addClass("modal-footer");
42 var footer = $("<div/>").addClass("modal-footer");
42
43
43 for (var label in options.buttons) {
44 for (var label in options.buttons) {
44 var btn_opts = options.buttons[label];
45 var btn_opts = options.buttons[label];
45 var button = $("<button/>")
46 var button = $("<button/>")
46 .addClass("btn btn-default btn-sm")
47 .addClass("btn btn-default btn-sm")
47 .attr("data-dismiss", "modal")
48 .attr("data-dismiss", "modal")
48 .text(label);
49 .text(label);
49 if (btn_opts.click) {
50 if (btn_opts.click) {
50 button.click($.proxy(btn_opts.click, dialog_content));
51 button.click($.proxy(btn_opts.click, dialog_content));
51 }
52 }
52 if (btn_opts.class) {
53 if (btn_opts.class) {
53 button.addClass(btn_opts.class);
54 button.addClass(btn_opts.class);
54 }
55 }
55 footer.append(button);
56 footer.append(button);
56 }
57 }
57 dialog_content.append(footer);
58 dialog_content.append(footer);
58 // hook up on-open event
59 // hook up on-open event
59 modal.on("shown.bs.modal", function() {
60 modal.on("shown.bs.modal", function() {
60 setTimeout(function() {
61 setTimeout(function() {
61 footer.find("button").last().focus();
62 footer.find("button").last().focus();
62 if (options.open) {
63 if (options.open) {
63 $.proxy(options.open, modal)();
64 $.proxy(options.open, modal)();
64 }
65 }
65 }, 0);
66 }, 0);
66 });
67 });
67
68
68 // destroy modal on hide, unless explicitly asked not to
69 // destroy modal on hide, unless explicitly asked not to
69 if (options.destroy === undefined || options.destroy) {
70 if (options.destroy === undefined || options.destroy) {
70 modal.on("hidden.bs.modal", function () {
71 modal.on("hidden.bs.modal", function () {
71 modal.remove();
72 modal.remove();
72 });
73 });
73 }
74 }
74 modal.on("hidden.bs.modal", function () {
75 modal.on("hidden.bs.modal", function () {
75 if (notebook) {
76 if (options.notebook) {
76 var cell = notebook.get_selected_cell();
77 var cell = options.notebook.get_selected_cell();
77 if (cell) cell.select();
78 if (cell) cell.select();
78 keyboard_manager.enable();
79 if (options.keyboard_manager) {
79 keyboard_manager.command_mode();
80 options.keyboard_manager.enable();
81 options.keyboard_manager.command_mode();
82 }
80 }
83 }
81 });
84 });
82
85
83 if (keyboard_manager) {
86 if (options.keyboard_manager) {
84 keyboard_manager.disable();
87 options.keyboard_manager.disable();
85 }
88 }
86
89
87 return modal.modal(options);
90 return modal.modal(options);
88 };
91 };
89
92
90 var edit_metadata = function (md, callback, name, keyboard_manager, notebook) {
93 var edit_metadata = function (md, callback, name, keyboard_manager, notebook) {
91 name = name || "Cell";
94 name = name || "Cell";
92 var error_div = $('<div/>').css('color', 'red');
95 var error_div = $('<div/>').css('color', 'red');
93 var message =
96 var message =
94 "Manually edit the JSON below to manipulate the metadata for this " + name + "." +
97 "Manually edit the JSON below to manipulate the metadata for this " + name + "." +
95 " We recommend putting custom metadata attributes in an appropriately named sub-structure," +
98 " We recommend putting custom metadata attributes in an appropriately named sub-structure," +
96 " so they don't conflict with those of others.";
99 " so they don't conflict with those of others.";
97
100
98 var textarea = $('<textarea/>')
101 var textarea = $('<textarea/>')
99 .attr('rows', '13')
102 .attr('rows', '13')
100 .attr('cols', '80')
103 .attr('cols', '80')
101 .attr('name', 'metadata')
104 .attr('name', 'metadata')
102 .text(JSON.stringify(md || {}, null, 2));
105 .text(JSON.stringify(md || {}, null, 2));
103
106
104 var dialogform = $('<div/>').attr('title', 'Edit the metadata')
107 var dialogform = $('<div/>').attr('title', 'Edit the metadata')
105 .append(
108 .append(
106 $('<form/>').append(
109 $('<form/>').append(
107 $('<fieldset/>').append(
110 $('<fieldset/>').append(
108 $('<label/>')
111 $('<label/>')
109 .attr('for','metadata')
112 .attr('for','metadata')
110 .text(message)
113 .text(message)
111 )
114 )
112 .append(error_div)
115 .append(error_div)
113 .append($('<br/>'))
116 .append($('<br/>'))
114 .append(textarea)
117 .append(textarea)
115 )
118 )
116 );
119 );
117 var editor = CodeMirror.fromTextArea(textarea[0], {
120 var editor = CodeMirror.fromTextArea(textarea[0], {
118 lineNumbers: true,
121 lineNumbers: true,
119 matchBrackets: true,
122 matchBrackets: true,
120 indentUnit: 2,
123 indentUnit: 2,
121 autoIndent: true,
124 autoIndent: true,
122 mode: 'application/json',
125 mode: 'application/json',
123 });
126 });
124 var modal = modal({
127 var modal = modal({
125 title: "Edit " + name + " Metadata",
128 title: "Edit " + name + " Metadata",
126 body: dialogform,
129 body: dialogform,
127 buttons: {
130 buttons: {
128 OK: { class : "btn-primary",
131 OK: { class : "btn-primary",
129 click: function() {
132 click: function() {
130 // validate json and set it
133 // validate json and set it
131 var new_md;
134 var new_md;
132 try {
135 try {
133 new_md = JSON.parse(editor.getValue());
136 new_md = JSON.parse(editor.getValue());
134 } catch(e) {
137 } catch(e) {
135 console.log(e);
138 console.log(e);
136 error_div.text('WARNING: Could not save invalid JSON.');
139 error_div.text('WARNING: Could not save invalid JSON.');
137 return false;
140 return false;
138 }
141 }
139 callback(new_md);
142 callback(new_md);
140 }
143 }
141 },
144 },
142 Cancel: {}
145 Cancel: {}
143 }
146 }
144 }, keyboard_manager, notebook);
147 }, keyboard_manager, notebook);
145
148
146 modal.on('shown.bs.modal', function(){ editor.refresh(); });
149 modal.on('shown.bs.modal', function(){ editor.refresh(); });
147 };
150 };
148
151
149 var dialog = {
152 var dialog = {
150 modal : modal,
153 modal : modal,
151 edit_metadata : edit_metadata,
154 edit_metadata : edit_metadata,
152 };
155 };
153
156
154 // Backwards compatability.
157 // Backwards compatability.
155 IPython.Dialog = dialog;
158 IPython.Dialog = dialog;
156
159
157 return dialog;
160 return dialog;
158 });
161 });
@@ -1,117 +1,121
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 // add trusting data-attributes to the default sanitizeAttribs from caja
22 // this function is mostly copied from the caja source
22 // this function is mostly copied from the caja source
23 var ATTRIBS = caja.html4.ATTRIBS;
23 var ATTRIBS = caja.html4.ATTRIBS;
24 for (var i = 0; i < attribs.length; i += 2) {
24 for (var i = 0; i < attribs.length; i += 2) {
25 var attribName = attribs[i];
25 var attribName = attribs[i];
26 if (attribName.substr(0,5) == 'data-') {
26 if (attribName.substr(0,5) == 'data-') {
27 var attribKey = '*::' + attribName;
27 var attribKey = '*::' + attribName;
28 if (!ATTRIBS.hasOwnProperty(attribKey)) {
28 if (!ATTRIBS.hasOwnProperty(attribKey)) {
29 ATTRIBS[attribKey] = 0;
29 ATTRIBS[attribKey] = 0;
30 }
30 }
31 }
31 }
32 }
32 }
33 return caja.sanitizeAttribs(tagName, attribs, opt_naiveUriRewriter, opt_nmTokenPolicy, opt_logger);
33 return caja.sanitizeAttribs(tagName, attribs, opt_naiveUriRewriter, opt_nmTokenPolicy, opt_logger);
34 };
34 };
35
35
36 var sanitize_css = function (css, tagPolicy) {
36 var sanitize_css = function (css, tagPolicy) {
37 // sanitize CSS
37 // sanitize CSS
38 // like sanitize_html, but for CSS
38 // like sanitize_html, but for CSS
39 // called by sanitize_stylesheets
39 // called by sanitize_stylesheets
40 return caja.sanitizeStylesheet(
40 return caja.sanitizeStylesheet(
41 window.location.pathname,
41 window.location.pathname,
42 css,
42 css,
43 {
43 {
44 containerClass: null,
44 containerClass: null,
45 idSuffix: '',
45 idSuffix: '',
46 tagPolicy: tagPolicy,
46 tagPolicy: tagPolicy,
47 virtualizeAttrName: noop
47 virtualizeAttrName: noop
48 },
48 },
49 noop
49 noop
50 );
50 );
51 };
51 };
52
52
53 var sanitize_stylesheets = function (html, tagPolicy) {
53 var sanitize_stylesheets = function (html, tagPolicy) {
54 // sanitize just the css in style tags in a block of html
54 // sanitize just the css in style tags in a block of html
55 // called by sanitize_html, if allow_css is true
55 // called by sanitize_html, if allow_css is true
56 var h = $("<div/>").append(html);
56 var h = $("<div/>").append(html);
57 var style_tags = h.find("style");
57 var style_tags = h.find("style");
58 if (!style_tags.length) {
58 if (!style_tags.length) {
59 // no style tags to sanitize
59 // no style tags to sanitize
60 return html;
60 return html;
61 }
61 }
62 style_tags.each(function(i, style) {
62 style_tags.each(function(i, style) {
63 style.innerHTML = sanitize_css(style.innerHTML, tagPolicy);
63 style.innerHTML = sanitize_css(style.innerHTML, tagPolicy);
64 });
64 });
65 return h.html();
65 return h.html();
66 };
66 };
67
67
68 var sanitize_html = function (html, allow_css) {
68 var sanitize_html = function (html, allow_css) {
69 // sanitize HTML
69 // sanitize HTML
70 // if allow_css is true (default: false), CSS is sanitized as well.
70 // if allow_css is true (default: false), CSS is sanitized as well.
71 // otherwise, CSS elements and attributes are simply removed.
71 // otherwise, CSS elements and attributes are simply removed.
72 var html4 = caja.html4;
72 var html4 = caja.html4;
73
73
74 if (allow_css) {
74 if (allow_css) {
75 // allow sanitization of style tags,
75 // allow sanitization of style tags,
76 // not just scrubbing
76 // not just scrubbing
77 html4.ELEMENTS.style &= ~html4.eflags.UNSAFE;
77 html4.ELEMENTS.style &= ~html4.eflags.UNSAFE;
78 html4.ATTRIBS.style = html4.atype.STYLE;
78 html4.ATTRIBS.style = html4.atype.STYLE;
79 } else {
79 } else {
80 // scrub all CSS
80 // scrub all CSS
81 html4.ELEMENTS.style |= html4.eflags.UNSAFE;
81 html4.ELEMENTS.style |= html4.eflags.UNSAFE;
82 html4.ATTRIBS.style = html4.atype.SCRIPT;
82 html4.ATTRIBS.style = html4.atype.SCRIPT;
83 }
83 }
84
84
85 var record_messages = function (msg, opts) {
85 var record_messages = function (msg, opts) {
86 console.log("HTML Sanitizer", msg, opts);
86 console.log("HTML Sanitizer", msg, opts);
87 };
87 };
88
88
89 var policy = function (tagName, attribs) {
89 var policy = function (tagName, attribs) {
90 if (!(html4.ELEMENTS[tagName] & html4.eflags.UNSAFE)) {
90 if (!(html4.ELEMENTS[tagName] & html4.eflags.UNSAFE)) {
91 return {
91 return {
92 'attribs': sanitizeAttribs(tagName, attribs,
92 'attribs': sanitizeAttribs(tagName, attribs,
93 noop, noop, record_messages)
93 noop, noop, record_messages)
94 };
94 };
95 } else {
95 } else {
96 record_messages(tagName + " removed", {
96 record_messages(tagName + " removed", {
97 change: "removed",
97 change: "removed",
98 tagName: tagName
98 tagName: tagName
99 });
99 });
100 }
100 }
101 };
101 };
102
102
103 var sanitized = caja.sanitizeWithPolicy(html, policy);
103 var sanitized = caja.sanitizeWithPolicy(html, policy);
104
104
105 if (allow_css) {
105 if (allow_css) {
106 // sanitize style tags as stylesheets
106 // sanitize style tags as stylesheets
107 sanitized = sanitize_stylesheets(result.sanitized, policy);
107 sanitized = sanitize_stylesheets(result.sanitized, policy);
108 }
108 }
109
109
110 return sanitized;
110 return sanitized;
111 };
111 };
112
112
113 return {
113 var security = {
114 caja: caja,
114 caja: caja,
115 sanitize_html: sanitize_html
115 sanitize_html: sanitize_html
116 };
116 };
117
118 IPython.security = security;
119
120 return security;
117 });
121 });
@@ -1,132 +1,136
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 var ipython = ipython || {};
5 require([
4 require([
6 'base/js/namespace',
5 'base/js/namespace',
7 'jquery',
6 'jquery',
8 'notebook/js/notebook',
7 'notebook/js/notebook',
9 'base/js/utils',
8 'base/js/utils',
9 'base/js/keyboard',
10 'base/js/page',
10 'base/js/page',
11 'notebook/js/layoutmanager',
11 'notebook/js/layoutmanager',
12 'base/js/events',
12 'base/js/events',
13 'auth/js/loginwidget',
13 'auth/js/loginwidget',
14 'notebook/js/maintoolbar',
14 'notebook/js/maintoolbar',
15 'notebook/js/pager',
15 'notebook/js/pager',
16 'notebook/js/quickhelp',
16 'notebook/js/quickhelp',
17 'notebook/js/menubar',
17 'notebook/js/menubar',
18 'notebook/js/notificationarea',
18 'notebook/js/notificationarea',
19 'notebook/js/savewidget',
19 'notebook/js/savewidget',
20 'notebook/js/keyboardmanager',
20 'notebook/js/keyboardmanager',
21 'notebook/js/config',
21 'notebook/js/config',
22 ], function(
22 ], function(
23 IPython,
23 IPython,
24 $,
24 $,
25 notebook,
25 notebook,
26 utils,
26 utils,
27 keyboard,
27 page,
28 page,
28 layoutmanager,
29 layoutmanager,
29 events,
30 events,
30 loginwidget,
31 loginwidget,
31 maintoolbar,
32 maintoolbar,
32 pager,
33 pager,
33 quickhelp,
34 quickhelp,
34 menubar,
35 menubar,
35 notificationarea,
36 notificationarea,
36 savewidget,
37 savewidget,
37 keyboardmanager,
38 keyboardmanager,
38 config
39 config
39 ) {
40 ) {
40 "use strict";
41 "use strict";
41
42
42 $('#ipython-main-app').addClass('border-box-sizing');
43 $('#ipython-main-app').addClass('border-box-sizing');
43 $('div#notebook_panel').addClass('border-box-sizing');
44 $('div#notebook_panel').addClass('border-box-sizing');
44
45
45 var common_options = {
46 var common_options = {
46 base_url : utils.get_body_data("baseUrl"),
47 base_url : utils.get_body_data("baseUrl"),
47 notebook_path : utils.get_body_data("notebookPath"),
48 notebook_path : utils.get_body_data("notebookPath"),
48 notebook_name : utils.get_body_data('notebookName')
49 notebook_name : utils.get_body_data('notebookName')
49 };
50 };
50
51
51 var user_config = $.extend({}, config.default_config);
52 var user_config = $.extend({}, config.default_config);
52 var page = new page.Page();
53 var page = new page.Page();
53 var layout_manager = new layoutmanager.LayoutManager();
54 var layout_manager = new layoutmanager.LayoutManager();
54 var events = $([new events.Events()]);
55 var events = $([new events.Events()]);
55 var pager = new pager.Pager('div#pager', 'div#pager_splitter', {
56 var pager = new pager.Pager('div#pager', 'div#pager_splitter', {
56 layout_manager: layout_manager,
57 layout_manager: layout_manager,
57 events: events});
58 events: events});
58 var keyboard_manager = new keyboardmanager.KeyboardManager({
59 var keyboard_manager = new keyboardmanager.KeyboardManager({
59 pager: pager,
60 pager: pager,
60 events: events});
61 events: events});
61 var save_widget = new savewidget.SaveWidget('span#save_widget', events);
62 var save_widget = new savewidget.SaveWidget('span#save_widget', events);
62 var notebook = new notebook.Notebook('div#notebook', $.extend({
63 var notebook = new notebook.Notebook('div#notebook', $.extend({
63 events: events,
64 events: events,
64 keyboard_manager: keyboard_manager,
65 keyboard_manager: keyboard_manager,
65 save_widget: save_widget,
66 save_widget: save_widget,
66 config: user_config},
67 config: user_config},
67 common_options));
68 common_options));
68 var login_widget = new loginwidget.LoginWidget('span#login_widget', common_options);
69 var login_widget = new loginwidget.LoginWidget('span#login_widget', common_options);
69 var toolbar = new maintoolbar.MainToolBar('#maintoolbar-container', {
70 var toolbar = new maintoolbar.MainToolBar('#maintoolbar-container', {
70 notebook: notebook,
71 notebook: notebook,
71 events: events});
72 events: events});
72 var quick_help = new quickhelp.QuickHelp({
73 var quick_help = new quickhelp.QuickHelp({
73 keyboard_manager: keyboard_manager,
74 keyboard_manager: keyboard_manager,
74 events: events});
75 events: events,
76 notebook: notebook});
75 var menubar = new menubar.MenuBar('#menubar', $.extend({
77 var menubar = new menubar.MenuBar('#menubar', $.extend({
76 notebook: notebook,
78 notebook: notebook,
77 layout_manager: layout_manager,
79 layout_manager: layout_manager,
78 events: events,
80 events: events,
79 save_widget: save_widget,
81 save_widget: save_widget,
80 quick_help: quick_help},
82 quick_help: quick_help},
81 common_options));
83 common_options));
82 var notification_area = new notificationarea.NotificationArea(
84 var notification_area = new notificationarea.NotificationArea(
83 '#notification_area', {
85 '#notification_area', {
84 events: events,
86 events: events,
85 save_widget: save_widget,
87 save_widget: save_widget,
86 notebook: notebook});
88 notebook: notebook,
89 keyboard_manager: keyboard_manager});
87 notification_area.init_notification_widgets();
90 notification_area.init_notification_widgets();
88
91
89 $('body').append('<div id="fonttest"><pre><span id="test1">x</span>'+
92 $('body').append('<div id="fonttest"><pre><span id="test1">x</span>'+
90 '<span id="test2" style="font-weight: bold;">x</span>'+
93 '<span id="test2" style="font-weight: bold;">x</span>'+
91 '<span id="test3" style="font-style: italic;">x</span></pre></div>');
94 '<span id="test3" style="font-style: italic;">x</span></pre></div>');
92 var nh = $('#test1').innerHeight();
95 var nh = $('#test1').innerHeight();
93 var bh = $('#test2').innerHeight();
96 var bh = $('#test2').innerHeight();
94 var ih = $('#test3').innerHeight();
97 var ih = $('#test3').innerHeight();
95 if(nh != bh || nh != ih) {
98 if(nh != bh || nh != ih) {
96 $('head').append('<style>.CodeMirror span { vertical-align: bottom; }</style>');
99 $('head').append('<style>.CodeMirror span { vertical-align: bottom; }</style>');
97 }
100 }
98 $('#fonttest').remove();
101 $('#fonttest').remove();
99
102
100 page.show();
103 page.show();
101
104
102 layout_manager.do_resize();
105 layout_manager.do_resize();
103 var first_load = function () {
106 var first_load = function () {
104 layout_manager.do_resize();
107 layout_manager.do_resize();
105 var hash = document.location.hash;
108 var hash = document.location.hash;
106 if (hash) {
109 if (hash) {
107 document.location.hash = '';
110 document.location.hash = '';
108 document.location.hash = hash;
111 document.location.hash = hash;
109 }
112 }
110 notebook.set_autosave_interval(notebook.minimum_autosave_interval);
113 notebook.set_autosave_interval(notebook.minimum_autosave_interval);
111 // only do this once
114 // only do this once
112 events.off('notebook_loaded.Notebook', first_load);
115 events.off('notebook_loaded.Notebook', first_load);
113 };
116 };
114
117
115 events.on('notebook_loaded.Notebook', first_load);
118 events.on('notebook_loaded.Notebook', first_load);
116 events.trigger('app_initialized.NotebookApp');
119 events.trigger('app_initialized.NotebookApp');
117 notebook.load_notebook(common_options.notebook_name, common_options.notebook_path);
120 notebook.load_notebook(common_options.notebook_name, common_options.notebook_path);
118
121
119 ipython.page = page;
122 IPython.page = page;
120 ipython.layout_manager = layout_manager;
123 IPython.layout_manager = layout_manager;
121 ipython.notebook = notebook;
124 IPython.notebook = notebook;
122 ipython.pager = pager;
125 IPython.pager = pager;
123 ipython.quick_help = quick_help;
126 IPython.quick_help = quick_help;
124 ipython.login_widget = login_widget;
127 IPython.login_widget = login_widget;
125 ipython.menubar = menubar;
128 IPython.menubar = menubar;
126 ipython.toolbar = toolbar;
129 IPython.toolbar = toolbar;
127 ipython.notification_area = notification_area;
130 IPython.notification_area = notification_area;
128 ipython.events = events;
131 IPython.events = events;
129 ipython.keyboard_manager = keyboard_manager;
132 IPython.keyboard_manager = keyboard_manager;
130 ipython.save_widget = save_widget;
133 IPython.save_widget = save_widget;
131 ipython.config = user_config;
134 IPython.config = user_config;
135 IPython.keyboard = keyboard;
132 });
136 });
@@ -1,2496 +1,2514
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 define([
4 define([
5 'base/js/namespace',
5 'base/js/namespace',
6 'jquery',
6 'jquery',
7 'base/js/utils',
7 'base/js/utils',
8 'base/js/dialog',
8 'base/js/dialog',
9 'notebook/js/textcell',
9 'notebook/js/textcell',
10 'notebook/js/codecell',
10 'notebook/js/codecell',
11 'services/sessions/js/session',
11 'services/sessions/js/session',
12 'notebook/js/celltoolbar',
12 'notebook/js/celltoolbar',
13 'components/marked/lib/marked',
13 'components/marked/lib/marked',
14 'notebook/js/mathjaxutils',
14 'notebook/js/mathjaxutils',
15 'base/js/keyboard',
15 'base/js/keyboard',
16 'components/jquery-ui/ui/minified/jquery-ui.min',
16 'components/jquery-ui/ui/minified/jquery-ui.min',
17 'components/bootstrap/js/bootstrap.min',
17 'components/bootstrap/js/bootstrap.min',
18 ], function (
18 ], function (
19 IPython,
19 IPython,
20 $,
20 $,
21 utils,
21 utils,
22 dialog,
22 dialog,
23 textcell,
23 textcell,
24 codecell,
24 codecell,
25 session,
25 session,
26 celltoolbar,
26 celltoolbar,
27 marked,
27 marked,
28 mathjaxutils,
28 mathjaxutils,
29 keyboard
29 keyboard
30 ) {
30 ) {
31
31
32 var Notebook = function (selector, options) {
32 var Notebook = function (selector, options) {
33 // Constructor
33 // Constructor
34 //
34 //
35 // A notebook contains and manages cells.
35 // A notebook contains and manages cells.
36 //
36 //
37 // Parameters:
37 // Parameters:
38 // selector: string
38 // selector: string
39 // options: dictionary
39 // options: dictionary
40 // Dictionary of keyword arguments.
40 // Dictionary of keyword arguments.
41 // events: $(Events) instance
41 // events: $(Events) instance
42 // keyboard_manager: KeyboardManager instance
42 // keyboard_manager: KeyboardManager instance
43 // save_widget: SaveWidget instance
43 // save_widget: SaveWidget instance
44 // config: dictionary
44 // config: dictionary
45 // base_url : string
45 // base_url : string
46 // notebook_path : string
46 // notebook_path : string
47 // notebook_name : string
47 // notebook_name : string
48 this.config = options.config || {};
48 this.config = options.config || {};
49 this.base_url = options.base_url;
49 this.base_url = options.base_url;
50 this.notebook_path = options.notebook_path;
50 this.notebook_path = options.notebook_path;
51 this.notebook_name = options.notebook_name;
51 this.notebook_name = options.notebook_name;
52 this.events = options.events;
52 this.events = options.events;
53 this.keyboard_manager = options.keyboard_manager;
53 this.keyboard_manager = options.keyboard_manager;
54 this.save_widget = options.save_widget;
54 this.save_widget = options.save_widget;
55 // TODO: This code smells (and the other `= this` line a couple lines down)
55 // TODO: This code smells (and the other `= this` line a couple lines down)
56 // We need a better way to deal with circular instance references.
56 // We need a better way to deal with circular instance references.
57 this.keyboard_manager.notebook = this;
57 this.keyboard_manager.notebook = this;
58 this.save_widget.notebook = this;
58 this.save_widget.notebook = this;
59
59
60 mathjaxutils.init();
60 mathjaxutils.init();
61
61
62 if (marked) {
62 if (marked) {
63 marked.setOptions({
63 marked.setOptions({
64 gfm : true,
64 gfm : true,
65 tables: true,
65 tables: true,
66 langPrefix: "language-",
66 langPrefix: "language-",
67 highlight: function(code, lang) {
67 highlight: function(code, lang) {
68 if (!lang) {
68 if (!lang) {
69 // no language, no highlight
69 // no language, no highlight
70 return code;
70 return code;
71 }
71 }
72 var highlighted;
72 var highlighted;
73 try {
73 try {
74 highlighted = hljs.highlight(lang, code, false);
74 highlighted = hljs.highlight(lang, code, false);
75 } catch(err) {
75 } catch(err) {
76 highlighted = hljs.highlightAuto(code);
76 highlighted = hljs.highlightAuto(code);
77 }
77 }
78 return highlighted.value;
78 return highlighted.value;
79 }
79 }
80 });
80 });
81 }
81 }
82
82
83 // Backwards compatability.
83 // Backwards compatability.
84 IPython.keyboard_manager = this.keyboard_manager;
84 IPython.keyboard_manager = this.keyboard_manager;
85 IPython.save_widget = this.save_widget;
85 IPython.save_widget = this.save_widget;
86 IPython.keyboard = this.keyboard;
86 IPython.keyboard = this.keyboard;
87
87
88 this.element = $(selector);
88 this.element = $(selector);
89 this.element.scroll();
89 this.element.scroll();
90 this.element.data("notebook", this);
90 this.element.data("notebook", this);
91 this.next_prompt_number = 1;
91 this.next_prompt_number = 1;
92 this.session = null;
92 this.session = null;
93 this.kernel = null;
93 this.kernel = null;
94 this.clipboard = null;
94 this.clipboard = null;
95 this.undelete_backup = null;
95 this.undelete_backup = null;
96 this.undelete_index = null;
96 this.undelete_index = null;
97 this.undelete_below = false;
97 this.undelete_below = false;
98 this.paste_enabled = false;
98 this.paste_enabled = false;
99 // It is important to start out in command mode to match the intial mode
99 // It is important to start out in command mode to match the intial mode
100 // of the KeyboardManager.
100 // of the KeyboardManager.
101 this.mode = 'command';
101 this.mode = 'command';
102 this.set_dirty(false);
102 this.set_dirty(false);
103 this.metadata = {};
103 this.metadata = {};
104 this._checkpoint_after_save = false;
104 this._checkpoint_after_save = false;
105 this.last_checkpoint = null;
105 this.last_checkpoint = null;
106 this.checkpoints = [];
106 this.checkpoints = [];
107 this.autosave_interval = 0;
107 this.autosave_interval = 0;
108 this.autosave_timer = null;
108 this.autosave_timer = null;
109 // autosave *at most* every two minutes
109 // autosave *at most* every two minutes
110 this.minimum_autosave_interval = 120000;
110 this.minimum_autosave_interval = 120000;
111 // single worksheet for now
111 // single worksheet for now
112 this.worksheet_metadata = {};
112 this.worksheet_metadata = {};
113 this.notebook_name_blacklist_re = /[\/\\:]/;
113 this.notebook_name_blacklist_re = /[\/\\:]/;
114 this.nbformat = 3; // Increment this when changing the nbformat
114 this.nbformat = 3; // Increment this when changing the nbformat
115 this.nbformat_minor = 0; // Increment this when changing the nbformat
115 this.nbformat_minor = 0; // Increment this when changing the nbformat
116 this.style();
116 this.style();
117 this.create_elements();
117 this.create_elements();
118 this.bind_events();
118 this.bind_events();
119 this.save_notebook = function() { // don't allow save until notebook_loaded
119 this.save_notebook = function() { // don't allow save until notebook_loaded
120 this.save_notebook_error(null, null, "Load failed, save is disabled");
120 this.save_notebook_error(null, null, "Load failed, save is disabled");
121 };
121 };
122 };
122 };
123
123
124 /**
124 /**
125 * Tweak the notebook's CSS style.
125 * Tweak the notebook's CSS style.
126 *
126 *
127 * @method style
127 * @method style
128 */
128 */
129 Notebook.prototype.style = function () {
129 Notebook.prototype.style = function () {
130 $('div#notebook').addClass('border-box-sizing');
130 $('div#notebook').addClass('border-box-sizing');
131 };
131 };
132
132
133 /**
133 /**
134 * Create an HTML and CSS representation of the notebook.
134 * Create an HTML and CSS representation of the notebook.
135 *
135 *
136 * @method create_elements
136 * @method create_elements
137 */
137 */
138 Notebook.prototype.create_elements = function () {
138 Notebook.prototype.create_elements = function () {
139 var that = this;
139 var that = this;
140 this.element.attr('tabindex','-1');
140 this.element.attr('tabindex','-1');
141 this.container = $("<div/>").addClass("container").attr("id", "notebook-container");
141 this.container = $("<div/>").addClass("container").attr("id", "notebook-container");
142 // We add this end_space div to the end of the notebook div to:
142 // We add this end_space div to the end of the notebook div to:
143 // i) provide a margin between the last cell and the end of the notebook
143 // i) provide a margin between the last cell and the end of the notebook
144 // ii) to prevent the div from scrolling up when the last cell is being
144 // ii) to prevent the div from scrolling up when the last cell is being
145 // edited, but is too low on the page, which browsers will do automatically.
145 // edited, but is too low on the page, which browsers will do automatically.
146 var end_space = $('<div/>').addClass('end_space');
146 var end_space = $('<div/>').addClass('end_space');
147 end_space.dblclick(function (e) {
147 end_space.dblclick(function (e) {
148 var ncells = that.ncells();
148 var ncells = that.ncells();
149 that.insert_cell_below('code',ncells-1);
149 that.insert_cell_below('code',ncells-1);
150 });
150 });
151 this.element.append(this.container);
151 this.element.append(this.container);
152 this.container.append(end_space);
152 this.container.append(end_space);
153 };
153 };
154
154
155 /**
155 /**
156 * Bind JavaScript events: key presses and custom IPython events.
156 * Bind JavaScript events: key presses and custom IPython events.
157 *
157 *
158 * @method bind_events
158 * @method bind_events
159 */
159 */
160 Notebook.prototype.bind_events = function () {
160 Notebook.prototype.bind_events = function () {
161 var that = this;
161 var that = this;
162
162
163 this.events.on('set_next_input.Notebook', function (event, data) {
163 this.events.on('set_next_input.Notebook', function (event, data) {
164 var index = that.find_cell_index(data.cell);
164 var index = that.find_cell_index(data.cell);
165 var new_cell = that.insert_cell_below('code',index);
165 var new_cell = that.insert_cell_below('code',index);
166 new_cell.set_text(data.text);
166 new_cell.set_text(data.text);
167 that.dirty = true;
167 that.dirty = true;
168 });
168 });
169
169
170 this.events.on('set_dirty.Notebook', function (event, data) {
170 this.events.on('set_dirty.Notebook', function (event, data) {
171 that.dirty = data.value;
171 that.dirty = data.value;
172 });
172 });
173
173
174 this.events.on('trust_changed.Notebook', function (event, data) {
174 this.events.on('trust_changed.Notebook', function (event, data) {
175 that.trusted = data.value;
175 that.trusted = data.value;
176 });
176 });
177
177
178 this.events.on('select.Cell', function (event, data) {
178 this.events.on('select.Cell', function (event, data) {
179 var index = that.find_cell_index(data.cell);
179 var index = that.find_cell_index(data.cell);
180 that.select(index);
180 that.select(index);
181 });
181 });
182
182
183 this.events.on('edit_mode.Cell', function (event, data) {
183 this.events.on('edit_mode.Cell', function (event, data) {
184 that.handle_edit_mode(data.cell);
184 that.handle_edit_mode(data.cell);
185 });
185 });
186
186
187 this.events.on('command_mode.Cell', function (event, data) {
187 this.events.on('command_mode.Cell', function (event, data) {
188 that.handle_command_mode(data.cell);
188 that.handle_command_mode(data.cell);
189 });
189 });
190
190
191 this.events.on('status_autorestarting.Kernel', function () {
191 this.events.on('status_autorestarting.Kernel', function () {
192 dialog.modal({
192 dialog.modal({
193 notebook: that,
194 keyboard_manager: that.keyboard_manager,
193 title: "Kernel Restarting",
195 title: "Kernel Restarting",
194 body: "The kernel appears to have died. It will restart automatically.",
196 body: "The kernel appears to have died. It will restart automatically.",
195 buttons: {
197 buttons: {
196 OK : {
198 OK : {
197 class : "btn-primary"
199 class : "btn-primary"
198 }
200 }
199 }
201 }
200 });
202 });
201 });
203 });
202
204
203 var collapse_time = function (time) {
205 var collapse_time = function (time) {
204 var app_height = $('#ipython-main-app').height(); // content height
206 var app_height = $('#ipython-main-app').height(); // content height
205 var splitter_height = $('div#pager_splitter').outerHeight(true);
207 var splitter_height = $('div#pager_splitter').outerHeight(true);
206 var new_height = app_height - splitter_height;
208 var new_height = app_height - splitter_height;
207 that.element.animate({height : new_height + 'px'}, time);
209 that.element.animate({height : new_height + 'px'}, time);
208 };
210 };
209
211
210 this.element.bind('collapse_pager', function (event, extrap) {
212 this.element.bind('collapse_pager', function (event, extrap) {
211 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
213 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
212 collapse_time(time);
214 collapse_time(time);
213 });
215 });
214
216
215 var expand_time = function (time) {
217 var expand_time = function (time) {
216 var app_height = $('#ipython-main-app').height(); // content height
218 var app_height = $('#ipython-main-app').height(); // content height
217 var splitter_height = $('div#pager_splitter').outerHeight(true);
219 var splitter_height = $('div#pager_splitter').outerHeight(true);
218 var pager_height = $('div#pager').outerHeight(true);
220 var pager_height = $('div#pager').outerHeight(true);
219 var new_height = app_height - pager_height - splitter_height;
221 var new_height = app_height - pager_height - splitter_height;
220 that.element.animate({height : new_height + 'px'}, time);
222 that.element.animate({height : new_height + 'px'}, time);
221 };
223 };
222
224
223 this.element.bind('expand_pager', function (event, extrap) {
225 this.element.bind('expand_pager', function (event, extrap) {
224 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
226 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
225 expand_time(time);
227 expand_time(time);
226 });
228 });
227
229
228 // Firefox 22 broke $(window).on("beforeunload")
230 // Firefox 22 broke $(window).on("beforeunload")
229 // I'm not sure why or how.
231 // I'm not sure why or how.
230 window.onbeforeunload = function (e) {
232 window.onbeforeunload = function (e) {
231 // TODO: Make killing the kernel configurable.
233 // TODO: Make killing the kernel configurable.
232 var kill_kernel = false;
234 var kill_kernel = false;
233 if (kill_kernel) {
235 if (kill_kernel) {
234 that.session.kill_kernel();
236 that.session.kill_kernel();
235 }
237 }
236 // if we are autosaving, trigger an autosave on nav-away.
238 // if we are autosaving, trigger an autosave on nav-away.
237 // still warn, because if we don't the autosave may fail.
239 // still warn, because if we don't the autosave may fail.
238 if (that.dirty) {
240 if (that.dirty) {
239 if ( that.autosave_interval ) {
241 if ( that.autosave_interval ) {
240 // schedule autosave in a timeout
242 // schedule autosave in a timeout
241 // this gives you a chance to forcefully discard changes
243 // this gives you a chance to forcefully discard changes
242 // by reloading the page if you *really* want to.
244 // by reloading the page if you *really* want to.
243 // the timer doesn't start until you *dismiss* the dialog.
245 // the timer doesn't start until you *dismiss* the dialog.
244 setTimeout(function () {
246 setTimeout(function () {
245 if (that.dirty) {
247 if (that.dirty) {
246 that.save_notebook();
248 that.save_notebook();
247 }
249 }
248 }, 1000);
250 }, 1000);
249 return "Autosave in progress, latest changes may be lost.";
251 return "Autosave in progress, latest changes may be lost.";
250 } else {
252 } else {
251 return "Unsaved changes will be lost.";
253 return "Unsaved changes will be lost.";
252 }
254 }
253 }
255 }
254 // Null is the *only* return value that will make the browser not
256 // Null is the *only* return value that will make the browser not
255 // pop up the "don't leave" dialog.
257 // pop up the "don't leave" dialog.
256 return null;
258 return null;
257 };
259 };
258 };
260 };
259
261
260 /**
262 /**
261 * Set the dirty flag, and trigger the set_dirty.Notebook event
263 * Set the dirty flag, and trigger the set_dirty.Notebook event
262 *
264 *
263 * @method set_dirty
265 * @method set_dirty
264 */
266 */
265 Notebook.prototype.set_dirty = function (value) {
267 Notebook.prototype.set_dirty = function (value) {
266 if (value === undefined) {
268 if (value === undefined) {
267 value = true;
269 value = true;
268 }
270 }
269 if (this.dirty == value) {
271 if (this.dirty == value) {
270 return;
272 return;
271 }
273 }
272 this.events.trigger('set_dirty.Notebook', {value: value});
274 this.events.trigger('set_dirty.Notebook', {value: value});
273 };
275 };
274
276
275 /**
277 /**
276 * Scroll the top of the page to a given cell.
278 * Scroll the top of the page to a given cell.
277 *
279 *
278 * @method scroll_to_cell
280 * @method scroll_to_cell
279 * @param {Number} cell_number An index of the cell to view
281 * @param {Number} cell_number An index of the cell to view
280 * @param {Number} time Animation time in milliseconds
282 * @param {Number} time Animation time in milliseconds
281 * @return {Number} Pixel offset from the top of the container
283 * @return {Number} Pixel offset from the top of the container
282 */
284 */
283 Notebook.prototype.scroll_to_cell = function (cell_number, time) {
285 Notebook.prototype.scroll_to_cell = function (cell_number, time) {
284 var cells = this.get_cells();
286 var cells = this.get_cells();
285 time = time || 0;
287 time = time || 0;
286 cell_number = Math.min(cells.length-1,cell_number);
288 cell_number = Math.min(cells.length-1,cell_number);
287 cell_number = Math.max(0 ,cell_number);
289 cell_number = Math.max(0 ,cell_number);
288 var scroll_value = cells[cell_number].element.position().top-cells[0].element.position().top ;
290 var scroll_value = cells[cell_number].element.position().top-cells[0].element.position().top ;
289 this.element.animate({scrollTop:scroll_value}, time);
291 this.element.animate({scrollTop:scroll_value}, time);
290 return scroll_value;
292 return scroll_value;
291 };
293 };
292
294
293 /**
295 /**
294 * Scroll to the bottom of the page.
296 * Scroll to the bottom of the page.
295 *
297 *
296 * @method scroll_to_bottom
298 * @method scroll_to_bottom
297 */
299 */
298 Notebook.prototype.scroll_to_bottom = function () {
300 Notebook.prototype.scroll_to_bottom = function () {
299 this.element.animate({scrollTop:this.element.get(0).scrollHeight}, 0);
301 this.element.animate({scrollTop:this.element.get(0).scrollHeight}, 0);
300 };
302 };
301
303
302 /**
304 /**
303 * Scroll to the top of the page.
305 * Scroll to the top of the page.
304 *
306 *
305 * @method scroll_to_top
307 * @method scroll_to_top
306 */
308 */
307 Notebook.prototype.scroll_to_top = function () {
309 Notebook.prototype.scroll_to_top = function () {
308 this.element.animate({scrollTop:0}, 0);
310 this.element.animate({scrollTop:0}, 0);
309 };
311 };
310
312
311 // Edit Notebook metadata
313 // Edit Notebook metadata
312
314
313 Notebook.prototype.edit_metadata = function () {
315 Notebook.prototype.edit_metadata = function () {
314 var that = this;
316 var that = this;
315 dialog.edit_metadata(this.metadata, function (md) {
317 dialog.edit_metadata(this.metadata, function (md) {
316 that.metadata = md;
318 that.metadata = md;
317 }, 'Notebook');
319 }, 'Notebook');
318 };
320 };
319
321
320 // Cell indexing, retrieval, etc.
322 // Cell indexing, retrieval, etc.
321
323
322 /**
324 /**
323 * Get all cell elements in the notebook.
325 * Get all cell elements in the notebook.
324 *
326 *
325 * @method get_cell_elements
327 * @method get_cell_elements
326 * @return {jQuery} A selector of all cell elements
328 * @return {jQuery} A selector of all cell elements
327 */
329 */
328 Notebook.prototype.get_cell_elements = function () {
330 Notebook.prototype.get_cell_elements = function () {
329 return this.container.children("div.cell");
331 return this.container.children("div.cell");
330 };
332 };
331
333
332 /**
334 /**
333 * Get a particular cell element.
335 * Get a particular cell element.
334 *
336 *
335 * @method get_cell_element
337 * @method get_cell_element
336 * @param {Number} index An index of a cell to select
338 * @param {Number} index An index of a cell to select
337 * @return {jQuery} A selector of the given cell.
339 * @return {jQuery} A selector of the given cell.
338 */
340 */
339 Notebook.prototype.get_cell_element = function (index) {
341 Notebook.prototype.get_cell_element = function (index) {
340 var result = null;
342 var result = null;
341 var e = this.get_cell_elements().eq(index);
343 var e = this.get_cell_elements().eq(index);
342 if (e.length !== 0) {
344 if (e.length !== 0) {
343 result = e;
345 result = e;
344 }
346 }
345 return result;
347 return result;
346 };
348 };
347
349
348 /**
350 /**
349 * Try to get a particular cell by msg_id.
351 * Try to get a particular cell by msg_id.
350 *
352 *
351 * @method get_msg_cell
353 * @method get_msg_cell
352 * @param {String} msg_id A message UUID
354 * @param {String} msg_id A message UUID
353 * @return {Cell} Cell or null if no cell was found.
355 * @return {Cell} Cell or null if no cell was found.
354 */
356 */
355 Notebook.prototype.get_msg_cell = function (msg_id) {
357 Notebook.prototype.get_msg_cell = function (msg_id) {
356 return codecell.CodeCell.msg_cells[msg_id] || null;
358 return codecell.CodeCell.msg_cells[msg_id] || null;
357 };
359 };
358
360
359 /**
361 /**
360 * Count the cells in this notebook.
362 * Count the cells in this notebook.
361 *
363 *
362 * @method ncells
364 * @method ncells
363 * @return {Number} The number of cells in this notebook
365 * @return {Number} The number of cells in this notebook
364 */
366 */
365 Notebook.prototype.ncells = function () {
367 Notebook.prototype.ncells = function () {
366 return this.get_cell_elements().length;
368 return this.get_cell_elements().length;
367 };
369 };
368
370
369 /**
371 /**
370 * Get all Cell objects in this notebook.
372 * Get all Cell objects in this notebook.
371 *
373 *
372 * @method get_cells
374 * @method get_cells
373 * @return {Array} This notebook's Cell objects
375 * @return {Array} This notebook's Cell objects
374 */
376 */
375 // TODO: we are often calling cells as cells()[i], which we should optimize
377 // TODO: we are often calling cells as cells()[i], which we should optimize
376 // to cells(i) or a new method.
378 // to cells(i) or a new method.
377 Notebook.prototype.get_cells = function () {
379 Notebook.prototype.get_cells = function () {
378 return this.get_cell_elements().toArray().map(function (e) {
380 return this.get_cell_elements().toArray().map(function (e) {
379 return $(e).data("cell");
381 return $(e).data("cell");
380 });
382 });
381 };
383 };
382
384
383 /**
385 /**
384 * Get a Cell object from this notebook.
386 * Get a Cell object from this notebook.
385 *
387 *
386 * @method get_cell
388 * @method get_cell
387 * @param {Number} index An index of a cell to retrieve
389 * @param {Number} index An index of a cell to retrieve
388 * @return {Cell} A particular cell
390 * @return {Cell} A particular cell
389 */
391 */
390 Notebook.prototype.get_cell = function (index) {
392 Notebook.prototype.get_cell = function (index) {
391 var result = null;
393 var result = null;
392 var ce = this.get_cell_element(index);
394 var ce = this.get_cell_element(index);
393 if (ce !== null) {
395 if (ce !== null) {
394 result = ce.data('cell');
396 result = ce.data('cell');
395 }
397 }
396 return result;
398 return result;
397 };
399 };
398
400
399 /**
401 /**
400 * Get the cell below a given cell.
402 * Get the cell below a given cell.
401 *
403 *
402 * @method get_next_cell
404 * @method get_next_cell
403 * @param {Cell} cell The provided cell
405 * @param {Cell} cell The provided cell
404 * @return {Cell} The next cell
406 * @return {Cell} The next cell
405 */
407 */
406 Notebook.prototype.get_next_cell = function (cell) {
408 Notebook.prototype.get_next_cell = function (cell) {
407 var result = null;
409 var result = null;
408 var index = this.find_cell_index(cell);
410 var index = this.find_cell_index(cell);
409 if (this.is_valid_cell_index(index+1)) {
411 if (this.is_valid_cell_index(index+1)) {
410 result = this.get_cell(index+1);
412 result = this.get_cell(index+1);
411 }
413 }
412 return result;
414 return result;
413 };
415 };
414
416
415 /**
417 /**
416 * Get the cell above a given cell.
418 * Get the cell above a given cell.
417 *
419 *
418 * @method get_prev_cell
420 * @method get_prev_cell
419 * @param {Cell} cell The provided cell
421 * @param {Cell} cell The provided cell
420 * @return {Cell} The previous cell
422 * @return {Cell} The previous cell
421 */
423 */
422 Notebook.prototype.get_prev_cell = function (cell) {
424 Notebook.prototype.get_prev_cell = function (cell) {
423 // TODO: off-by-one
425 // TODO: off-by-one
424 // nb.get_prev_cell(nb.get_cell(1)) is null
426 // nb.get_prev_cell(nb.get_cell(1)) is null
425 var result = null;
427 var result = null;
426 var index = this.find_cell_index(cell);
428 var index = this.find_cell_index(cell);
427 if (index !== null && index > 1) {
429 if (index !== null && index > 1) {
428 result = this.get_cell(index-1);
430 result = this.get_cell(index-1);
429 }
431 }
430 return result;
432 return result;
431 };
433 };
432
434
433 /**
435 /**
434 * Get the numeric index of a given cell.
436 * Get the numeric index of a given cell.
435 *
437 *
436 * @method find_cell_index
438 * @method find_cell_index
437 * @param {Cell} cell The provided cell
439 * @param {Cell} cell The provided cell
438 * @return {Number} The cell's numeric index
440 * @return {Number} The cell's numeric index
439 */
441 */
440 Notebook.prototype.find_cell_index = function (cell) {
442 Notebook.prototype.find_cell_index = function (cell) {
441 var result = null;
443 var result = null;
442 this.get_cell_elements().filter(function (index) {
444 this.get_cell_elements().filter(function (index) {
443 if ($(this).data("cell") === cell) {
445 if ($(this).data("cell") === cell) {
444 result = index;
446 result = index;
445 }
447 }
446 });
448 });
447 return result;
449 return result;
448 };
450 };
449
451
450 /**
452 /**
451 * Get a given index , or the selected index if none is provided.
453 * Get a given index , or the selected index if none is provided.
452 *
454 *
453 * @method index_or_selected
455 * @method index_or_selected
454 * @param {Number} index A cell's index
456 * @param {Number} index A cell's index
455 * @return {Number} The given index, or selected index if none is provided.
457 * @return {Number} The given index, or selected index if none is provided.
456 */
458 */
457 Notebook.prototype.index_or_selected = function (index) {
459 Notebook.prototype.index_or_selected = function (index) {
458 var i;
460 var i;
459 if (index === undefined || index === null) {
461 if (index === undefined || index === null) {
460 i = this.get_selected_index();
462 i = this.get_selected_index();
461 if (i === null) {
463 if (i === null) {
462 i = 0;
464 i = 0;
463 }
465 }
464 } else {
466 } else {
465 i = index;
467 i = index;
466 }
468 }
467 return i;
469 return i;
468 };
470 };
469
471
470 /**
472 /**
471 * Get the currently selected cell.
473 * Get the currently selected cell.
472 * @method get_selected_cell
474 * @method get_selected_cell
473 * @return {Cell} The selected cell
475 * @return {Cell} The selected cell
474 */
476 */
475 Notebook.prototype.get_selected_cell = function () {
477 Notebook.prototype.get_selected_cell = function () {
476 var index = this.get_selected_index();
478 var index = this.get_selected_index();
477 return this.get_cell(index);
479 return this.get_cell(index);
478 };
480 };
479
481
480 /**
482 /**
481 * Check whether a cell index is valid.
483 * Check whether a cell index is valid.
482 *
484 *
483 * @method is_valid_cell_index
485 * @method is_valid_cell_index
484 * @param {Number} index A cell index
486 * @param {Number} index A cell index
485 * @return True if the index is valid, false otherwise
487 * @return True if the index is valid, false otherwise
486 */
488 */
487 Notebook.prototype.is_valid_cell_index = function (index) {
489 Notebook.prototype.is_valid_cell_index = function (index) {
488 if (index !== null && index >= 0 && index < this.ncells()) {
490 if (index !== null && index >= 0 && index < this.ncells()) {
489 return true;
491 return true;
490 } else {
492 } else {
491 return false;
493 return false;
492 }
494 }
493 };
495 };
494
496
495 /**
497 /**
496 * Get the index of the currently selected cell.
498 * Get the index of the currently selected cell.
497
499
498 * @method get_selected_index
500 * @method get_selected_index
499 * @return {Number} The selected cell's numeric index
501 * @return {Number} The selected cell's numeric index
500 */
502 */
501 Notebook.prototype.get_selected_index = function () {
503 Notebook.prototype.get_selected_index = function () {
502 var result = null;
504 var result = null;
503 this.get_cell_elements().filter(function (index) {
505 this.get_cell_elements().filter(function (index) {
504 if ($(this).data("cell").selected === true) {
506 if ($(this).data("cell").selected === true) {
505 result = index;
507 result = index;
506 }
508 }
507 });
509 });
508 return result;
510 return result;
509 };
511 };
510
512
511
513
512 // Cell selection.
514 // Cell selection.
513
515
514 /**
516 /**
515 * Programmatically select a cell.
517 * Programmatically select a cell.
516 *
518 *
517 * @method select
519 * @method select
518 * @param {Number} index A cell's index
520 * @param {Number} index A cell's index
519 * @return {Notebook} This notebook
521 * @return {Notebook} This notebook
520 */
522 */
521 Notebook.prototype.select = function (index) {
523 Notebook.prototype.select = function (index) {
522 if (this.is_valid_cell_index(index)) {
524 if (this.is_valid_cell_index(index)) {
523 var sindex = this.get_selected_index();
525 var sindex = this.get_selected_index();
524 if (sindex !== null && index !== sindex) {
526 if (sindex !== null && index !== sindex) {
525 // If we are about to select a different cell, make sure we are
527 // If we are about to select a different cell, make sure we are
526 // first in command mode.
528 // first in command mode.
527 if (this.mode !== 'command') {
529 if (this.mode !== 'command') {
528 this.command_mode();
530 this.command_mode();
529 }
531 }
530 this.get_cell(sindex).unselect();
532 this.get_cell(sindex).unselect();
531 }
533 }
532 var cell = this.get_cell(index);
534 var cell = this.get_cell(index);
533 cell.select();
535 cell.select();
534 if (cell.cell_type === 'heading') {
536 if (cell.cell_type === 'heading') {
535 this.events.trigger('selected_cell_type_changed.Notebook',
537 this.events.trigger('selected_cell_type_changed.Notebook',
536 {'cell_type':cell.cell_type,level:cell.level}
538 {'cell_type':cell.cell_type,level:cell.level}
537 );
539 );
538 } else {
540 } else {
539 this.events.trigger('selected_cell_type_changed.Notebook',
541 this.events.trigger('selected_cell_type_changed.Notebook',
540 {'cell_type':cell.cell_type}
542 {'cell_type':cell.cell_type}
541 );
543 );
542 }
544 }
543 }
545 }
544 return this;
546 return this;
545 };
547 };
546
548
547 /**
549 /**
548 * Programmatically select the next cell.
550 * Programmatically select the next cell.
549 *
551 *
550 * @method select_next
552 * @method select_next
551 * @return {Notebook} This notebook
553 * @return {Notebook} This notebook
552 */
554 */
553 Notebook.prototype.select_next = function () {
555 Notebook.prototype.select_next = function () {
554 var index = this.get_selected_index();
556 var index = this.get_selected_index();
555 this.select(index+1);
557 this.select(index+1);
556 return this;
558 return this;
557 };
559 };
558
560
559 /**
561 /**
560 * Programmatically select the previous cell.
562 * Programmatically select the previous cell.
561 *
563 *
562 * @method select_prev
564 * @method select_prev
563 * @return {Notebook} This notebook
565 * @return {Notebook} This notebook
564 */
566 */
565 Notebook.prototype.select_prev = function () {
567 Notebook.prototype.select_prev = function () {
566 var index = this.get_selected_index();
568 var index = this.get_selected_index();
567 this.select(index-1);
569 this.select(index-1);
568 return this;
570 return this;
569 };
571 };
570
572
571
573
572 // Edit/Command mode
574 // Edit/Command mode
573
575
574 /**
576 /**
575 * Gets the index of the cell that is in edit mode.
577 * Gets the index of the cell that is in edit mode.
576 *
578 *
577 * @method get_edit_index
579 * @method get_edit_index
578 *
580 *
579 * @return index {int}
581 * @return index {int}
580 **/
582 **/
581 Notebook.prototype.get_edit_index = function () {
583 Notebook.prototype.get_edit_index = function () {
582 var result = null;
584 var result = null;
583 this.get_cell_elements().filter(function (index) {
585 this.get_cell_elements().filter(function (index) {
584 if ($(this).data("cell").mode === 'edit') {
586 if ($(this).data("cell").mode === 'edit') {
585 result = index;
587 result = index;
586 }
588 }
587 });
589 });
588 return result;
590 return result;
589 };
591 };
590
592
591 /**
593 /**
592 * Handle when a a cell blurs and the notebook should enter command mode.
594 * Handle when a a cell blurs and the notebook should enter command mode.
593 *
595 *
594 * @method handle_command_mode
596 * @method handle_command_mode
595 * @param [cell] {Cell} Cell to enter command mode on.
597 * @param [cell] {Cell} Cell to enter command mode on.
596 **/
598 **/
597 Notebook.prototype.handle_command_mode = function (cell) {
599 Notebook.prototype.handle_command_mode = function (cell) {
598 if (this.mode !== 'command') {
600 if (this.mode !== 'command') {
599 cell.command_mode();
601 cell.command_mode();
600 this.mode = 'command';
602 this.mode = 'command';
601 this.events.trigger('command_mode.Notebook');
603 this.events.trigger('command_mode.Notebook');
602 this.keyboard_manager.command_mode();
604 this.keyboard_manager.command_mode();
603 }
605 }
604 };
606 };
605
607
606 /**
608 /**
607 * Make the notebook enter command mode.
609 * Make the notebook enter command mode.
608 *
610 *
609 * @method command_mode
611 * @method command_mode
610 **/
612 **/
611 Notebook.prototype.command_mode = function () {
613 Notebook.prototype.command_mode = function () {
612 var cell = this.get_cell(this.get_edit_index());
614 var cell = this.get_cell(this.get_edit_index());
613 if (cell && this.mode !== 'command') {
615 if (cell && this.mode !== 'command') {
614 // We don't call cell.command_mode, but rather call cell.focus_cell()
616 // We don't call cell.command_mode, but rather call cell.focus_cell()
615 // which will blur and CM editor and trigger the call to
617 // which will blur and CM editor and trigger the call to
616 // handle_command_mode.
618 // handle_command_mode.
617 cell.focus_cell();
619 cell.focus_cell();
618 }
620 }
619 };
621 };
620
622
621 /**
623 /**
622 * Handle when a cell fires it's edit_mode event.
624 * Handle when a cell fires it's edit_mode event.
623 *
625 *
624 * @method handle_edit_mode
626 * @method handle_edit_mode
625 * @param [cell] {Cell} Cell to enter edit mode on.
627 * @param [cell] {Cell} Cell to enter edit mode on.
626 **/
628 **/
627 Notebook.prototype.handle_edit_mode = function (cell) {
629 Notebook.prototype.handle_edit_mode = function (cell) {
628 if (cell && this.mode !== 'edit') {
630 if (cell && this.mode !== 'edit') {
629 cell.edit_mode();
631 cell.edit_mode();
630 this.mode = 'edit';
632 this.mode = 'edit';
631 this.events.trigger('edit_mode.Notebook');
633 this.events.trigger('edit_mode.Notebook');
632 this.keyboard_manager.edit_mode();
634 this.keyboard_manager.edit_mode();
633 }
635 }
634 };
636 };
635
637
636 /**
638 /**
637 * Make a cell enter edit mode.
639 * Make a cell enter edit mode.
638 *
640 *
639 * @method edit_mode
641 * @method edit_mode
640 **/
642 **/
641 Notebook.prototype.edit_mode = function () {
643 Notebook.prototype.edit_mode = function () {
642 var cell = this.get_selected_cell();
644 var cell = this.get_selected_cell();
643 if (cell && this.mode !== 'edit') {
645 if (cell && this.mode !== 'edit') {
644 cell.unrender();
646 cell.unrender();
645 cell.focus_editor();
647 cell.focus_editor();
646 }
648 }
647 };
649 };
648
650
649 /**
651 /**
650 * Focus the currently selected cell.
652 * Focus the currently selected cell.
651 *
653 *
652 * @method focus_cell
654 * @method focus_cell
653 **/
655 **/
654 Notebook.prototype.focus_cell = function () {
656 Notebook.prototype.focus_cell = function () {
655 var cell = this.get_selected_cell();
657 var cell = this.get_selected_cell();
656 if (cell === null) {return;} // No cell is selected
658 if (cell === null) {return;} // No cell is selected
657 cell.focus_cell();
659 cell.focus_cell();
658 };
660 };
659
661
660 // Cell movement
662 // Cell movement
661
663
662 /**
664 /**
663 * Move given (or selected) cell up and select it.
665 * Move given (or selected) cell up and select it.
664 *
666 *
665 * @method move_cell_up
667 * @method move_cell_up
666 * @param [index] {integer} cell index
668 * @param [index] {integer} cell index
667 * @return {Notebook} This notebook
669 * @return {Notebook} This notebook
668 **/
670 **/
669 Notebook.prototype.move_cell_up = function (index) {
671 Notebook.prototype.move_cell_up = function (index) {
670 var i = this.index_or_selected(index);
672 var i = this.index_or_selected(index);
671 if (this.is_valid_cell_index(i) && i > 0) {
673 if (this.is_valid_cell_index(i) && i > 0) {
672 var pivot = this.get_cell_element(i-1);
674 var pivot = this.get_cell_element(i-1);
673 var tomove = this.get_cell_element(i);
675 var tomove = this.get_cell_element(i);
674 if (pivot !== null && tomove !== null) {
676 if (pivot !== null && tomove !== null) {
675 tomove.detach();
677 tomove.detach();
676 pivot.before(tomove);
678 pivot.before(tomove);
677 this.select(i-1);
679 this.select(i-1);
678 var cell = this.get_selected_cell();
680 var cell = this.get_selected_cell();
679 cell.focus_cell();
681 cell.focus_cell();
680 }
682 }
681 this.set_dirty(true);
683 this.set_dirty(true);
682 }
684 }
683 return this;
685 return this;
684 };
686 };
685
687
686
688
687 /**
689 /**
688 * Move given (or selected) cell down and select it
690 * Move given (or selected) cell down and select it
689 *
691 *
690 * @method move_cell_down
692 * @method move_cell_down
691 * @param [index] {integer} cell index
693 * @param [index] {integer} cell index
692 * @return {Notebook} This notebook
694 * @return {Notebook} This notebook
693 **/
695 **/
694 Notebook.prototype.move_cell_down = function (index) {
696 Notebook.prototype.move_cell_down = function (index) {
695 var i = this.index_or_selected(index);
697 var i = this.index_or_selected(index);
696 if (this.is_valid_cell_index(i) && this.is_valid_cell_index(i+1)) {
698 if (this.is_valid_cell_index(i) && this.is_valid_cell_index(i+1)) {
697 var pivot = this.get_cell_element(i+1);
699 var pivot = this.get_cell_element(i+1);
698 var tomove = this.get_cell_element(i);
700 var tomove = this.get_cell_element(i);
699 if (pivot !== null && tomove !== null) {
701 if (pivot !== null && tomove !== null) {
700 tomove.detach();
702 tomove.detach();
701 pivot.after(tomove);
703 pivot.after(tomove);
702 this.select(i+1);
704 this.select(i+1);
703 var cell = this.get_selected_cell();
705 var cell = this.get_selected_cell();
704 cell.focus_cell();
706 cell.focus_cell();
705 }
707 }
706 }
708 }
707 this.set_dirty();
709 this.set_dirty();
708 return this;
710 return this;
709 };
711 };
710
712
711
713
712 // Insertion, deletion.
714 // Insertion, deletion.
713
715
714 /**
716 /**
715 * Delete a cell from the notebook.
717 * Delete a cell from the notebook.
716 *
718 *
717 * @method delete_cell
719 * @method delete_cell
718 * @param [index] A cell's numeric index
720 * @param [index] A cell's numeric index
719 * @return {Notebook} This notebook
721 * @return {Notebook} This notebook
720 */
722 */
721 Notebook.prototype.delete_cell = function (index) {
723 Notebook.prototype.delete_cell = function (index) {
722 var i = this.index_or_selected(index);
724 var i = this.index_or_selected(index);
723 var cell = this.get_selected_cell();
725 var cell = this.get_selected_cell();
724 this.undelete_backup = cell.toJSON();
726 this.undelete_backup = cell.toJSON();
725 $('#undelete_cell').removeClass('disabled');
727 $('#undelete_cell').removeClass('disabled');
726 if (this.is_valid_cell_index(i)) {
728 if (this.is_valid_cell_index(i)) {
727 var old_ncells = this.ncells();
729 var old_ncells = this.ncells();
728 var ce = this.get_cell_element(i);
730 var ce = this.get_cell_element(i);
729 ce.remove();
731 ce.remove();
730 if (i === 0) {
732 if (i === 0) {
731 // Always make sure we have at least one cell.
733 // Always make sure we have at least one cell.
732 if (old_ncells === 1) {
734 if (old_ncells === 1) {
733 this.insert_cell_below('code');
735 this.insert_cell_below('code');
734 }
736 }
735 this.select(0);
737 this.select(0);
736 this.undelete_index = 0;
738 this.undelete_index = 0;
737 this.undelete_below = false;
739 this.undelete_below = false;
738 } else if (i === old_ncells-1 && i !== 0) {
740 } else if (i === old_ncells-1 && i !== 0) {
739 this.select(i-1);
741 this.select(i-1);
740 this.undelete_index = i - 1;
742 this.undelete_index = i - 1;
741 this.undelete_below = true;
743 this.undelete_below = true;
742 } else {
744 } else {
743 this.select(i);
745 this.select(i);
744 this.undelete_index = i;
746 this.undelete_index = i;
745 this.undelete_below = false;
747 this.undelete_below = false;
746 }
748 }
747 this.events.trigger('delete.Cell', {'cell': cell, 'index': i});
749 this.events.trigger('delete.Cell', {'cell': cell, 'index': i});
748 this.set_dirty(true);
750 this.set_dirty(true);
749 }
751 }
750 return this;
752 return this;
751 };
753 };
752
754
753 /**
755 /**
754 * Restore the most recently deleted cell.
756 * Restore the most recently deleted cell.
755 *
757 *
756 * @method undelete
758 * @method undelete
757 */
759 */
758 Notebook.prototype.undelete_cell = function() {
760 Notebook.prototype.undelete_cell = function() {
759 if (this.undelete_backup !== null && this.undelete_index !== null) {
761 if (this.undelete_backup !== null && this.undelete_index !== null) {
760 var current_index = this.get_selected_index();
762 var current_index = this.get_selected_index();
761 if (this.undelete_index < current_index) {
763 if (this.undelete_index < current_index) {
762 current_index = current_index + 1;
764 current_index = current_index + 1;
763 }
765 }
764 if (this.undelete_index >= this.ncells()) {
766 if (this.undelete_index >= this.ncells()) {
765 this.select(this.ncells() - 1);
767 this.select(this.ncells() - 1);
766 }
768 }
767 else {
769 else {
768 this.select(this.undelete_index);
770 this.select(this.undelete_index);
769 }
771 }
770 var cell_data = this.undelete_backup;
772 var cell_data = this.undelete_backup;
771 var new_cell = null;
773 var new_cell = null;
772 if (this.undelete_below) {
774 if (this.undelete_below) {
773 new_cell = this.insert_cell_below(cell_data.cell_type);
775 new_cell = this.insert_cell_below(cell_data.cell_type);
774 } else {
776 } else {
775 new_cell = this.insert_cell_above(cell_data.cell_type);
777 new_cell = this.insert_cell_above(cell_data.cell_type);
776 }
778 }
777 new_cell.fromJSON(cell_data);
779 new_cell.fromJSON(cell_data);
778 if (this.undelete_below) {
780 if (this.undelete_below) {
779 this.select(current_index+1);
781 this.select(current_index+1);
780 } else {
782 } else {
781 this.select(current_index);
783 this.select(current_index);
782 }
784 }
783 this.undelete_backup = null;
785 this.undelete_backup = null;
784 this.undelete_index = null;
786 this.undelete_index = null;
785 }
787 }
786 $('#undelete_cell').addClass('disabled');
788 $('#undelete_cell').addClass('disabled');
787 };
789 };
788
790
789 /**
791 /**
790 * Insert a cell so that after insertion the cell is at given index.
792 * Insert a cell so that after insertion the cell is at given index.
791 *
793 *
792 * If cell type is not provided, it will default to the type of the
794 * If cell type is not provided, it will default to the type of the
793 * currently active cell.
795 * currently active cell.
794 *
796 *
795 * Similar to insert_above, but index parameter is mandatory
797 * Similar to insert_above, but index parameter is mandatory
796 *
798 *
797 * Index will be brought back into the accessible range [0,n]
799 * Index will be brought back into the accessible range [0,n]
798 *
800 *
799 * @method insert_cell_at_index
801 * @method insert_cell_at_index
800 * @param [type] {string} in ['code','markdown','heading'], defaults to 'code'
802 * @param [type] {string} in ['code','markdown','heading'], defaults to 'code'
801 * @param [index] {int} a valid index where to insert cell
803 * @param [index] {int} a valid index where to insert cell
802 *
804 *
803 * @return cell {cell|null} created cell or null
805 * @return cell {cell|null} created cell or null
804 **/
806 **/
805 Notebook.prototype.insert_cell_at_index = function(type, index){
807 Notebook.prototype.insert_cell_at_index = function(type, index){
806
808
807 var ncells = this.ncells();
809 var ncells = this.ncells();
808 index = Math.min(index,ncells);
810 index = Math.min(index,ncells);
809 index = Math.max(index,0);
811 index = Math.max(index,0);
810 var cell = null;
812 var cell = null;
811 type = type || this.get_selected_cell().cell_type;
813 type = type || this.get_selected_cell().cell_type;
812
814
813 if (ncells === 0 || this.is_valid_cell_index(index) || index === ncells) {
815 if (ncells === 0 || this.is_valid_cell_index(index) || index === ncells) {
814 var cell_options = {
816 var cell_options = {
815 events: this.events,
817 events: this.events,
816 config: this.config,
818 config: this.config,
817 keyboard_manager: this.keyboard_manager,
819 keyboard_manager: this.keyboard_manager,
818 notebook: this
820 notebook: this
819 };
821 };
820 if (type === 'code') {
822 if (type === 'code') {
821 cell = new codecell.CodeCell(this.kernel, cell_options);
823 cell = new codecell.CodeCell(this.kernel, cell_options);
822 cell.set_input_prompt();
824 cell.set_input_prompt();
823 } else if (type === 'markdown') {
825 } else if (type === 'markdown') {
824 cell = new textcell.MarkdownCell(cell_options);
826 cell = new textcell.MarkdownCell(cell_options);
825 } else if (type === 'raw') {
827 } else if (type === 'raw') {
826 cell = new textcell.RawCell(cell_options);
828 cell = new textcell.RawCell(cell_options);
827 } else if (type === 'heading') {
829 } else if (type === 'heading') {
828 cell = new textcell.HeadingCell(cell_options);
830 cell = new textcell.HeadingCell(cell_options);
829 }
831 }
830
832
831 if(this._insert_element_at_index(cell.element,index)) {
833 if(this._insert_element_at_index(cell.element,index)) {
832 cell.render();
834 cell.render();
833 this.events.trigger('create.Cell', {'cell': cell, 'index': index});
835 this.events.trigger('create.Cell', {'cell': cell, 'index': index});
834 cell.refresh();
836 cell.refresh();
835 // We used to select the cell after we refresh it, but there
837 // We used to select the cell after we refresh it, but there
836 // are now cases were this method is called where select is
838 // are now cases were this method is called where select is
837 // not appropriate. The selection logic should be handled by the
839 // not appropriate. The selection logic should be handled by the
838 // caller of the the top level insert_cell methods.
840 // caller of the the top level insert_cell methods.
839 this.set_dirty(true);
841 this.set_dirty(true);
840 }
842 }
841 }
843 }
842 return cell;
844 return cell;
843
845
844 };
846 };
845
847
846 /**
848 /**
847 * Insert an element at given cell index.
849 * Insert an element at given cell index.
848 *
850 *
849 * @method _insert_element_at_index
851 * @method _insert_element_at_index
850 * @param element {dom element} a cell element
852 * @param element {dom element} a cell element
851 * @param [index] {int} a valid index where to inser cell
853 * @param [index] {int} a valid index where to inser cell
852 * @private
854 * @private
853 *
855 *
854 * return true if everything whent fine.
856 * return true if everything whent fine.
855 **/
857 **/
856 Notebook.prototype._insert_element_at_index = function(element, index){
858 Notebook.prototype._insert_element_at_index = function(element, index){
857 if (element === undefined){
859 if (element === undefined){
858 return false;
860 return false;
859 }
861 }
860
862
861 var ncells = this.ncells();
863 var ncells = this.ncells();
862
864
863 if (ncells === 0) {
865 if (ncells === 0) {
864 // special case append if empty
866 // special case append if empty
865 this.element.find('div.end_space').before(element);
867 this.element.find('div.end_space').before(element);
866 } else if ( ncells === index ) {
868 } else if ( ncells === index ) {
867 // special case append it the end, but not empty
869 // special case append it the end, but not empty
868 this.get_cell_element(index-1).after(element);
870 this.get_cell_element(index-1).after(element);
869 } else if (this.is_valid_cell_index(index)) {
871 } else if (this.is_valid_cell_index(index)) {
870 // otherwise always somewhere to append to
872 // otherwise always somewhere to append to
871 this.get_cell_element(index).before(element);
873 this.get_cell_element(index).before(element);
872 } else {
874 } else {
873 return false;
875 return false;
874 }
876 }
875
877
876 if (this.undelete_index !== null && index <= this.undelete_index) {
878 if (this.undelete_index !== null && index <= this.undelete_index) {
877 this.undelete_index = this.undelete_index + 1;
879 this.undelete_index = this.undelete_index + 1;
878 this.set_dirty(true);
880 this.set_dirty(true);
879 }
881 }
880 return true;
882 return true;
881 };
883 };
882
884
883 /**
885 /**
884 * Insert a cell of given type above given index, or at top
886 * Insert a cell of given type above given index, or at top
885 * of notebook if index smaller than 0.
887 * of notebook if index smaller than 0.
886 *
888 *
887 * default index value is the one of currently selected cell
889 * default index value is the one of currently selected cell
888 *
890 *
889 * @method insert_cell_above
891 * @method insert_cell_above
890 * @param [type] {string} cell type
892 * @param [type] {string} cell type
891 * @param [index] {integer}
893 * @param [index] {integer}
892 *
894 *
893 * @return handle to created cell or null
895 * @return handle to created cell or null
894 **/
896 **/
895 Notebook.prototype.insert_cell_above = function (type, index) {
897 Notebook.prototype.insert_cell_above = function (type, index) {
896 index = this.index_or_selected(index);
898 index = this.index_or_selected(index);
897 return this.insert_cell_at_index(type, index);
899 return this.insert_cell_at_index(type, index);
898 };
900 };
899
901
900 /**
902 /**
901 * Insert a cell of given type below given index, or at bottom
903 * Insert a cell of given type below given index, or at bottom
902 * of notebook if index greater than number of cells
904 * of notebook if index greater than number of cells
903 *
905 *
904 * default index value is the one of currently selected cell
906 * default index value is the one of currently selected cell
905 *
907 *
906 * @method insert_cell_below
908 * @method insert_cell_below
907 * @param [type] {string} cell type
909 * @param [type] {string} cell type
908 * @param [index] {integer}
910 * @param [index] {integer}
909 *
911 *
910 * @return handle to created cell or null
912 * @return handle to created cell or null
911 *
913 *
912 **/
914 **/
913 Notebook.prototype.insert_cell_below = function (type, index) {
915 Notebook.prototype.insert_cell_below = function (type, index) {
914 index = this.index_or_selected(index);
916 index = this.index_or_selected(index);
915 return this.insert_cell_at_index(type, index+1);
917 return this.insert_cell_at_index(type, index+1);
916 };
918 };
917
919
918
920
919 /**
921 /**
920 * Insert cell at end of notebook
922 * Insert cell at end of notebook
921 *
923 *
922 * @method insert_cell_at_bottom
924 * @method insert_cell_at_bottom
923 * @param {String} type cell type
925 * @param {String} type cell type
924 *
926 *
925 * @return the added cell; or null
927 * @return the added cell; or null
926 **/
928 **/
927 Notebook.prototype.insert_cell_at_bottom = function (type){
929 Notebook.prototype.insert_cell_at_bottom = function (type){
928 var len = this.ncells();
930 var len = this.ncells();
929 return this.insert_cell_below(type,len-1);
931 return this.insert_cell_below(type,len-1);
930 };
932 };
931
933
932 /**
934 /**
933 * Turn a cell into a code cell.
935 * Turn a cell into a code cell.
934 *
936 *
935 * @method to_code
937 * @method to_code
936 * @param {Number} [index] A cell's index
938 * @param {Number} [index] A cell's index
937 */
939 */
938 Notebook.prototype.to_code = function (index) {
940 Notebook.prototype.to_code = function (index) {
939 var i = this.index_or_selected(index);
941 var i = this.index_or_selected(index);
940 if (this.is_valid_cell_index(i)) {
942 if (this.is_valid_cell_index(i)) {
941 var source_element = this.get_cell_element(i);
943 var source_element = this.get_cell_element(i);
942 var source_cell = source_element.data("cell");
944 var source_cell = source_element.data("cell");
943 if (!(source_cell instanceof codecell.CodeCell)) {
945 if (!(source_cell instanceof codecell.CodeCell)) {
944 var target_cell = this.insert_cell_below('code',i);
946 var target_cell = this.insert_cell_below('code',i);
945 var text = source_cell.get_text();
947 var text = source_cell.get_text();
946 if (text === source_cell.placeholder) {
948 if (text === source_cell.placeholder) {
947 text = '';
949 text = '';
948 }
950 }
949 target_cell.set_text(text);
951 target_cell.set_text(text);
950 // make this value the starting point, so that we can only undo
952 // make this value the starting point, so that we can only undo
951 // to this state, instead of a blank cell
953 // to this state, instead of a blank cell
952 target_cell.code_mirror.clearHistory();
954 target_cell.code_mirror.clearHistory();
953 source_element.remove();
955 source_element.remove();
954 this.select(i);
956 this.select(i);
955 var cursor = source_cell.code_mirror.getCursor();
957 var cursor = source_cell.code_mirror.getCursor();
956 target_cell.code_mirror.setCursor(cursor);
958 target_cell.code_mirror.setCursor(cursor);
957 this.set_dirty(true);
959 this.set_dirty(true);
958 }
960 }
959 }
961 }
960 };
962 };
961
963
962 /**
964 /**
963 * Turn a cell into a Markdown cell.
965 * Turn a cell into a Markdown cell.
964 *
966 *
965 * @method to_markdown
967 * @method to_markdown
966 * @param {Number} [index] A cell's index
968 * @param {Number} [index] A cell's index
967 */
969 */
968 Notebook.prototype.to_markdown = function (index) {
970 Notebook.prototype.to_markdown = function (index) {
969 var i = this.index_or_selected(index);
971 var i = this.index_or_selected(index);
970 if (this.is_valid_cell_index(i)) {
972 if (this.is_valid_cell_index(i)) {
971 var source_element = this.get_cell_element(i);
973 var source_element = this.get_cell_element(i);
972 var source_cell = source_element.data("cell");
974 var source_cell = source_element.data("cell");
973 if (!(source_cell instanceof textcell.MarkdownCell)) {
975 if (!(source_cell instanceof textcell.MarkdownCell)) {
974 var target_cell = this.insert_cell_below('markdown',i);
976 var target_cell = this.insert_cell_below('markdown',i);
975 var text = source_cell.get_text();
977 var text = source_cell.get_text();
976 if (text === source_cell.placeholder) {
978 if (text === source_cell.placeholder) {
977 text = '';
979 text = '';
978 }
980 }
979 // We must show the editor before setting its contents
981 // We must show the editor before setting its contents
980 target_cell.unrender();
982 target_cell.unrender();
981 target_cell.set_text(text);
983 target_cell.set_text(text);
982 // make this value the starting point, so that we can only undo
984 // make this value the starting point, so that we can only undo
983 // to this state, instead of a blank cell
985 // to this state, instead of a blank cell
984 target_cell.code_mirror.clearHistory();
986 target_cell.code_mirror.clearHistory();
985 source_element.remove();
987 source_element.remove();
986 this.select(i);
988 this.select(i);
987 if ((source_cell instanceof textcell.TextCell) && source_cell.rendered) {
989 if ((source_cell instanceof textcell.TextCell) && source_cell.rendered) {
988 target_cell.render();
990 target_cell.render();
989 }
991 }
990 var cursor = source_cell.code_mirror.getCursor();
992 var cursor = source_cell.code_mirror.getCursor();
991 target_cell.code_mirror.setCursor(cursor);
993 target_cell.code_mirror.setCursor(cursor);
992 this.set_dirty(true);
994 this.set_dirty(true);
993 }
995 }
994 }
996 }
995 };
997 };
996
998
997 /**
999 /**
998 * Turn a cell into a raw text cell.
1000 * Turn a cell into a raw text cell.
999 *
1001 *
1000 * @method to_raw
1002 * @method to_raw
1001 * @param {Number} [index] A cell's index
1003 * @param {Number} [index] A cell's index
1002 */
1004 */
1003 Notebook.prototype.to_raw = function (index) {
1005 Notebook.prototype.to_raw = function (index) {
1004 var i = this.index_or_selected(index);
1006 var i = this.index_or_selected(index);
1005 if (this.is_valid_cell_index(i)) {
1007 if (this.is_valid_cell_index(i)) {
1006 var source_element = this.get_cell_element(i);
1008 var source_element = this.get_cell_element(i);
1007 var source_cell = source_element.data("cell");
1009 var source_cell = source_element.data("cell");
1008 var target_cell = null;
1010 var target_cell = null;
1009 if (!(source_cell instanceof textcell.RawCell)) {
1011 if (!(source_cell instanceof textcell.RawCell)) {
1010 target_cell = this.insert_cell_below('raw',i);
1012 target_cell = this.insert_cell_below('raw',i);
1011 var text = source_cell.get_text();
1013 var text = source_cell.get_text();
1012 if (text === source_cell.placeholder) {
1014 if (text === source_cell.placeholder) {
1013 text = '';
1015 text = '';
1014 }
1016 }
1015 // We must show the editor before setting its contents
1017 // We must show the editor before setting its contents
1016 target_cell.unrender();
1018 target_cell.unrender();
1017 target_cell.set_text(text);
1019 target_cell.set_text(text);
1018 // make this value the starting point, so that we can only undo
1020 // make this value the starting point, so that we can only undo
1019 // to this state, instead of a blank cell
1021 // to this state, instead of a blank cell
1020 target_cell.code_mirror.clearHistory();
1022 target_cell.code_mirror.clearHistory();
1021 source_element.remove();
1023 source_element.remove();
1022 this.select(i);
1024 this.select(i);
1023 var cursor = source_cell.code_mirror.getCursor();
1025 var cursor = source_cell.code_mirror.getCursor();
1024 target_cell.code_mirror.setCursor(cursor);
1026 target_cell.code_mirror.setCursor(cursor);
1025 this.set_dirty(true);
1027 this.set_dirty(true);
1026 }
1028 }
1027 }
1029 }
1028 };
1030 };
1029
1031
1030 /**
1032 /**
1031 * Turn a cell into a heading cell.
1033 * Turn a cell into a heading cell.
1032 *
1034 *
1033 * @method to_heading
1035 * @method to_heading
1034 * @param {Number} [index] A cell's index
1036 * @param {Number} [index] A cell's index
1035 * @param {Number} [level] A heading level (e.g., 1 becomes &lt;h1&gt;)
1037 * @param {Number} [level] A heading level (e.g., 1 becomes &lt;h1&gt;)
1036 */
1038 */
1037 Notebook.prototype.to_heading = function (index, level) {
1039 Notebook.prototype.to_heading = function (index, level) {
1038 level = level || 1;
1040 level = level || 1;
1039 var i = this.index_or_selected(index);
1041 var i = this.index_or_selected(index);
1040 if (this.is_valid_cell_index(i)) {
1042 if (this.is_valid_cell_index(i)) {
1041 var source_element = this.get_cell_element(i);
1043 var source_element = this.get_cell_element(i);
1042 var source_cell = source_element.data("cell");
1044 var source_cell = source_element.data("cell");
1043 var target_cell = null;
1045 var target_cell = null;
1044 if (source_cell instanceof textcell.HeadingCell) {
1046 if (source_cell instanceof textcell.HeadingCell) {
1045 source_cell.set_level(level);
1047 source_cell.set_level(level);
1046 } else {
1048 } else {
1047 target_cell = this.insert_cell_below('heading',i);
1049 target_cell = this.insert_cell_below('heading',i);
1048 var text = source_cell.get_text();
1050 var text = source_cell.get_text();
1049 if (text === source_cell.placeholder) {
1051 if (text === source_cell.placeholder) {
1050 text = '';
1052 text = '';
1051 }
1053 }
1052 // We must show the editor before setting its contents
1054 // We must show the editor before setting its contents
1053 target_cell.set_level(level);
1055 target_cell.set_level(level);
1054 target_cell.unrender();
1056 target_cell.unrender();
1055 target_cell.set_text(text);
1057 target_cell.set_text(text);
1056 // make this value the starting point, so that we can only undo
1058 // make this value the starting point, so that we can only undo
1057 // to this state, instead of a blank cell
1059 // to this state, instead of a blank cell
1058 target_cell.code_mirror.clearHistory();
1060 target_cell.code_mirror.clearHistory();
1059 source_element.remove();
1061 source_element.remove();
1060 this.select(i);
1062 this.select(i);
1061 var cursor = source_cell.code_mirror.getCursor();
1063 var cursor = source_cell.code_mirror.getCursor();
1062 target_cell.code_mirror.setCursor(cursor);
1064 target_cell.code_mirror.setCursor(cursor);
1063 if ((source_cell instanceof textcell.TextCell) && source_cell.rendered) {
1065 if ((source_cell instanceof textcell.TextCell) && source_cell.rendered) {
1064 target_cell.render();
1066 target_cell.render();
1065 }
1067 }
1066 }
1068 }
1067 this.set_dirty(true);
1069 this.set_dirty(true);
1068 this.events.trigger('selected_cell_type_changed.Notebook',
1070 this.events.trigger('selected_cell_type_changed.Notebook',
1069 {'cell_type':'heading',level:level}
1071 {'cell_type':'heading',level:level}
1070 );
1072 );
1071 }
1073 }
1072 };
1074 };
1073
1075
1074
1076
1075 // Cut/Copy/Paste
1077 // Cut/Copy/Paste
1076
1078
1077 /**
1079 /**
1078 * Enable UI elements for pasting cells.
1080 * Enable UI elements for pasting cells.
1079 *
1081 *
1080 * @method enable_paste
1082 * @method enable_paste
1081 */
1083 */
1082 Notebook.prototype.enable_paste = function () {
1084 Notebook.prototype.enable_paste = function () {
1083 var that = this;
1085 var that = this;
1084 if (!this.paste_enabled) {
1086 if (!this.paste_enabled) {
1085 $('#paste_cell_replace').removeClass('disabled')
1087 $('#paste_cell_replace').removeClass('disabled')
1086 .on('click', function () {that.paste_cell_replace();});
1088 .on('click', function () {that.paste_cell_replace();});
1087 $('#paste_cell_above').removeClass('disabled')
1089 $('#paste_cell_above').removeClass('disabled')
1088 .on('click', function () {that.paste_cell_above();});
1090 .on('click', function () {that.paste_cell_above();});
1089 $('#paste_cell_below').removeClass('disabled')
1091 $('#paste_cell_below').removeClass('disabled')
1090 .on('click', function () {that.paste_cell_below();});
1092 .on('click', function () {that.paste_cell_below();});
1091 this.paste_enabled = true;
1093 this.paste_enabled = true;
1092 }
1094 }
1093 };
1095 };
1094
1096
1095 /**
1097 /**
1096 * Disable UI elements for pasting cells.
1098 * Disable UI elements for pasting cells.
1097 *
1099 *
1098 * @method disable_paste
1100 * @method disable_paste
1099 */
1101 */
1100 Notebook.prototype.disable_paste = function () {
1102 Notebook.prototype.disable_paste = function () {
1101 if (this.paste_enabled) {
1103 if (this.paste_enabled) {
1102 $('#paste_cell_replace').addClass('disabled').off('click');
1104 $('#paste_cell_replace').addClass('disabled').off('click');
1103 $('#paste_cell_above').addClass('disabled').off('click');
1105 $('#paste_cell_above').addClass('disabled').off('click');
1104 $('#paste_cell_below').addClass('disabled').off('click');
1106 $('#paste_cell_below').addClass('disabled').off('click');
1105 this.paste_enabled = false;
1107 this.paste_enabled = false;
1106 }
1108 }
1107 };
1109 };
1108
1110
1109 /**
1111 /**
1110 * Cut a cell.
1112 * Cut a cell.
1111 *
1113 *
1112 * @method cut_cell
1114 * @method cut_cell
1113 */
1115 */
1114 Notebook.prototype.cut_cell = function () {
1116 Notebook.prototype.cut_cell = function () {
1115 this.copy_cell();
1117 this.copy_cell();
1116 this.delete_cell();
1118 this.delete_cell();
1117 };
1119 };
1118
1120
1119 /**
1121 /**
1120 * Copy a cell.
1122 * Copy a cell.
1121 *
1123 *
1122 * @method copy_cell
1124 * @method copy_cell
1123 */
1125 */
1124 Notebook.prototype.copy_cell = function () {
1126 Notebook.prototype.copy_cell = function () {
1125 var cell = this.get_selected_cell();
1127 var cell = this.get_selected_cell();
1126 this.clipboard = cell.toJSON();
1128 this.clipboard = cell.toJSON();
1127 this.enable_paste();
1129 this.enable_paste();
1128 };
1130 };
1129
1131
1130 /**
1132 /**
1131 * Replace the selected cell with a cell in the clipboard.
1133 * Replace the selected cell with a cell in the clipboard.
1132 *
1134 *
1133 * @method paste_cell_replace
1135 * @method paste_cell_replace
1134 */
1136 */
1135 Notebook.prototype.paste_cell_replace = function () {
1137 Notebook.prototype.paste_cell_replace = function () {
1136 if (this.clipboard !== null && this.paste_enabled) {
1138 if (this.clipboard !== null && this.paste_enabled) {
1137 var cell_data = this.clipboard;
1139 var cell_data = this.clipboard;
1138 var new_cell = this.insert_cell_above(cell_data.cell_type);
1140 var new_cell = this.insert_cell_above(cell_data.cell_type);
1139 new_cell.fromJSON(cell_data);
1141 new_cell.fromJSON(cell_data);
1140 var old_cell = this.get_next_cell(new_cell);
1142 var old_cell = this.get_next_cell(new_cell);
1141 this.delete_cell(this.find_cell_index(old_cell));
1143 this.delete_cell(this.find_cell_index(old_cell));
1142 this.select(this.find_cell_index(new_cell));
1144 this.select(this.find_cell_index(new_cell));
1143 }
1145 }
1144 };
1146 };
1145
1147
1146 /**
1148 /**
1147 * Paste a cell from the clipboard above the selected cell.
1149 * Paste a cell from the clipboard above the selected cell.
1148 *
1150 *
1149 * @method paste_cell_above
1151 * @method paste_cell_above
1150 */
1152 */
1151 Notebook.prototype.paste_cell_above = function () {
1153 Notebook.prototype.paste_cell_above = function () {
1152 if (this.clipboard !== null && this.paste_enabled) {
1154 if (this.clipboard !== null && this.paste_enabled) {
1153 var cell_data = this.clipboard;
1155 var cell_data = this.clipboard;
1154 var new_cell = this.insert_cell_above(cell_data.cell_type);
1156 var new_cell = this.insert_cell_above(cell_data.cell_type);
1155 new_cell.fromJSON(cell_data);
1157 new_cell.fromJSON(cell_data);
1156 new_cell.focus_cell();
1158 new_cell.focus_cell();
1157 }
1159 }
1158 };
1160 };
1159
1161
1160 /**
1162 /**
1161 * Paste a cell from the clipboard below the selected cell.
1163 * Paste a cell from the clipboard below the selected cell.
1162 *
1164 *
1163 * @method paste_cell_below
1165 * @method paste_cell_below
1164 */
1166 */
1165 Notebook.prototype.paste_cell_below = function () {
1167 Notebook.prototype.paste_cell_below = function () {
1166 if (this.clipboard !== null && this.paste_enabled) {
1168 if (this.clipboard !== null && this.paste_enabled) {
1167 var cell_data = this.clipboard;
1169 var cell_data = this.clipboard;
1168 var new_cell = this.insert_cell_below(cell_data.cell_type);
1170 var new_cell = this.insert_cell_below(cell_data.cell_type);
1169 new_cell.fromJSON(cell_data);
1171 new_cell.fromJSON(cell_data);
1170 new_cell.focus_cell();
1172 new_cell.focus_cell();
1171 }
1173 }
1172 };
1174 };
1173
1175
1174 // Split/merge
1176 // Split/merge
1175
1177
1176 /**
1178 /**
1177 * Split the selected cell into two, at the cursor.
1179 * Split the selected cell into two, at the cursor.
1178 *
1180 *
1179 * @method split_cell
1181 * @method split_cell
1180 */
1182 */
1181 Notebook.prototype.split_cell = function () {
1183 Notebook.prototype.split_cell = function () {
1182 var mdc = textcell.MarkdownCell;
1184 var mdc = textcell.MarkdownCell;
1183 var rc = textcell.RawCell;
1185 var rc = textcell.RawCell;
1184 var cell = this.get_selected_cell();
1186 var cell = this.get_selected_cell();
1185 if (cell.is_splittable()) {
1187 if (cell.is_splittable()) {
1186 var texta = cell.get_pre_cursor();
1188 var texta = cell.get_pre_cursor();
1187 var textb = cell.get_post_cursor();
1189 var textb = cell.get_post_cursor();
1188 if (cell instanceof codecell.CodeCell) {
1190 if (cell instanceof codecell.CodeCell) {
1189 // In this case the operations keep the notebook in its existing mode
1191 // In this case the operations keep the notebook in its existing mode
1190 // so we don't need to do any post-op mode changes.
1192 // so we don't need to do any post-op mode changes.
1191 cell.set_text(textb);
1193 cell.set_text(textb);
1192 var new_cell = this.insert_cell_above('code');
1194 var new_cell = this.insert_cell_above('code');
1193 new_cell.set_text(texta);
1195 new_cell.set_text(texta);
1194 } else if ((cell instanceof mdc && !cell.rendered) || (cell instanceof rc)) {
1196 } else if ((cell instanceof mdc && !cell.rendered) || (cell instanceof rc)) {
1195 // We know cell is !rendered so we can use set_text.
1197 // We know cell is !rendered so we can use set_text.
1196 cell.set_text(textb);
1198 cell.set_text(textb);
1197 var new_cell = this.insert_cell_above(cell.cell_type);
1199 var new_cell = this.insert_cell_above(cell.cell_type);
1198 // Unrender the new cell so we can call set_text.
1200 // Unrender the new cell so we can call set_text.
1199 new_cell.unrender();
1201 new_cell.unrender();
1200 new_cell.set_text(texta);
1202 new_cell.set_text(texta);
1201 }
1203 }
1202 }
1204 }
1203 };
1205 };
1204
1206
1205 /**
1207 /**
1206 * Combine the selected cell into the cell above it.
1208 * Combine the selected cell into the cell above it.
1207 *
1209 *
1208 * @method merge_cell_above
1210 * @method merge_cell_above
1209 */
1211 */
1210 Notebook.prototype.merge_cell_above = function () {
1212 Notebook.prototype.merge_cell_above = function () {
1211 var mdc = textcell.MarkdownCell;
1213 var mdc = textcell.MarkdownCell;
1212 var rc = textcell.RawCell;
1214 var rc = textcell.RawCell;
1213 var index = this.get_selected_index();
1215 var index = this.get_selected_index();
1214 var cell = this.get_cell(index);
1216 var cell = this.get_cell(index);
1215 var render = cell.rendered;
1217 var render = cell.rendered;
1216 if (!cell.is_mergeable()) {
1218 if (!cell.is_mergeable()) {
1217 return;
1219 return;
1218 }
1220 }
1219 if (index > 0) {
1221 if (index > 0) {
1220 var upper_cell = this.get_cell(index-1);
1222 var upper_cell = this.get_cell(index-1);
1221 if (!upper_cell.is_mergeable()) {
1223 if (!upper_cell.is_mergeable()) {
1222 return;
1224 return;
1223 }
1225 }
1224 var upper_text = upper_cell.get_text();
1226 var upper_text = upper_cell.get_text();
1225 var text = cell.get_text();
1227 var text = cell.get_text();
1226 if (cell instanceof codecell.CodeCell) {
1228 if (cell instanceof codecell.CodeCell) {
1227 cell.set_text(upper_text+'\n'+text);
1229 cell.set_text(upper_text+'\n'+text);
1228 } else if ((cell instanceof mdc) || (cell instanceof rc)) {
1230 } else if ((cell instanceof mdc) || (cell instanceof rc)) {
1229 cell.unrender(); // Must unrender before we set_text.
1231 cell.unrender(); // Must unrender before we set_text.
1230 cell.set_text(upper_text+'\n\n'+text);
1232 cell.set_text(upper_text+'\n\n'+text);
1231 if (render) {
1233 if (render) {
1232 // The rendered state of the final cell should match
1234 // The rendered state of the final cell should match
1233 // that of the original selected cell;
1235 // that of the original selected cell;
1234 cell.render();
1236 cell.render();
1235 }
1237 }
1236 }
1238 }
1237 this.delete_cell(index-1);
1239 this.delete_cell(index-1);
1238 this.select(this.find_cell_index(cell));
1240 this.select(this.find_cell_index(cell));
1239 }
1241 }
1240 };
1242 };
1241
1243
1242 /**
1244 /**
1243 * Combine the selected cell into the cell below it.
1245 * Combine the selected cell into the cell below it.
1244 *
1246 *
1245 * @method merge_cell_below
1247 * @method merge_cell_below
1246 */
1248 */
1247 Notebook.prototype.merge_cell_below = function () {
1249 Notebook.prototype.merge_cell_below = function () {
1248 var mdc = textcell.MarkdownCell;
1250 var mdc = textcell.MarkdownCell;
1249 var rc = textcell.RawCell;
1251 var rc = textcell.RawCell;
1250 var index = this.get_selected_index();
1252 var index = this.get_selected_index();
1251 var cell = this.get_cell(index);
1253 var cell = this.get_cell(index);
1252 var render = cell.rendered;
1254 var render = cell.rendered;
1253 if (!cell.is_mergeable()) {
1255 if (!cell.is_mergeable()) {
1254 return;
1256 return;
1255 }
1257 }
1256 if (index < this.ncells()-1) {
1258 if (index < this.ncells()-1) {
1257 var lower_cell = this.get_cell(index+1);
1259 var lower_cell = this.get_cell(index+1);
1258 if (!lower_cell.is_mergeable()) {
1260 if (!lower_cell.is_mergeable()) {
1259 return;
1261 return;
1260 }
1262 }
1261 var lower_text = lower_cell.get_text();
1263 var lower_text = lower_cell.get_text();
1262 var text = cell.get_text();
1264 var text = cell.get_text();
1263 if (cell instanceof codecell.CodeCell) {
1265 if (cell instanceof codecell.CodeCell) {
1264 cell.set_text(text+'\n'+lower_text);
1266 cell.set_text(text+'\n'+lower_text);
1265 } else if ((cell instanceof mdc) || (cell instanceof rc)) {
1267 } else if ((cell instanceof mdc) || (cell instanceof rc)) {
1266 cell.unrender(); // Must unrender before we set_text.
1268 cell.unrender(); // Must unrender before we set_text.
1267 cell.set_text(text+'\n\n'+lower_text);
1269 cell.set_text(text+'\n\n'+lower_text);
1268 if (render) {
1270 if (render) {
1269 // The rendered state of the final cell should match
1271 // The rendered state of the final cell should match
1270 // that of the original selected cell;
1272 // that of the original selected cell;
1271 cell.render();
1273 cell.render();
1272 }
1274 }
1273 }
1275 }
1274 this.delete_cell(index+1);
1276 this.delete_cell(index+1);
1275 this.select(this.find_cell_index(cell));
1277 this.select(this.find_cell_index(cell));
1276 }
1278 }
1277 };
1279 };
1278
1280
1279
1281
1280 // Cell collapsing and output clearing
1282 // Cell collapsing and output clearing
1281
1283
1282 /**
1284 /**
1283 * Hide a cell's output.
1285 * Hide a cell's output.
1284 *
1286 *
1285 * @method collapse_output
1287 * @method collapse_output
1286 * @param {Number} index A cell's numeric index
1288 * @param {Number} index A cell's numeric index
1287 */
1289 */
1288 Notebook.prototype.collapse_output = function (index) {
1290 Notebook.prototype.collapse_output = function (index) {
1289 var i = this.index_or_selected(index);
1291 var i = this.index_or_selected(index);
1290 var cell = this.get_cell(i);
1292 var cell = this.get_cell(i);
1291 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1293 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1292 cell.collapse_output();
1294 cell.collapse_output();
1293 this.set_dirty(true);
1295 this.set_dirty(true);
1294 }
1296 }
1295 };
1297 };
1296
1298
1297 /**
1299 /**
1298 * Hide each code cell's output area.
1300 * Hide each code cell's output area.
1299 *
1301 *
1300 * @method collapse_all_output
1302 * @method collapse_all_output
1301 */
1303 */
1302 Notebook.prototype.collapse_all_output = function () {
1304 Notebook.prototype.collapse_all_output = function () {
1303 $.map(this.get_cells(), function (cell, i) {
1305 $.map(this.get_cells(), function (cell, i) {
1304 if (cell instanceof codecell.CodeCell) {
1306 if (cell instanceof codecell.CodeCell) {
1305 cell.collapse_output();
1307 cell.collapse_output();
1306 }
1308 }
1307 });
1309 });
1308 // this should not be set if the `collapse` key is removed from nbformat
1310 // this should not be set if the `collapse` key is removed from nbformat
1309 this.set_dirty(true);
1311 this.set_dirty(true);
1310 };
1312 };
1311
1313
1312 /**
1314 /**
1313 * Show a cell's output.
1315 * Show a cell's output.
1314 *
1316 *
1315 * @method expand_output
1317 * @method expand_output
1316 * @param {Number} index A cell's numeric index
1318 * @param {Number} index A cell's numeric index
1317 */
1319 */
1318 Notebook.prototype.expand_output = function (index) {
1320 Notebook.prototype.expand_output = function (index) {
1319 var i = this.index_or_selected(index);
1321 var i = this.index_or_selected(index);
1320 var cell = this.get_cell(i);
1322 var cell = this.get_cell(i);
1321 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1323 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1322 cell.expand_output();
1324 cell.expand_output();
1323 this.set_dirty(true);
1325 this.set_dirty(true);
1324 }
1326 }
1325 };
1327 };
1326
1328
1327 /**
1329 /**
1328 * Expand each code cell's output area, and remove scrollbars.
1330 * Expand each code cell's output area, and remove scrollbars.
1329 *
1331 *
1330 * @method expand_all_output
1332 * @method expand_all_output
1331 */
1333 */
1332 Notebook.prototype.expand_all_output = function () {
1334 Notebook.prototype.expand_all_output = function () {
1333 $.map(this.get_cells(), function (cell, i) {
1335 $.map(this.get_cells(), function (cell, i) {
1334 if (cell instanceof codecell.CodeCell) {
1336 if (cell instanceof codecell.CodeCell) {
1335 cell.expand_output();
1337 cell.expand_output();
1336 }
1338 }
1337 });
1339 });
1338 // this should not be set if the `collapse` key is removed from nbformat
1340 // this should not be set if the `collapse` key is removed from nbformat
1339 this.set_dirty(true);
1341 this.set_dirty(true);
1340 };
1342 };
1341
1343
1342 /**
1344 /**
1343 * Clear the selected CodeCell's output area.
1345 * Clear the selected CodeCell's output area.
1344 *
1346 *
1345 * @method clear_output
1347 * @method clear_output
1346 * @param {Number} index A cell's numeric index
1348 * @param {Number} index A cell's numeric index
1347 */
1349 */
1348 Notebook.prototype.clear_output = function (index) {
1350 Notebook.prototype.clear_output = function (index) {
1349 var i = this.index_or_selected(index);
1351 var i = this.index_or_selected(index);
1350 var cell = this.get_cell(i);
1352 var cell = this.get_cell(i);
1351 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1353 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1352 cell.clear_output();
1354 cell.clear_output();
1353 this.set_dirty(true);
1355 this.set_dirty(true);
1354 }
1356 }
1355 };
1357 };
1356
1358
1357 /**
1359 /**
1358 * Clear each code cell's output area.
1360 * Clear each code cell's output area.
1359 *
1361 *
1360 * @method clear_all_output
1362 * @method clear_all_output
1361 */
1363 */
1362 Notebook.prototype.clear_all_output = function () {
1364 Notebook.prototype.clear_all_output = function () {
1363 $.map(this.get_cells(), function (cell, i) {
1365 $.map(this.get_cells(), function (cell, i) {
1364 if (cell instanceof codecell.CodeCell) {
1366 if (cell instanceof codecell.CodeCell) {
1365 cell.clear_output();
1367 cell.clear_output();
1366 }
1368 }
1367 });
1369 });
1368 this.set_dirty(true);
1370 this.set_dirty(true);
1369 };
1371 };
1370
1372
1371 /**
1373 /**
1372 * Scroll the selected CodeCell's output area.
1374 * Scroll the selected CodeCell's output area.
1373 *
1375 *
1374 * @method scroll_output
1376 * @method scroll_output
1375 * @param {Number} index A cell's numeric index
1377 * @param {Number} index A cell's numeric index
1376 */
1378 */
1377 Notebook.prototype.scroll_output = function (index) {
1379 Notebook.prototype.scroll_output = function (index) {
1378 var i = this.index_or_selected(index);
1380 var i = this.index_or_selected(index);
1379 var cell = this.get_cell(i);
1381 var cell = this.get_cell(i);
1380 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1382 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1381 cell.scroll_output();
1383 cell.scroll_output();
1382 this.set_dirty(true);
1384 this.set_dirty(true);
1383 }
1385 }
1384 };
1386 };
1385
1387
1386 /**
1388 /**
1387 * Expand each code cell's output area, and add a scrollbar for long output.
1389 * Expand each code cell's output area, and add a scrollbar for long output.
1388 *
1390 *
1389 * @method scroll_all_output
1391 * @method scroll_all_output
1390 */
1392 */
1391 Notebook.prototype.scroll_all_output = function () {
1393 Notebook.prototype.scroll_all_output = function () {
1392 $.map(this.get_cells(), function (cell, i) {
1394 $.map(this.get_cells(), function (cell, i) {
1393 if (cell instanceof codecell.CodeCell) {
1395 if (cell instanceof codecell.CodeCell) {
1394 cell.scroll_output();
1396 cell.scroll_output();
1395 }
1397 }
1396 });
1398 });
1397 // this should not be set if the `collapse` key is removed from nbformat
1399 // this should not be set if the `collapse` key is removed from nbformat
1398 this.set_dirty(true);
1400 this.set_dirty(true);
1399 };
1401 };
1400
1402
1401 /** Toggle whether a cell's output is collapsed or expanded.
1403 /** Toggle whether a cell's output is collapsed or expanded.
1402 *
1404 *
1403 * @method toggle_output
1405 * @method toggle_output
1404 * @param {Number} index A cell's numeric index
1406 * @param {Number} index A cell's numeric index
1405 */
1407 */
1406 Notebook.prototype.toggle_output = function (index) {
1408 Notebook.prototype.toggle_output = function (index) {
1407 var i = this.index_or_selected(index);
1409 var i = this.index_or_selected(index);
1408 var cell = this.get_cell(i);
1410 var cell = this.get_cell(i);
1409 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1411 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1410 cell.toggle_output();
1412 cell.toggle_output();
1411 this.set_dirty(true);
1413 this.set_dirty(true);
1412 }
1414 }
1413 };
1415 };
1414
1416
1415 /**
1417 /**
1416 * Hide/show the output of all cells.
1418 * Hide/show the output of all cells.
1417 *
1419 *
1418 * @method toggle_all_output
1420 * @method toggle_all_output
1419 */
1421 */
1420 Notebook.prototype.toggle_all_output = function () {
1422 Notebook.prototype.toggle_all_output = function () {
1421 $.map(this.get_cells(), function (cell, i) {
1423 $.map(this.get_cells(), function (cell, i) {
1422 if (cell instanceof codecell.CodeCell) {
1424 if (cell instanceof codecell.CodeCell) {
1423 cell.toggle_output();
1425 cell.toggle_output();
1424 }
1426 }
1425 });
1427 });
1426 // this should not be set if the `collapse` key is removed from nbformat
1428 // this should not be set if the `collapse` key is removed from nbformat
1427 this.set_dirty(true);
1429 this.set_dirty(true);
1428 };
1430 };
1429
1431
1430 /**
1432 /**
1431 * Toggle a scrollbar for long cell outputs.
1433 * Toggle a scrollbar for long cell outputs.
1432 *
1434 *
1433 * @method toggle_output_scroll
1435 * @method toggle_output_scroll
1434 * @param {Number} index A cell's numeric index
1436 * @param {Number} index A cell's numeric index
1435 */
1437 */
1436 Notebook.prototype.toggle_output_scroll = function (index) {
1438 Notebook.prototype.toggle_output_scroll = function (index) {
1437 var i = this.index_or_selected(index);
1439 var i = this.index_or_selected(index);
1438 var cell = this.get_cell(i);
1440 var cell = this.get_cell(i);
1439 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1441 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1440 cell.toggle_output_scroll();
1442 cell.toggle_output_scroll();
1441 this.set_dirty(true);
1443 this.set_dirty(true);
1442 }
1444 }
1443 };
1445 };
1444
1446
1445 /**
1447 /**
1446 * Toggle the scrolling of long output on all cells.
1448 * Toggle the scrolling of long output on all cells.
1447 *
1449 *
1448 * @method toggle_all_output_scrolling
1450 * @method toggle_all_output_scrolling
1449 */
1451 */
1450 Notebook.prototype.toggle_all_output_scroll = function () {
1452 Notebook.prototype.toggle_all_output_scroll = function () {
1451 $.map(this.get_cells(), function (cell, i) {
1453 $.map(this.get_cells(), function (cell, i) {
1452 if (cell instanceof codecell.CodeCell) {
1454 if (cell instanceof codecell.CodeCell) {
1453 cell.toggle_output_scroll();
1455 cell.toggle_output_scroll();
1454 }
1456 }
1455 });
1457 });
1456 // this should not be set if the `collapse` key is removed from nbformat
1458 // this should not be set if the `collapse` key is removed from nbformat
1457 this.set_dirty(true);
1459 this.set_dirty(true);
1458 };
1460 };
1459
1461
1460 // Other cell functions: line numbers, ...
1462 // Other cell functions: line numbers, ...
1461
1463
1462 /**
1464 /**
1463 * Toggle line numbers in the selected cell's input area.
1465 * Toggle line numbers in the selected cell's input area.
1464 *
1466 *
1465 * @method cell_toggle_line_numbers
1467 * @method cell_toggle_line_numbers
1466 */
1468 */
1467 Notebook.prototype.cell_toggle_line_numbers = function() {
1469 Notebook.prototype.cell_toggle_line_numbers = function() {
1468 this.get_selected_cell().toggle_line_numbers();
1470 this.get_selected_cell().toggle_line_numbers();
1469 };
1471 };
1470
1472
1471 // Session related things
1473 // Session related things
1472
1474
1473 /**
1475 /**
1474 * Start a new session and set it on each code cell.
1476 * Start a new session and set it on each code cell.
1475 *
1477 *
1476 * @method start_session
1478 * @method start_session
1477 */
1479 */
1478 Notebook.prototype.start_session = function () {
1480 Notebook.prototype.start_session = function () {
1479 this.session = new session.Session(this, {
1481 this.session = new session.Session({
1480 base_url: base_url,
1482 base_url: this.base_url,
1481 notebook_path: notebook_path,
1483 notebook_path: this.notebook_path,
1482 notebook_name: notebook_name,
1484 notebook_name: this.notebook_name,
1483 notebook: this});
1485 notebook: this});
1484 this.session.start($.proxy(this._session_started, this));
1486 this.session.start($.proxy(this._session_started, this));
1485 };
1487 };
1486
1488
1487
1489
1488 /**
1490 /**
1489 * Once a session is started, link the code cells to the kernel and pass the
1491 * Once a session is started, link the code cells to the kernel and pass the
1490 * comm manager to the widget manager
1492 * comm manager to the widget manager
1491 *
1493 *
1492 */
1494 */
1493 Notebook.prototype._session_started = function(){
1495 Notebook.prototype._session_started = function(){
1494 this.kernel = this.session.kernel;
1496 this.kernel = this.session.kernel;
1495 var ncells = this.ncells();
1497 var ncells = this.ncells();
1496 for (var i=0; i<ncells; i++) {
1498 for (var i=0; i<ncells; i++) {
1497 var cell = this.get_cell(i);
1499 var cell = this.get_cell(i);
1498 if (cell instanceof codecell.CodeCell) {
1500 if (cell instanceof codecell.CodeCell) {
1499 cell.set_kernel(this.session.kernel);
1501 cell.set_kernel(this.session.kernel);
1500 }
1502 }
1501 }
1503 }
1502 };
1504 };
1503
1505
1504 /**
1506 /**
1505 * Prompt the user to restart the IPython kernel.
1507 * Prompt the user to restart the IPython kernel.
1506 *
1508 *
1507 * @method restart_kernel
1509 * @method restart_kernel
1508 */
1510 */
1509 Notebook.prototype.restart_kernel = function () {
1511 Notebook.prototype.restart_kernel = function () {
1510 var that = this;
1512 var that = this;
1511 dialog.modal({
1513 dialog.modal({
1514 notebook: this,
1515 keyboard_manager: this.keyboard_manager,
1512 title : "Restart kernel or continue running?",
1516 title : "Restart kernel or continue running?",
1513 body : $("<p/>").text(
1517 body : $("<p/>").text(
1514 'Do you want to restart the current kernel? You will lose all variables defined in it.'
1518 'Do you want to restart the current kernel? You will lose all variables defined in it.'
1515 ),
1519 ),
1516 buttons : {
1520 buttons : {
1517 "Continue running" : {},
1521 "Continue running" : {},
1518 "Restart" : {
1522 "Restart" : {
1519 "class" : "btn-danger",
1523 "class" : "btn-danger",
1520 "click" : function() {
1524 "click" : function() {
1521 that.session.restart_kernel();
1525 that.session.restart_kernel();
1522 }
1526 }
1523 }
1527 }
1524 }
1528 }
1525 });
1529 });
1526 };
1530 };
1527
1531
1528 /**
1532 /**
1529 * Execute or render cell outputs and go into command mode.
1533 * Execute or render cell outputs and go into command mode.
1530 *
1534 *
1531 * @method execute_cell
1535 * @method execute_cell
1532 */
1536 */
1533 Notebook.prototype.execute_cell = function () {
1537 Notebook.prototype.execute_cell = function () {
1534 // mode = shift, ctrl, alt
1538 // mode = shift, ctrl, alt
1535 var cell = this.get_selected_cell();
1539 var cell = this.get_selected_cell();
1536 var cell_index = this.find_cell_index(cell);
1540 var cell_index = this.find_cell_index(cell);
1537
1541
1538 cell.execute();
1542 cell.execute();
1539 this.command_mode();
1543 this.command_mode();
1540 this.set_dirty(true);
1544 this.set_dirty(true);
1541 };
1545 };
1542
1546
1543 /**
1547 /**
1544 * Execute or render cell outputs and insert a new cell below.
1548 * Execute or render cell outputs and insert a new cell below.
1545 *
1549 *
1546 * @method execute_cell_and_insert_below
1550 * @method execute_cell_and_insert_below
1547 */
1551 */
1548 Notebook.prototype.execute_cell_and_insert_below = function () {
1552 Notebook.prototype.execute_cell_and_insert_below = function () {
1549 var cell = this.get_selected_cell();
1553 var cell = this.get_selected_cell();
1550 var cell_index = this.find_cell_index(cell);
1554 var cell_index = this.find_cell_index(cell);
1551
1555
1552 cell.execute();
1556 cell.execute();
1553
1557
1554 // If we are at the end always insert a new cell and return
1558 // If we are at the end always insert a new cell and return
1555 if (cell_index === (this.ncells()-1)) {
1559 if (cell_index === (this.ncells()-1)) {
1556 this.command_mode();
1560 this.command_mode();
1557 this.insert_cell_below();
1561 this.insert_cell_below();
1558 this.select(cell_index+1);
1562 this.select(cell_index+1);
1559 this.edit_mode();
1563 this.edit_mode();
1560 this.scroll_to_bottom();
1564 this.scroll_to_bottom();
1561 this.set_dirty(true);
1565 this.set_dirty(true);
1562 return;
1566 return;
1563 }
1567 }
1564
1568
1565 this.command_mode();
1569 this.command_mode();
1566 this.insert_cell_below();
1570 this.insert_cell_below();
1567 this.select(cell_index+1);
1571 this.select(cell_index+1);
1568 this.edit_mode();
1572 this.edit_mode();
1569 this.set_dirty(true);
1573 this.set_dirty(true);
1570 };
1574 };
1571
1575
1572 /**
1576 /**
1573 * Execute or render cell outputs and select the next cell.
1577 * Execute or render cell outputs and select the next cell.
1574 *
1578 *
1575 * @method execute_cell_and_select_below
1579 * @method execute_cell_and_select_below
1576 */
1580 */
1577 Notebook.prototype.execute_cell_and_select_below = function () {
1581 Notebook.prototype.execute_cell_and_select_below = function () {
1578
1582
1579 var cell = this.get_selected_cell();
1583 var cell = this.get_selected_cell();
1580 var cell_index = this.find_cell_index(cell);
1584 var cell_index = this.find_cell_index(cell);
1581
1585
1582 cell.execute();
1586 cell.execute();
1583
1587
1584 // If we are at the end always insert a new cell and return
1588 // If we are at the end always insert a new cell and return
1585 if (cell_index === (this.ncells()-1)) {
1589 if (cell_index === (this.ncells()-1)) {
1586 this.command_mode();
1590 this.command_mode();
1587 this.insert_cell_below();
1591 this.insert_cell_below();
1588 this.select(cell_index+1);
1592 this.select(cell_index+1);
1589 this.edit_mode();
1593 this.edit_mode();
1590 this.scroll_to_bottom();
1594 this.scroll_to_bottom();
1591 this.set_dirty(true);
1595 this.set_dirty(true);
1592 return;
1596 return;
1593 }
1597 }
1594
1598
1595 this.command_mode();
1599 this.command_mode();
1596 this.select(cell_index+1);
1600 this.select(cell_index+1);
1597 this.focus_cell();
1601 this.focus_cell();
1598 this.set_dirty(true);
1602 this.set_dirty(true);
1599 };
1603 };
1600
1604
1601 /**
1605 /**
1602 * Execute all cells below the selected cell.
1606 * Execute all cells below the selected cell.
1603 *
1607 *
1604 * @method execute_cells_below
1608 * @method execute_cells_below
1605 */
1609 */
1606 Notebook.prototype.execute_cells_below = function () {
1610 Notebook.prototype.execute_cells_below = function () {
1607 this.execute_cell_range(this.get_selected_index(), this.ncells());
1611 this.execute_cell_range(this.get_selected_index(), this.ncells());
1608 this.scroll_to_bottom();
1612 this.scroll_to_bottom();
1609 };
1613 };
1610
1614
1611 /**
1615 /**
1612 * Execute all cells above the selected cell.
1616 * Execute all cells above the selected cell.
1613 *
1617 *
1614 * @method execute_cells_above
1618 * @method execute_cells_above
1615 */
1619 */
1616 Notebook.prototype.execute_cells_above = function () {
1620 Notebook.prototype.execute_cells_above = function () {
1617 this.execute_cell_range(0, this.get_selected_index());
1621 this.execute_cell_range(0, this.get_selected_index());
1618 };
1622 };
1619
1623
1620 /**
1624 /**
1621 * Execute all cells.
1625 * Execute all cells.
1622 *
1626 *
1623 * @method execute_all_cells
1627 * @method execute_all_cells
1624 */
1628 */
1625 Notebook.prototype.execute_all_cells = function () {
1629 Notebook.prototype.execute_all_cells = function () {
1626 this.execute_cell_range(0, this.ncells());
1630 this.execute_cell_range(0, this.ncells());
1627 this.scroll_to_bottom();
1631 this.scroll_to_bottom();
1628 };
1632 };
1629
1633
1630 /**
1634 /**
1631 * Execute a contiguous range of cells.
1635 * Execute a contiguous range of cells.
1632 *
1636 *
1633 * @method execute_cell_range
1637 * @method execute_cell_range
1634 * @param {Number} start Index of the first cell to execute (inclusive)
1638 * @param {Number} start Index of the first cell to execute (inclusive)
1635 * @param {Number} end Index of the last cell to execute (exclusive)
1639 * @param {Number} end Index of the last cell to execute (exclusive)
1636 */
1640 */
1637 Notebook.prototype.execute_cell_range = function (start, end) {
1641 Notebook.prototype.execute_cell_range = function (start, end) {
1638 this.command_mode();
1642 this.command_mode();
1639 for (var i=start; i<end; i++) {
1643 for (var i=start; i<end; i++) {
1640 this.select(i);
1644 this.select(i);
1641 this.execute_cell();
1645 this.execute_cell();
1642 }
1646 }
1643 };
1647 };
1644
1648
1645 // Persistance and loading
1649 // Persistance and loading
1646
1650
1647 /**
1651 /**
1648 * Getter method for this notebook's name.
1652 * Getter method for this notebook's name.
1649 *
1653 *
1650 * @method get_notebook_name
1654 * @method get_notebook_name
1651 * @return {String} This notebook's name (excluding file extension)
1655 * @return {String} This notebook's name (excluding file extension)
1652 */
1656 */
1653 Notebook.prototype.get_notebook_name = function () {
1657 Notebook.prototype.get_notebook_name = function () {
1654 var nbname = this.notebook_name.substring(0,this.notebook_name.length-6);
1658 var nbname = this.notebook_name.substring(0,this.notebook_name.length-6);
1655 return nbname;
1659 return nbname;
1656 };
1660 };
1657
1661
1658 /**
1662 /**
1659 * Setter method for this notebook's name.
1663 * Setter method for this notebook's name.
1660 *
1664 *
1661 * @method set_notebook_name
1665 * @method set_notebook_name
1662 * @param {String} name A new name for this notebook
1666 * @param {String} name A new name for this notebook
1663 */
1667 */
1664 Notebook.prototype.set_notebook_name = function (name) {
1668 Notebook.prototype.set_notebook_name = function (name) {
1665 this.notebook_name = name;
1669 this.notebook_name = name;
1666 };
1670 };
1667
1671
1668 /**
1672 /**
1669 * Check that a notebook's name is valid.
1673 * Check that a notebook's name is valid.
1670 *
1674 *
1671 * @method test_notebook_name
1675 * @method test_notebook_name
1672 * @param {String} nbname A name for this notebook
1676 * @param {String} nbname A name for this notebook
1673 * @return {Boolean} True if the name is valid, false if invalid
1677 * @return {Boolean} True if the name is valid, false if invalid
1674 */
1678 */
1675 Notebook.prototype.test_notebook_name = function (nbname) {
1679 Notebook.prototype.test_notebook_name = function (nbname) {
1676 nbname = nbname || '';
1680 nbname = nbname || '';
1677 if (nbname.length>0 && !this.notebook_name_blacklist_re.test(nbname)) {
1681 if (nbname.length>0 && !this.notebook_name_blacklist_re.test(nbname)) {
1678 return true;
1682 return true;
1679 } else {
1683 } else {
1680 return false;
1684 return false;
1681 }
1685 }
1682 };
1686 };
1683
1687
1684 /**
1688 /**
1685 * Load a notebook from JSON (.ipynb).
1689 * Load a notebook from JSON (.ipynb).
1686 *
1690 *
1687 * This currently handles one worksheet: others are deleted.
1691 * This currently handles one worksheet: others are deleted.
1688 *
1692 *
1689 * @method fromJSON
1693 * @method fromJSON
1690 * @param {Object} data JSON representation of a notebook
1694 * @param {Object} data JSON representation of a notebook
1691 */
1695 */
1692 Notebook.prototype.fromJSON = function (data) {
1696 Notebook.prototype.fromJSON = function (data) {
1693 var content = data.content;
1697 var content = data.content;
1694 var ncells = this.ncells();
1698 var ncells = this.ncells();
1695 var i;
1699 var i;
1696 for (i=0; i<ncells; i++) {
1700 for (i=0; i<ncells; i++) {
1697 // Always delete cell 0 as they get renumbered as they are deleted.
1701 // Always delete cell 0 as they get renumbered as they are deleted.
1698 this.delete_cell(0);
1702 this.delete_cell(0);
1699 }
1703 }
1700 // Save the metadata and name.
1704 // Save the metadata and name.
1701 this.metadata = content.metadata;
1705 this.metadata = content.metadata;
1702 this.notebook_name = data.name;
1706 this.notebook_name = data.name;
1703 var trusted = true;
1707 var trusted = true;
1704 // Only handle 1 worksheet for now.
1708 // Only handle 1 worksheet for now.
1705 var worksheet = content.worksheets[0];
1709 var worksheet = content.worksheets[0];
1706 if (worksheet !== undefined) {
1710 if (worksheet !== undefined) {
1707 if (worksheet.metadata) {
1711 if (worksheet.metadata) {
1708 this.worksheet_metadata = worksheet.metadata;
1712 this.worksheet_metadata = worksheet.metadata;
1709 }
1713 }
1710 var new_cells = worksheet.cells;
1714 var new_cells = worksheet.cells;
1711 ncells = new_cells.length;
1715 ncells = new_cells.length;
1712 var cell_data = null;
1716 var cell_data = null;
1713 var new_cell = null;
1717 var new_cell = null;
1714 for (i=0; i<ncells; i++) {
1718 for (i=0; i<ncells; i++) {
1715 cell_data = new_cells[i];
1719 cell_data = new_cells[i];
1716 // VERSIONHACK: plaintext -> raw
1720 // VERSIONHACK: plaintext -> raw
1717 // handle never-released plaintext name for raw cells
1721 // handle never-released plaintext name for raw cells
1718 if (cell_data.cell_type === 'plaintext'){
1722 if (cell_data.cell_type === 'plaintext'){
1719 cell_data.cell_type = 'raw';
1723 cell_data.cell_type = 'raw';
1720 }
1724 }
1721
1725
1722 new_cell = this.insert_cell_at_index(cell_data.cell_type, i);
1726 new_cell = this.insert_cell_at_index(cell_data.cell_type, i);
1723 new_cell.fromJSON(cell_data);
1727 new_cell.fromJSON(cell_data);
1724 if (new_cell.cell_type == 'code' && !new_cell.output_area.trusted) {
1728 if (new_cell.cell_type == 'code' && !new_cell.output_area.trusted) {
1725 trusted = false;
1729 trusted = false;
1726 }
1730 }
1727 }
1731 }
1728 }
1732 }
1729 if (trusted != this.trusted) {
1733 if (trusted != this.trusted) {
1730 this.trusted = trusted;
1734 this.trusted = trusted;
1731 this.events.trigger("trust_changed.Notebook", trusted);
1735 this.events.trigger("trust_changed.Notebook", trusted);
1732 }
1736 }
1733 if (content.worksheets.length > 1) {
1737 if (content.worksheets.length > 1) {
1734 dialog.modal({
1738 dialog.modal({
1739 notebook: this,
1740 keyboard_manager: this.keyboard_manager,
1735 title : "Multiple worksheets",
1741 title : "Multiple worksheets",
1736 body : "This notebook has " + data.worksheets.length + " worksheets, " +
1742 body : "This notebook has " + data.worksheets.length + " worksheets, " +
1737 "but this version of IPython can only handle the first. " +
1743 "but this version of IPython can only handle the first. " +
1738 "If you save this notebook, worksheets after the first will be lost.",
1744 "If you save this notebook, worksheets after the first will be lost.",
1739 buttons : {
1745 buttons : {
1740 OK : {
1746 OK : {
1741 class : "btn-danger"
1747 class : "btn-danger"
1742 }
1748 }
1743 }
1749 }
1744 });
1750 });
1745 }
1751 }
1746 };
1752 };
1747
1753
1748 /**
1754 /**
1749 * Dump this notebook into a JSON-friendly object.
1755 * Dump this notebook into a JSON-friendly object.
1750 *
1756 *
1751 * @method toJSON
1757 * @method toJSON
1752 * @return {Object} A JSON-friendly representation of this notebook.
1758 * @return {Object} A JSON-friendly representation of this notebook.
1753 */
1759 */
1754 Notebook.prototype.toJSON = function () {
1760 Notebook.prototype.toJSON = function () {
1755 var cells = this.get_cells();
1761 var cells = this.get_cells();
1756 var ncells = cells.length;
1762 var ncells = cells.length;
1757 var cell_array = new Array(ncells);
1763 var cell_array = new Array(ncells);
1758 var trusted = true;
1764 var trusted = true;
1759 for (var i=0; i<ncells; i++) {
1765 for (var i=0; i<ncells; i++) {
1760 var cell = cells[i];
1766 var cell = cells[i];
1761 if (cell.cell_type == 'code' && !cell.output_area.trusted) {
1767 if (cell.cell_type == 'code' && !cell.output_area.trusted) {
1762 trusted = false;
1768 trusted = false;
1763 }
1769 }
1764 cell_array[i] = cell.toJSON();
1770 cell_array[i] = cell.toJSON();
1765 }
1771 }
1766 var data = {
1772 var data = {
1767 // Only handle 1 worksheet for now.
1773 // Only handle 1 worksheet for now.
1768 worksheets : [{
1774 worksheets : [{
1769 cells: cell_array,
1775 cells: cell_array,
1770 metadata: this.worksheet_metadata
1776 metadata: this.worksheet_metadata
1771 }],
1777 }],
1772 metadata : this.metadata
1778 metadata : this.metadata
1773 };
1779 };
1774 if (trusted != this.trusted) {
1780 if (trusted != this.trusted) {
1775 this.trusted = trusted;
1781 this.trusted = trusted;
1776 this.events.trigger("trust_changed.Notebook", trusted);
1782 this.events.trigger("trust_changed.Notebook", trusted);
1777 }
1783 }
1778 return data;
1784 return data;
1779 };
1785 };
1780
1786
1781 /**
1787 /**
1782 * Start an autosave timer, for periodically saving the notebook.
1788 * Start an autosave timer, for periodically saving the notebook.
1783 *
1789 *
1784 * @method set_autosave_interval
1790 * @method set_autosave_interval
1785 * @param {Integer} interval the autosave interval in milliseconds
1791 * @param {Integer} interval the autosave interval in milliseconds
1786 */
1792 */
1787 Notebook.prototype.set_autosave_interval = function (interval) {
1793 Notebook.prototype.set_autosave_interval = function (interval) {
1788 var that = this;
1794 var that = this;
1789 // clear previous interval, so we don't get simultaneous timers
1795 // clear previous interval, so we don't get simultaneous timers
1790 if (this.autosave_timer) {
1796 if (this.autosave_timer) {
1791 clearInterval(this.autosave_timer);
1797 clearInterval(this.autosave_timer);
1792 }
1798 }
1793
1799
1794 this.autosave_interval = this.minimum_autosave_interval = interval;
1800 this.autosave_interval = this.minimum_autosave_interval = interval;
1795 if (interval) {
1801 if (interval) {
1796 this.autosave_timer = setInterval(function() {
1802 this.autosave_timer = setInterval(function() {
1797 if (that.dirty) {
1803 if (that.dirty) {
1798 that.save_notebook();
1804 that.save_notebook();
1799 }
1805 }
1800 }, interval);
1806 }, interval);
1801 this.events.trigger("autosave_enabled.Notebook", interval);
1807 this.events.trigger("autosave_enabled.Notebook", interval);
1802 } else {
1808 } else {
1803 this.autosave_timer = null;
1809 this.autosave_timer = null;
1804 this.events.trigger("autosave_disabled.Notebook");
1810 this.events.trigger("autosave_disabled.Notebook");
1805 }
1811 }
1806 };
1812 };
1807
1813
1808 /**
1814 /**
1809 * Save this notebook on the server. This becomes a notebook instance's
1815 * Save this notebook on the server. This becomes a notebook instance's
1810 * .save_notebook method *after* the entire notebook has been loaded.
1816 * .save_notebook method *after* the entire notebook has been loaded.
1811 *
1817 *
1812 * @method save_notebook
1818 * @method save_notebook
1813 */
1819 */
1814 Notebook.prototype.save_notebook = function (extra_settings) {
1820 Notebook.prototype.save_notebook = function (extra_settings) {
1815 // Create a JSON model to be sent to the server.
1821 // Create a JSON model to be sent to the server.
1816 var model = {};
1822 var model = {};
1817 model.name = this.notebook_name;
1823 model.name = this.notebook_name;
1818 model.path = this.notebook_path;
1824 model.path = this.notebook_path;
1819 model.content = this.toJSON();
1825 model.content = this.toJSON();
1820 model.content.nbformat = this.nbformat;
1826 model.content.nbformat = this.nbformat;
1821 model.content.nbformat_minor = this.nbformat_minor;
1827 model.content.nbformat_minor = this.nbformat_minor;
1822 // time the ajax call for autosave tuning purposes.
1828 // time the ajax call for autosave tuning purposes.
1823 var start = new Date().getTime();
1829 var start = new Date().getTime();
1824 // We do the call with settings so we can set cache to false.
1830 // We do the call with settings so we can set cache to false.
1825 var settings = {
1831 var settings = {
1826 processData : false,
1832 processData : false,
1827 cache : false,
1833 cache : false,
1828 type : "PUT",
1834 type : "PUT",
1829 data : JSON.stringify(model),
1835 data : JSON.stringify(model),
1830 headers : {'Content-Type': 'application/json'},
1836 headers : {'Content-Type': 'application/json'},
1831 success : $.proxy(this.save_notebook_success, this, start),
1837 success : $.proxy(this.save_notebook_success, this, start),
1832 error : $.proxy(this.save_notebook_error, this)
1838 error : $.proxy(this.save_notebook_error, this)
1833 };
1839 };
1834 if (extra_settings) {
1840 if (extra_settings) {
1835 for (var key in extra_settings) {
1841 for (var key in extra_settings) {
1836 settings[key] = extra_settings[key];
1842 settings[key] = extra_settings[key];
1837 }
1843 }
1838 }
1844 }
1839 this.events.trigger('notebook_saving.Notebook');
1845 this.events.trigger('notebook_saving.Notebook');
1840 var url = utils.url_join_encode(
1846 var url = utils.url_join_encode(
1841 this.base_url,
1847 this.base_url,
1842 'api/notebooks',
1848 'api/notebooks',
1843 this.notebook_path,
1849 this.notebook_path,
1844 this.notebook_name
1850 this.notebook_name
1845 );
1851 );
1846 $.ajax(url, settings);
1852 $.ajax(url, settings);
1847 };
1853 };
1848
1854
1849 /**
1855 /**
1850 * Success callback for saving a notebook.
1856 * Success callback for saving a notebook.
1851 *
1857 *
1852 * @method save_notebook_success
1858 * @method save_notebook_success
1853 * @param {Integer} start the time when the save request started
1859 * @param {Integer} start the time when the save request started
1854 * @param {Object} data JSON representation of a notebook
1860 * @param {Object} data JSON representation of a notebook
1855 * @param {String} status Description of response status
1861 * @param {String} status Description of response status
1856 * @param {jqXHR} xhr jQuery Ajax object
1862 * @param {jqXHR} xhr jQuery Ajax object
1857 */
1863 */
1858 Notebook.prototype.save_notebook_success = function (start, data, status, xhr) {
1864 Notebook.prototype.save_notebook_success = function (start, data, status, xhr) {
1859 this.set_dirty(false);
1865 this.set_dirty(false);
1860 this.events.trigger('notebook_saved.Notebook');
1866 this.events.trigger('notebook_saved.Notebook');
1861 this._update_autosave_interval(start);
1867 this._update_autosave_interval(start);
1862 if (this._checkpoint_after_save) {
1868 if (this._checkpoint_after_save) {
1863 this.create_checkpoint();
1869 this.create_checkpoint();
1864 this._checkpoint_after_save = false;
1870 this._checkpoint_after_save = false;
1865 }
1871 }
1866 };
1872 };
1867
1873
1868 /**
1874 /**
1869 * update the autosave interval based on how long the last save took
1875 * update the autosave interval based on how long the last save took
1870 *
1876 *
1871 * @method _update_autosave_interval
1877 * @method _update_autosave_interval
1872 * @param {Integer} timestamp when the save request started
1878 * @param {Integer} timestamp when the save request started
1873 */
1879 */
1874 Notebook.prototype._update_autosave_interval = function (start) {
1880 Notebook.prototype._update_autosave_interval = function (start) {
1875 var duration = (new Date().getTime() - start);
1881 var duration = (new Date().getTime() - start);
1876 if (this.autosave_interval) {
1882 if (this.autosave_interval) {
1877 // new save interval: higher of 10x save duration or parameter (default 30 seconds)
1883 // new save interval: higher of 10x save duration or parameter (default 30 seconds)
1878 var interval = Math.max(10 * duration, this.minimum_autosave_interval);
1884 var interval = Math.max(10 * duration, this.minimum_autosave_interval);
1879 // round to 10 seconds, otherwise we will be setting a new interval too often
1885 // round to 10 seconds, otherwise we will be setting a new interval too often
1880 interval = 10000 * Math.round(interval / 10000);
1886 interval = 10000 * Math.round(interval / 10000);
1881 // set new interval, if it's changed
1887 // set new interval, if it's changed
1882 if (interval != this.autosave_interval) {
1888 if (interval != this.autosave_interval) {
1883 this.set_autosave_interval(interval);
1889 this.set_autosave_interval(interval);
1884 }
1890 }
1885 }
1891 }
1886 };
1892 };
1887
1893
1888 /**
1894 /**
1889 * Failure callback for saving a notebook.
1895 * Failure callback for saving a notebook.
1890 *
1896 *
1891 * @method save_notebook_error
1897 * @method save_notebook_error
1892 * @param {jqXHR} xhr jQuery Ajax object
1898 * @param {jqXHR} xhr jQuery Ajax object
1893 * @param {String} status Description of response status
1899 * @param {String} status Description of response status
1894 * @param {String} error HTTP error message
1900 * @param {String} error HTTP error message
1895 */
1901 */
1896 Notebook.prototype.save_notebook_error = function (xhr, status, error) {
1902 Notebook.prototype.save_notebook_error = function (xhr, status, error) {
1897 this.events.trigger('notebook_save_failed.Notebook', [xhr, status, error]);
1903 this.events.trigger('notebook_save_failed.Notebook', [xhr, status, error]);
1898 };
1904 };
1899
1905
1900 /**
1906 /**
1901 * Explicitly trust the output of this notebook.
1907 * Explicitly trust the output of this notebook.
1902 *
1908 *
1903 * @method trust_notebook
1909 * @method trust_notebook
1904 */
1910 */
1905 Notebook.prototype.trust_notebook = function (extra_settings) {
1911 Notebook.prototype.trust_notebook = function (extra_settings) {
1906 var body = $("<div>").append($("<p>")
1912 var body = $("<div>").append($("<p>")
1907 .text("A trusted IPython notebook may execute hidden malicious code ")
1913 .text("A trusted IPython notebook may execute hidden malicious code ")
1908 .append($("<strong>")
1914 .append($("<strong>")
1909 .append(
1915 .append(
1910 $("<em>").text("when you open it")
1916 $("<em>").text("when you open it")
1911 )
1917 )
1912 ).append(".").append(
1918 ).append(".").append(
1913 " Selecting trust will immediately reload this notebook in a trusted state."
1919 " Selecting trust will immediately reload this notebook in a trusted state."
1914 ).append(
1920 ).append(
1915 " For more information, see the "
1921 " For more information, see the "
1916 ).append($("<a>").attr("href", "http://ipython.org/ipython-doc/2/notebook/security.html")
1922 ).append($("<a>").attr("href", "http://ipython.org/ipython-doc/2/notebook/security.html")
1917 .text("IPython security documentation")
1923 .text("IPython security documentation")
1918 ).append(".")
1924 ).append(".")
1919 );
1925 );
1920
1926
1921 var nb = this;
1927 var nb = this;
1922 dialog.modal({
1928 dialog.modal({
1929 notebook: this,
1930 keyboard_manager: this.keyboard_manager,
1923 title: "Trust this notebook?",
1931 title: "Trust this notebook?",
1924 body: body,
1932 body: body,
1925
1933
1926 buttons: {
1934 buttons: {
1927 Cancel : {},
1935 Cancel : {},
1928 Trust : {
1936 Trust : {
1929 class : "btn-danger",
1937 class : "btn-danger",
1930 click : function () {
1938 click : function () {
1931 var cells = nb.get_cells();
1939 var cells = nb.get_cells();
1932 for (var i = 0; i < cells.length; i++) {
1940 for (var i = 0; i < cells.length; i++) {
1933 var cell = cells[i];
1941 var cell = cells[i];
1934 if (cell.cell_type == 'code') {
1942 if (cell.cell_type == 'code') {
1935 cell.output_area.trusted = true;
1943 cell.output_area.trusted = true;
1936 }
1944 }
1937 }
1945 }
1938 this.events.on('notebook_saved.Notebook', function () {
1946 this.events.on('notebook_saved.Notebook', function () {
1939 window.location.reload();
1947 window.location.reload();
1940 });
1948 });
1941 nb.save_notebook();
1949 nb.save_notebook();
1942 }
1950 }
1943 }
1951 }
1944 }
1952 }
1945 });
1953 });
1946 };
1954 };
1947
1955
1948 Notebook.prototype.new_notebook = function(){
1956 Notebook.prototype.new_notebook = function(){
1949 var path = this.notebook_path;
1957 var path = this.notebook_path;
1950 var base_url = this.base_url;
1958 var base_url = this.base_url;
1951 var settings = {
1959 var settings = {
1952 processData : false,
1960 processData : false,
1953 cache : false,
1961 cache : false,
1954 type : "POST",
1962 type : "POST",
1955 dataType : "json",
1963 dataType : "json",
1956 async : false,
1964 async : false,
1957 success : function (data, status, xhr){
1965 success : function (data, status, xhr){
1958 var notebook_name = data.name;
1966 var notebook_name = data.name;
1959 window.open(
1967 window.open(
1960 utils.url_join_encode(
1968 utils.url_join_encode(
1961 base_url,
1969 base_url,
1962 'notebooks',
1970 'notebooks',
1963 path,
1971 path,
1964 notebook_name
1972 notebook_name
1965 ),
1973 ),
1966 '_blank'
1974 '_blank'
1967 );
1975 );
1968 },
1976 },
1969 error : utils.log_ajax_error,
1977 error : utils.log_ajax_error,
1970 };
1978 };
1971 var url = utils.url_join_encode(
1979 var url = utils.url_join_encode(
1972 base_url,
1980 base_url,
1973 'api/notebooks',
1981 'api/notebooks',
1974 path
1982 path
1975 );
1983 );
1976 $.ajax(url,settings);
1984 $.ajax(url,settings);
1977 };
1985 };
1978
1986
1979
1987
1980 Notebook.prototype.copy_notebook = function(){
1988 Notebook.prototype.copy_notebook = function(){
1981 var path = this.notebook_path;
1989 var path = this.notebook_path;
1982 var base_url = this.base_url;
1990 var base_url = this.base_url;
1983 var settings = {
1991 var settings = {
1984 processData : false,
1992 processData : false,
1985 cache : false,
1993 cache : false,
1986 type : "POST",
1994 type : "POST",
1987 dataType : "json",
1995 dataType : "json",
1988 data : JSON.stringify({copy_from : this.notebook_name}),
1996 data : JSON.stringify({copy_from : this.notebook_name}),
1989 async : false,
1997 async : false,
1990 success : function (data, status, xhr) {
1998 success : function (data, status, xhr) {
1991 window.open(utils.url_join_encode(
1999 window.open(utils.url_join_encode(
1992 base_url,
2000 base_url,
1993 'notebooks',
2001 'notebooks',
1994 data.path,
2002 data.path,
1995 data.name
2003 data.name
1996 ), '_blank');
2004 ), '_blank');
1997 },
2005 },
1998 error : utils.log_ajax_error,
2006 error : utils.log_ajax_error,
1999 };
2007 };
2000 var url = utils.url_join_encode(
2008 var url = utils.url_join_encode(
2001 base_url,
2009 base_url,
2002 'api/notebooks',
2010 'api/notebooks',
2003 path
2011 path
2004 );
2012 );
2005 $.ajax(url,settings);
2013 $.ajax(url,settings);
2006 };
2014 };
2007
2015
2008 Notebook.prototype.rename = function (nbname) {
2016 Notebook.prototype.rename = function (nbname) {
2009 var that = this;
2017 var that = this;
2010 if (!nbname.match(/\.ipynb$/)) {
2018 if (!nbname.match(/\.ipynb$/)) {
2011 nbname = nbname + ".ipynb";
2019 nbname = nbname + ".ipynb";
2012 }
2020 }
2013 var data = {name: nbname};
2021 var data = {name: nbname};
2014 var settings = {
2022 var settings = {
2015 processData : false,
2023 processData : false,
2016 cache : false,
2024 cache : false,
2017 type : "PATCH",
2025 type : "PATCH",
2018 data : JSON.stringify(data),
2026 data : JSON.stringify(data),
2019 dataType: "json",
2027 dataType: "json",
2020 headers : {'Content-Type': 'application/json'},
2028 headers : {'Content-Type': 'application/json'},
2021 success : $.proxy(that.rename_success, this),
2029 success : $.proxy(that.rename_success, this),
2022 error : $.proxy(that.rename_error, this)
2030 error : $.proxy(that.rename_error, this)
2023 };
2031 };
2024 this.events.trigger('rename_notebook.Notebook', data);
2032 this.events.trigger('rename_notebook.Notebook', data);
2025 var url = utils.url_join_encode(
2033 var url = utils.url_join_encode(
2026 this.base_url,
2034 this.base_url,
2027 'api/notebooks',
2035 'api/notebooks',
2028 this.notebook_path,
2036 this.notebook_path,
2029 this.notebook_name
2037 this.notebook_name
2030 );
2038 );
2031 $.ajax(url, settings);
2039 $.ajax(url, settings);
2032 };
2040 };
2033
2041
2034 Notebook.prototype.delete = function () {
2042 Notebook.prototype.delete = function () {
2035 var that = this;
2043 var that = this;
2036 var settings = {
2044 var settings = {
2037 processData : false,
2045 processData : false,
2038 cache : false,
2046 cache : false,
2039 type : "DELETE",
2047 type : "DELETE",
2040 dataType: "json",
2048 dataType: "json",
2041 error : utils.log_ajax_error,
2049 error : utils.log_ajax_error,
2042 };
2050 };
2043 var url = utils.url_join_encode(
2051 var url = utils.url_join_encode(
2044 this.base_url,
2052 this.base_url,
2045 'api/notebooks',
2053 'api/notebooks',
2046 this.notebook_path,
2054 this.notebook_path,
2047 this.notebook_name
2055 this.notebook_name
2048 );
2056 );
2049 $.ajax(url, settings);
2057 $.ajax(url, settings);
2050 };
2058 };
2051
2059
2052
2060
2053 Notebook.prototype.rename_success = function (json, status, xhr) {
2061 Notebook.prototype.rename_success = function (json, status, xhr) {
2054 var name = this.notebook_name = json.name;
2062 var name = this.notebook_name = json.name;
2055 var path = json.path;
2063 var path = json.path;
2056 this.session.rename_notebook(name, path);
2064 this.session.rename_notebook(name, path);
2057 this.events.trigger('notebook_renamed.Notebook', json);
2065 this.events.trigger('notebook_renamed.Notebook', json);
2058 };
2066 };
2059
2067
2060 Notebook.prototype.rename_error = function (xhr, status, error) {
2068 Notebook.prototype.rename_error = function (xhr, status, error) {
2061 var that = this;
2069 var that = this;
2062 var dialog_body = $('<div/>').append(
2070 var dialog_body = $('<div/>').append(
2063 $("<p/>").addClass("rename-message")
2071 $("<p/>").addClass("rename-message")
2064 .text('This notebook name already exists.')
2072 .text('This notebook name already exists.')
2065 );
2073 );
2066 this.events.trigger('notebook_rename_failed.Notebook', [xhr, status, error]);
2074 this.events.trigger('notebook_rename_failed.Notebook', [xhr, status, error]);
2067 dialog.modal({
2075 dialog.modal({
2076 notebook: this,
2077 keyboard_manager: this.keyboard_manager,
2068 title: "Notebook Rename Error!",
2078 title: "Notebook Rename Error!",
2069 body: dialog_body,
2079 body: dialog_body,
2070 buttons : {
2080 buttons : {
2071 "Cancel": {},
2081 "Cancel": {},
2072 "OK": {
2082 "OK": {
2073 class: "btn-primary",
2083 class: "btn-primary",
2074 click: function () {
2084 click: function () {
2075 this.save_widget.rename_notebook();
2085 this.save_widget.rename_notebook();
2076 }}
2086 }}
2077 },
2087 },
2078 open : function (event, ui) {
2088 open : function (event, ui) {
2079 var that = $(this);
2089 var that = $(this);
2080 // Upon ENTER, click the OK button.
2090 // Upon ENTER, click the OK button.
2081 that.find('input[type="text"]').keydown(function (event, ui) {
2091 that.find('input[type="text"]').keydown(function (event, ui) {
2082 if (event.which === this.keyboard.keycodes.enter) {
2092 if (event.which === this.keyboard.keycodes.enter) {
2083 that.find('.btn-primary').first().click();
2093 that.find('.btn-primary').first().click();
2084 }
2094 }
2085 });
2095 });
2086 that.find('input[type="text"]').focus();
2096 that.find('input[type="text"]').focus();
2087 }
2097 }
2088 });
2098 });
2089 };
2099 };
2090
2100
2091 /**
2101 /**
2092 * Request a notebook's data from the server.
2102 * Request a notebook's data from the server.
2093 *
2103 *
2094 * @method load_notebook
2104 * @method load_notebook
2095 * @param {String} notebook_name and path A notebook to load
2105 * @param {String} notebook_name and path A notebook to load
2096 */
2106 */
2097 Notebook.prototype.load_notebook = function (notebook_name, notebook_path) {
2107 Notebook.prototype.load_notebook = function (notebook_name, notebook_path) {
2098 var that = this;
2108 var that = this;
2099 this.notebook_name = notebook_name;
2109 this.notebook_name = notebook_name;
2100 this.notebook_path = notebook_path;
2110 this.notebook_path = notebook_path;
2101 // We do the call with settings so we can set cache to false.
2111 // We do the call with settings so we can set cache to false.
2102 var settings = {
2112 var settings = {
2103 processData : false,
2113 processData : false,
2104 cache : false,
2114 cache : false,
2105 type : "GET",
2115 type : "GET",
2106 dataType : "json",
2116 dataType : "json",
2107 success : $.proxy(this.load_notebook_success,this),
2117 success : $.proxy(this.load_notebook_success,this),
2108 error : $.proxy(this.load_notebook_error,this),
2118 error : $.proxy(this.load_notebook_error,this),
2109 };
2119 };
2110 this.events.trigger('notebook_loading.Notebook');
2120 this.events.trigger('notebook_loading.Notebook');
2111 var url = utils.url_join_encode(
2121 var url = utils.url_join_encode(
2112 this.base_url,
2122 this.base_url,
2113 'api/notebooks',
2123 'api/notebooks',
2114 this.notebook_path,
2124 this.notebook_path,
2115 this.notebook_name
2125 this.notebook_name
2116 );
2126 );
2117 $.ajax(url, settings);
2127 $.ajax(url, settings);
2118 };
2128 };
2119
2129
2120 /**
2130 /**
2121 * Success callback for loading a notebook from the server.
2131 * Success callback for loading a notebook from the server.
2122 *
2132 *
2123 * Load notebook data from the JSON response.
2133 * Load notebook data from the JSON response.
2124 *
2134 *
2125 * @method load_notebook_success
2135 * @method load_notebook_success
2126 * @param {Object} data JSON representation of a notebook
2136 * @param {Object} data JSON representation of a notebook
2127 * @param {String} status Description of response status
2137 * @param {String} status Description of response status
2128 * @param {jqXHR} xhr jQuery Ajax object
2138 * @param {jqXHR} xhr jQuery Ajax object
2129 */
2139 */
2130 Notebook.prototype.load_notebook_success = function (data, status, xhr) {
2140 Notebook.prototype.load_notebook_success = function (data, status, xhr) {
2131 this.fromJSON(data);
2141 this.fromJSON(data);
2132 if (this.ncells() === 0) {
2142 if (this.ncells() === 0) {
2133 this.insert_cell_below('code');
2143 this.insert_cell_below('code');
2134 this.edit_mode(0);
2144 this.edit_mode(0);
2135 } else {
2145 } else {
2136 this.select(0);
2146 this.select(0);
2137 this.handle_command_mode(this.get_cell(0));
2147 this.handle_command_mode(this.get_cell(0));
2138 }
2148 }
2139 this.set_dirty(false);
2149 this.set_dirty(false);
2140 this.scroll_to_top();
2150 this.scroll_to_top();
2141 if (data.orig_nbformat !== undefined && data.nbformat !== data.orig_nbformat) {
2151 if (data.orig_nbformat !== undefined && data.nbformat !== data.orig_nbformat) {
2142 var msg = "This notebook has been converted from an older " +
2152 var msg = "This notebook has been converted from an older " +
2143 "notebook format (v"+data.orig_nbformat+") to the current notebook " +
2153 "notebook format (v"+data.orig_nbformat+") to the current notebook " +
2144 "format (v"+data.nbformat+"). The next time you save this notebook, the " +
2154 "format (v"+data.nbformat+"). The next time you save this notebook, the " +
2145 "newer notebook format will be used and older versions of IPython " +
2155 "newer notebook format will be used and older versions of IPython " +
2146 "may not be able to read it. To keep the older version, close the " +
2156 "may not be able to read it. To keep the older version, close the " +
2147 "notebook without saving it.";
2157 "notebook without saving it.";
2148 dialog.modal({
2158 dialog.modal({
2159 notebook: this,
2160 keyboard_manager: this.keyboard_manager,
2149 title : "Notebook converted",
2161 title : "Notebook converted",
2150 body : msg,
2162 body : msg,
2151 buttons : {
2163 buttons : {
2152 OK : {
2164 OK : {
2153 class : "btn-primary"
2165 class : "btn-primary"
2154 }
2166 }
2155 }
2167 }
2156 });
2168 });
2157 } else if (data.orig_nbformat_minor !== undefined && data.nbformat_minor !== data.orig_nbformat_minor) {
2169 } else if (data.orig_nbformat_minor !== undefined && data.nbformat_minor !== data.orig_nbformat_minor) {
2158 var that = this;
2170 var that = this;
2159 var orig_vs = 'v' + data.nbformat + '.' + data.orig_nbformat_minor;
2171 var orig_vs = 'v' + data.nbformat + '.' + data.orig_nbformat_minor;
2160 var this_vs = 'v' + data.nbformat + '.' + this.nbformat_minor;
2172 var this_vs = 'v' + data.nbformat + '.' + this.nbformat_minor;
2161 var msg = "This notebook is version " + orig_vs + ", but we only fully support up to " +
2173 var msg = "This notebook is version " + orig_vs + ", but we only fully support up to " +
2162 this_vs + ". You can still work with this notebook, but some features " +
2174 this_vs + ". You can still work with this notebook, but some features " +
2163 "introduced in later notebook versions may not be available.";
2175 "introduced in later notebook versions may not be available.";
2164
2176
2165 dialog.modal({
2177 dialog.modal({
2178 notebook: this,
2179 keyboard_manager: this.keyboard_manager,
2166 title : "Newer Notebook",
2180 title : "Newer Notebook",
2167 body : msg,
2181 body : msg,
2168 buttons : {
2182 buttons : {
2169 OK : {
2183 OK : {
2170 class : "btn-danger"
2184 class : "btn-danger"
2171 }
2185 }
2172 }
2186 }
2173 });
2187 });
2174
2188
2175 }
2189 }
2176
2190
2177 // Create the session after the notebook is completely loaded to prevent
2191 // Create the session after the notebook is completely loaded to prevent
2178 // code execution upon loading, which is a security risk.
2192 // code execution upon loading, which is a security risk.
2179 if (this.session === null) {
2193 if (this.session === null) {
2180 this.start_session();
2194 this.start_session();
2181 }
2195 }
2182 // load our checkpoint list
2196 // load our checkpoint list
2183 this.list_checkpoints();
2197 this.list_checkpoints();
2184
2198
2185 // load toolbar state
2199 // load toolbar state
2186 if (this.metadata.celltoolbar) {
2200 if (this.metadata.celltoolbar) {
2187 celltoolbar.CellToolbar.global_show();
2201 celltoolbar.CellToolbar.global_show();
2188 celltoolbar.CellToolbar.activate_preset(this.metadata.celltoolbar);
2202 celltoolbar.CellToolbar.activate_preset(this.metadata.celltoolbar);
2189 } else {
2203 } else {
2190 celltoolbar.CellToolbar.global_hide();
2204 celltoolbar.CellToolbar.global_hide();
2191 }
2205 }
2192
2206
2193 // now that we're fully loaded, it is safe to restore save functionality
2207 // now that we're fully loaded, it is safe to restore save functionality
2194 delete(this.save_notebook);
2208 delete(this.save_notebook);
2195 this.events.trigger('notebook_loaded.Notebook');
2209 this.events.trigger('notebook_loaded.Notebook');
2196 };
2210 };
2197
2211
2198 /**
2212 /**
2199 * Failure callback for loading a notebook from the server.
2213 * Failure callback for loading a notebook from the server.
2200 *
2214 *
2201 * @method load_notebook_error
2215 * @method load_notebook_error
2202 * @param {jqXHR} xhr jQuery Ajax object
2216 * @param {jqXHR} xhr jQuery Ajax object
2203 * @param {String} status Description of response status
2217 * @param {String} status Description of response status
2204 * @param {String} error HTTP error message
2218 * @param {String} error HTTP error message
2205 */
2219 */
2206 Notebook.prototype.load_notebook_error = function (xhr, status, error) {
2220 Notebook.prototype.load_notebook_error = function (xhr, status, error) {
2207 this.events.trigger('notebook_load_failed.Notebook', [xhr, status, error]);
2221 this.events.trigger('notebook_load_failed.Notebook', [xhr, status, error]);
2208 var msg;
2222 var msg;
2209 if (xhr.status === 400) {
2223 if (xhr.status === 400) {
2210 msg = error;
2224 msg = error;
2211 } else if (xhr.status === 500) {
2225 } else if (xhr.status === 500) {
2212 msg = "An unknown error occurred while loading this notebook. " +
2226 msg = "An unknown error occurred while loading this notebook. " +
2213 "This version can load notebook formats " +
2227 "This version can load notebook formats " +
2214 "v" + this.nbformat + " or earlier.";
2228 "v" + this.nbformat + " or earlier.";
2215 }
2229 }
2216 dialog.modal({
2230 dialog.modal({
2231 notebook: this,
2232 keyboard_manager: this.keyboard_manager,
2217 title: "Error loading notebook",
2233 title: "Error loading notebook",
2218 body : msg,
2234 body : msg,
2219 buttons : {
2235 buttons : {
2220 "OK": {}
2236 "OK": {}
2221 }
2237 }
2222 });
2238 });
2223 };
2239 };
2224
2240
2225 /********************* checkpoint-related *********************/
2241 /********************* checkpoint-related *********************/
2226
2242
2227 /**
2243 /**
2228 * Save the notebook then immediately create a checkpoint.
2244 * Save the notebook then immediately create a checkpoint.
2229 *
2245 *
2230 * @method save_checkpoint
2246 * @method save_checkpoint
2231 */
2247 */
2232 Notebook.prototype.save_checkpoint = function () {
2248 Notebook.prototype.save_checkpoint = function () {
2233 this._checkpoint_after_save = true;
2249 this._checkpoint_after_save = true;
2234 this.save_notebook();
2250 this.save_notebook();
2235 };
2251 };
2236
2252
2237 /**
2253 /**
2238 * Add a checkpoint for this notebook.
2254 * Add a checkpoint for this notebook.
2239 * for use as a callback from checkpoint creation.
2255 * for use as a callback from checkpoint creation.
2240 *
2256 *
2241 * @method add_checkpoint
2257 * @method add_checkpoint
2242 */
2258 */
2243 Notebook.prototype.add_checkpoint = function (checkpoint) {
2259 Notebook.prototype.add_checkpoint = function (checkpoint) {
2244 var found = false;
2260 var found = false;
2245 for (var i = 0; i < this.checkpoints.length; i++) {
2261 for (var i = 0; i < this.checkpoints.length; i++) {
2246 var existing = this.checkpoints[i];
2262 var existing = this.checkpoints[i];
2247 if (existing.id == checkpoint.id) {
2263 if (existing.id == checkpoint.id) {
2248 found = true;
2264 found = true;
2249 this.checkpoints[i] = checkpoint;
2265 this.checkpoints[i] = checkpoint;
2250 break;
2266 break;
2251 }
2267 }
2252 }
2268 }
2253 if (!found) {
2269 if (!found) {
2254 this.checkpoints.push(checkpoint);
2270 this.checkpoints.push(checkpoint);
2255 }
2271 }
2256 this.last_checkpoint = this.checkpoints[this.checkpoints.length - 1];
2272 this.last_checkpoint = this.checkpoints[this.checkpoints.length - 1];
2257 };
2273 };
2258
2274
2259 /**
2275 /**
2260 * List checkpoints for this notebook.
2276 * List checkpoints for this notebook.
2261 *
2277 *
2262 * @method list_checkpoints
2278 * @method list_checkpoints
2263 */
2279 */
2264 Notebook.prototype.list_checkpoints = function () {
2280 Notebook.prototype.list_checkpoints = function () {
2265 var url = utils.url_join_encode(
2281 var url = utils.url_join_encode(
2266 this.base_url,
2282 this.base_url,
2267 'api/notebooks',
2283 'api/notebooks',
2268 this.notebook_path,
2284 this.notebook_path,
2269 this.notebook_name,
2285 this.notebook_name,
2270 'checkpoints'
2286 'checkpoints'
2271 );
2287 );
2272 $.get(url).done(
2288 $.get(url).done(
2273 $.proxy(this.list_checkpoints_success, this)
2289 $.proxy(this.list_checkpoints_success, this)
2274 ).fail(
2290 ).fail(
2275 $.proxy(this.list_checkpoints_error, this)
2291 $.proxy(this.list_checkpoints_error, this)
2276 );
2292 );
2277 };
2293 };
2278
2294
2279 /**
2295 /**
2280 * Success callback for listing checkpoints.
2296 * Success callback for listing checkpoints.
2281 *
2297 *
2282 * @method list_checkpoint_success
2298 * @method list_checkpoint_success
2283 * @param {Object} data JSON representation of a checkpoint
2299 * @param {Object} data JSON representation of a checkpoint
2284 * @param {String} status Description of response status
2300 * @param {String} status Description of response status
2285 * @param {jqXHR} xhr jQuery Ajax object
2301 * @param {jqXHR} xhr jQuery Ajax object
2286 */
2302 */
2287 Notebook.prototype.list_checkpoints_success = function (data, status, xhr) {
2303 Notebook.prototype.list_checkpoints_success = function (data, status, xhr) {
2288 data = $.parseJSON(data);
2304 data = $.parseJSON(data);
2289 this.checkpoints = data;
2305 this.checkpoints = data;
2290 if (data.length) {
2306 if (data.length) {
2291 this.last_checkpoint = data[data.length - 1];
2307 this.last_checkpoint = data[data.length - 1];
2292 } else {
2308 } else {
2293 this.last_checkpoint = null;
2309 this.last_checkpoint = null;
2294 }
2310 }
2295 this.events.trigger('checkpoints_listed.Notebook', [data]);
2311 this.events.trigger('checkpoints_listed.Notebook', [data]);
2296 };
2312 };
2297
2313
2298 /**
2314 /**
2299 * Failure callback for listing a checkpoint.
2315 * Failure callback for listing a checkpoint.
2300 *
2316 *
2301 * @method list_checkpoint_error
2317 * @method list_checkpoint_error
2302 * @param {jqXHR} xhr jQuery Ajax object
2318 * @param {jqXHR} xhr jQuery Ajax object
2303 * @param {String} status Description of response status
2319 * @param {String} status Description of response status
2304 * @param {String} error_msg HTTP error message
2320 * @param {String} error_msg HTTP error message
2305 */
2321 */
2306 Notebook.prototype.list_checkpoints_error = function (xhr, status, error_msg) {
2322 Notebook.prototype.list_checkpoints_error = function (xhr, status, error_msg) {
2307 this.events.trigger('list_checkpoints_failed.Notebook');
2323 this.events.trigger('list_checkpoints_failed.Notebook');
2308 };
2324 };
2309
2325
2310 /**
2326 /**
2311 * Create a checkpoint of this notebook on the server from the most recent save.
2327 * Create a checkpoint of this notebook on the server from the most recent save.
2312 *
2328 *
2313 * @method create_checkpoint
2329 * @method create_checkpoint
2314 */
2330 */
2315 Notebook.prototype.create_checkpoint = function () {
2331 Notebook.prototype.create_checkpoint = function () {
2316 var url = utils.url_join_encode(
2332 var url = utils.url_join_encode(
2317 this.base_url,
2333 this.base_url,
2318 'api/notebooks',
2334 'api/notebooks',
2319 this.notebook_path,
2335 this.notebook_path,
2320 this.notebook_name,
2336 this.notebook_name,
2321 'checkpoints'
2337 'checkpoints'
2322 );
2338 );
2323 $.post(url).done(
2339 $.post(url).done(
2324 $.proxy(this.create_checkpoint_success, this)
2340 $.proxy(this.create_checkpoint_success, this)
2325 ).fail(
2341 ).fail(
2326 $.proxy(this.create_checkpoint_error, this)
2342 $.proxy(this.create_checkpoint_error, this)
2327 );
2343 );
2328 };
2344 };
2329
2345
2330 /**
2346 /**
2331 * Success callback for creating a checkpoint.
2347 * Success callback for creating a checkpoint.
2332 *
2348 *
2333 * @method create_checkpoint_success
2349 * @method create_checkpoint_success
2334 * @param {Object} data JSON representation of a checkpoint
2350 * @param {Object} data JSON representation of a checkpoint
2335 * @param {String} status Description of response status
2351 * @param {String} status Description of response status
2336 * @param {jqXHR} xhr jQuery Ajax object
2352 * @param {jqXHR} xhr jQuery Ajax object
2337 */
2353 */
2338 Notebook.prototype.create_checkpoint_success = function (data, status, xhr) {
2354 Notebook.prototype.create_checkpoint_success = function (data, status, xhr) {
2339 data = $.parseJSON(data);
2355 data = $.parseJSON(data);
2340 this.add_checkpoint(data);
2356 this.add_checkpoint(data);
2341 this.events.trigger('checkpoint_created.Notebook', data);
2357 this.events.trigger('checkpoint_created.Notebook', data);
2342 };
2358 };
2343
2359
2344 /**
2360 /**
2345 * Failure callback for creating a checkpoint.
2361 * Failure callback for creating a checkpoint.
2346 *
2362 *
2347 * @method create_checkpoint_error
2363 * @method create_checkpoint_error
2348 * @param {jqXHR} xhr jQuery Ajax object
2364 * @param {jqXHR} xhr jQuery Ajax object
2349 * @param {String} status Description of response status
2365 * @param {String} status Description of response status
2350 * @param {String} error_msg HTTP error message
2366 * @param {String} error_msg HTTP error message
2351 */
2367 */
2352 Notebook.prototype.create_checkpoint_error = function (xhr, status, error_msg) {
2368 Notebook.prototype.create_checkpoint_error = function (xhr, status, error_msg) {
2353 this.events.trigger('checkpoint_failed.Notebook');
2369 this.events.trigger('checkpoint_failed.Notebook');
2354 };
2370 };
2355
2371
2356 Notebook.prototype.restore_checkpoint_dialog = function (checkpoint) {
2372 Notebook.prototype.restore_checkpoint_dialog = function (checkpoint) {
2357 var that = this;
2373 var that = this;
2358 checkpoint = checkpoint || this.last_checkpoint;
2374 checkpoint = checkpoint || this.last_checkpoint;
2359 if ( ! checkpoint ) {
2375 if ( ! checkpoint ) {
2360 console.log("restore dialog, but no checkpoint to restore to!");
2376 console.log("restore dialog, but no checkpoint to restore to!");
2361 return;
2377 return;
2362 }
2378 }
2363 var body = $('<div/>').append(
2379 var body = $('<div/>').append(
2364 $('<p/>').addClass("p-space").text(
2380 $('<p/>').addClass("p-space").text(
2365 "Are you sure you want to revert the notebook to " +
2381 "Are you sure you want to revert the notebook to " +
2366 "the latest checkpoint?"
2382 "the latest checkpoint?"
2367 ).append(
2383 ).append(
2368 $("<strong/>").text(
2384 $("<strong/>").text(
2369 " This cannot be undone."
2385 " This cannot be undone."
2370 )
2386 )
2371 )
2387 )
2372 ).append(
2388 ).append(
2373 $('<p/>').addClass("p-space").text("The checkpoint was last updated at:")
2389 $('<p/>').addClass("p-space").text("The checkpoint was last updated at:")
2374 ).append(
2390 ).append(
2375 $('<p/>').addClass("p-space").text(
2391 $('<p/>').addClass("p-space").text(
2376 Date(checkpoint.last_modified)
2392 Date(checkpoint.last_modified)
2377 ).css("text-align", "center")
2393 ).css("text-align", "center")
2378 );
2394 );
2379
2395
2380 dialog.modal({
2396 dialog.modal({
2397 notebook: this,
2398 keyboard_manager: this.keyboard_manager,
2381 title : "Revert notebook to checkpoint",
2399 title : "Revert notebook to checkpoint",
2382 body : body,
2400 body : body,
2383 buttons : {
2401 buttons : {
2384 Revert : {
2402 Revert : {
2385 class : "btn-danger",
2403 class : "btn-danger",
2386 click : function () {
2404 click : function () {
2387 that.restore_checkpoint(checkpoint.id);
2405 that.restore_checkpoint(checkpoint.id);
2388 }
2406 }
2389 },
2407 },
2390 Cancel : {}
2408 Cancel : {}
2391 }
2409 }
2392 });
2410 });
2393 };
2411 };
2394
2412
2395 /**
2413 /**
2396 * Restore the notebook to a checkpoint state.
2414 * Restore the notebook to a checkpoint state.
2397 *
2415 *
2398 * @method restore_checkpoint
2416 * @method restore_checkpoint
2399 * @param {String} checkpoint ID
2417 * @param {String} checkpoint ID
2400 */
2418 */
2401 Notebook.prototype.restore_checkpoint = function (checkpoint) {
2419 Notebook.prototype.restore_checkpoint = function (checkpoint) {
2402 this.events.trigger('notebook_restoring.Notebook', checkpoint);
2420 this.events.trigger('notebook_restoring.Notebook', checkpoint);
2403 var url = utils.url_join_encode(
2421 var url = utils.url_join_encode(
2404 this.base_url,
2422 this.base_url,
2405 'api/notebooks',
2423 'api/notebooks',
2406 this.notebook_path,
2424 this.notebook_path,
2407 this.notebook_name,
2425 this.notebook_name,
2408 'checkpoints',
2426 'checkpoints',
2409 checkpoint
2427 checkpoint
2410 );
2428 );
2411 $.post(url).done(
2429 $.post(url).done(
2412 $.proxy(this.restore_checkpoint_success, this)
2430 $.proxy(this.restore_checkpoint_success, this)
2413 ).fail(
2431 ).fail(
2414 $.proxy(this.restore_checkpoint_error, this)
2432 $.proxy(this.restore_checkpoint_error, this)
2415 );
2433 );
2416 };
2434 };
2417
2435
2418 /**
2436 /**
2419 * Success callback for restoring a notebook to a checkpoint.
2437 * Success callback for restoring a notebook to a checkpoint.
2420 *
2438 *
2421 * @method restore_checkpoint_success
2439 * @method restore_checkpoint_success
2422 * @param {Object} data (ignored, should be empty)
2440 * @param {Object} data (ignored, should be empty)
2423 * @param {String} status Description of response status
2441 * @param {String} status Description of response status
2424 * @param {jqXHR} xhr jQuery Ajax object
2442 * @param {jqXHR} xhr jQuery Ajax object
2425 */
2443 */
2426 Notebook.prototype.restore_checkpoint_success = function (data, status, xhr) {
2444 Notebook.prototype.restore_checkpoint_success = function (data, status, xhr) {
2427 this.events.trigger('checkpoint_restored.Notebook');
2445 this.events.trigger('checkpoint_restored.Notebook');
2428 this.load_notebook(this.notebook_name, this.notebook_path);
2446 this.load_notebook(this.notebook_name, this.notebook_path);
2429 };
2447 };
2430
2448
2431 /**
2449 /**
2432 * Failure callback for restoring a notebook to a checkpoint.
2450 * Failure callback for restoring a notebook to a checkpoint.
2433 *
2451 *
2434 * @method restore_checkpoint_error
2452 * @method restore_checkpoint_error
2435 * @param {jqXHR} xhr jQuery Ajax object
2453 * @param {jqXHR} xhr jQuery Ajax object
2436 * @param {String} status Description of response status
2454 * @param {String} status Description of response status
2437 * @param {String} error_msg HTTP error message
2455 * @param {String} error_msg HTTP error message
2438 */
2456 */
2439 Notebook.prototype.restore_checkpoint_error = function (xhr, status, error_msg) {
2457 Notebook.prototype.restore_checkpoint_error = function (xhr, status, error_msg) {
2440 this.events.trigger('checkpoint_restore_failed.Notebook');
2458 this.events.trigger('checkpoint_restore_failed.Notebook');
2441 };
2459 };
2442
2460
2443 /**
2461 /**
2444 * Delete a notebook checkpoint.
2462 * Delete a notebook checkpoint.
2445 *
2463 *
2446 * @method delete_checkpoint
2464 * @method delete_checkpoint
2447 * @param {String} checkpoint ID
2465 * @param {String} checkpoint ID
2448 */
2466 */
2449 Notebook.prototype.delete_checkpoint = function (checkpoint) {
2467 Notebook.prototype.delete_checkpoint = function (checkpoint) {
2450 this.events.trigger('notebook_restoring.Notebook', checkpoint);
2468 this.events.trigger('notebook_restoring.Notebook', checkpoint);
2451 var url = utils.url_join_encode(
2469 var url = utils.url_join_encode(
2452 this.base_url,
2470 this.base_url,
2453 'api/notebooks',
2471 'api/notebooks',
2454 this.notebook_path,
2472 this.notebook_path,
2455 this.notebook_name,
2473 this.notebook_name,
2456 'checkpoints',
2474 'checkpoints',
2457 checkpoint
2475 checkpoint
2458 );
2476 );
2459 $.ajax(url, {
2477 $.ajax(url, {
2460 type: 'DELETE',
2478 type: 'DELETE',
2461 success: $.proxy(this.delete_checkpoint_success, this),
2479 success: $.proxy(this.delete_checkpoint_success, this),
2462 error: $.proxy(this.delete_checkpoint_error, this)
2480 error: $.proxy(this.delete_checkpoint_error, this)
2463 });
2481 });
2464 };
2482 };
2465
2483
2466 /**
2484 /**
2467 * Success callback for deleting a notebook checkpoint
2485 * Success callback for deleting a notebook checkpoint
2468 *
2486 *
2469 * @method delete_checkpoint_success
2487 * @method delete_checkpoint_success
2470 * @param {Object} data (ignored, should be empty)
2488 * @param {Object} data (ignored, should be empty)
2471 * @param {String} status Description of response status
2489 * @param {String} status Description of response status
2472 * @param {jqXHR} xhr jQuery Ajax object
2490 * @param {jqXHR} xhr jQuery Ajax object
2473 */
2491 */
2474 Notebook.prototype.delete_checkpoint_success = function (data, status, xhr) {
2492 Notebook.prototype.delete_checkpoint_success = function (data, status, xhr) {
2475 this.events.trigger('checkpoint_deleted.Notebook', data);
2493 this.events.trigger('checkpoint_deleted.Notebook', data);
2476 this.load_notebook(this.notebook_name, this.notebook_path);
2494 this.load_notebook(this.notebook_name, this.notebook_path);
2477 };
2495 };
2478
2496
2479 /**
2497 /**
2480 * Failure callback for deleting a notebook checkpoint.
2498 * Failure callback for deleting a notebook checkpoint.
2481 *
2499 *
2482 * @method delete_checkpoint_error
2500 * @method delete_checkpoint_error
2483 * @param {jqXHR} xhr jQuery Ajax object
2501 * @param {jqXHR} xhr jQuery Ajax object
2484 * @param {String} status Description of response status
2502 * @param {String} status Description of response status
2485 * @param {String} error_msg HTTP error message
2503 * @param {String} error_msg HTTP error message
2486 */
2504 */
2487 Notebook.prototype.delete_checkpoint_error = function (xhr, status, error_msg) {
2505 Notebook.prototype.delete_checkpoint_error = function (xhr, status, error_msg) {
2488 this.events.trigger('checkpoint_delete_failed.Notebook');
2506 this.events.trigger('checkpoint_delete_failed.Notebook');
2489 };
2507 };
2490
2508
2491
2509
2492 // For backwards compatability.
2510 // For backwards compatability.
2493 IPython.Notebook = Notebook;
2511 IPython.Notebook = Notebook;
2494
2512
2495 return {'Notebook': Notebook};
2513 return {'Notebook': Notebook};
2496 });
2514 });
@@ -1,242 +1,247
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 define([
4 define([
5 'base/js/namespace',
5 'base/js/namespace',
6 'jquery',
6 'jquery',
7 'base/js/utils',
7 'base/js/utils',
8 'base/js/dialog',
8 'base/js/dialog',
9 'notebook/js/notificationwidget',
9 'notebook/js/notificationwidget',
10 ], function(IPython, $, utils, dialog, notificationwidget) {
10 ], function(IPython, $, utils, dialog, notificationwidget) {
11 "use strict";
11 "use strict";
12
12
13 var NotificationArea = function (selector, options) {
13 var NotificationArea = function (selector, options) {
14 // Constructor
14 // Constructor
15 //
15 //
16 // Parameters:
16 // Parameters:
17 // selector: string
17 // selector: string
18 // options: dictionary
18 // options: dictionary
19 // Dictionary of keyword arguments.
19 // Dictionary of keyword arguments.
20 // notebook: Notebook instance
20 // notebook: Notebook instance
21 // events: $(Events) instance
21 // events: $(Events) instance
22 // save_widget: SaveWidget instance
22 // save_widget: SaveWidget instance
23 this.selector = selector;
23 this.selector = selector;
24 this.events = options.events;
24 this.events = options.events;
25 this.save_widget = options.save_widget;
25 this.save_widget = options.save_widget;
26 this.notebook = options.notebook;
26 this.notebook = options.notebook;
27 this.keyboard_manager = options.keyboard_manager;
27 if (this.selector !== undefined) {
28 if (this.selector !== undefined) {
28 this.element = $(selector);
29 this.element = $(selector);
29 }
30 }
30 this.widget_dict = {};
31 this.widget_dict = {};
31 };
32 };
32
33
33 NotificationArea.prototype.temp_message = function (msg, timeout, css_class) {
34 NotificationArea.prototype.temp_message = function (msg, timeout, css_class) {
34 var uuid = utils.uuid();
35 var uuid = utils.uuid();
35 if( css_class == 'danger') {css_class = 'ui-state-error';}
36 if( css_class == 'danger') {css_class = 'ui-state-error';}
36 if( css_class == 'warning') {css_class = 'ui-state-highlight';}
37 if( css_class == 'warning') {css_class = 'ui-state-highlight';}
37 var tdiv = $('<div>')
38 var tdiv = $('<div>')
38 .attr('id',uuid)
39 .attr('id',uuid)
39 .addClass('notification_widget ui-widget ui-widget-content ui-corner-all')
40 .addClass('notification_widget ui-widget ui-widget-content ui-corner-all')
40 .addClass('border-box-sizing')
41 .addClass('border-box-sizing')
41 .addClass(css_class)
42 .addClass(css_class)
42 .hide()
43 .hide()
43 .text(msg);
44 .text(msg);
44
45
45 $(this.selector).append(tdiv);
46 $(this.selector).append(tdiv);
46 var tmout = Math.max(1500,(timeout||1500));
47 var tmout = Math.max(1500,(timeout||1500));
47 tdiv.fadeIn(100);
48 tdiv.fadeIn(100);
48
49
49 setTimeout(function () {
50 setTimeout(function () {
50 tdiv.fadeOut(100, function () {tdiv.remove();});
51 tdiv.fadeOut(100, function () {tdiv.remove();});
51 }, tmout);
52 }, tmout);
52 };
53 };
53
54
54 NotificationArea.prototype.widget = function(name) {
55 NotificationArea.prototype.widget = function(name) {
55 if(this.widget_dict[name] === undefined) {
56 if(this.widget_dict[name] === undefined) {
56 return this.new_notification_widget(name);
57 return this.new_notification_widget(name);
57 }
58 }
58 return this.get_widget(name);
59 return this.get_widget(name);
59 };
60 };
60
61
61 NotificationArea.prototype.get_widget = function(name) {
62 NotificationArea.prototype.get_widget = function(name) {
62 if(this.widget_dict[name] === undefined) {
63 if(this.widget_dict[name] === undefined) {
63 throw('no widgets with this name');
64 throw('no widgets with this name');
64 }
65 }
65 return this.widget_dict[name];
66 return this.widget_dict[name];
66 };
67 };
67
68
68 NotificationArea.prototype.new_notification_widget = function(name) {
69 NotificationArea.prototype.new_notification_widget = function(name) {
69 if(this.widget_dict[name] !== undefined) {
70 if(this.widget_dict[name] !== undefined) {
70 throw('widget with that name already exists ! ');
71 throw('widget with that name already exists ! ');
71 }
72 }
72 var div = $('<div/>').attr('id','notification_'+name);
73 var div = $('<div/>').attr('id','notification_'+name);
73 $(this.selector).append(div);
74 $(this.selector).append(div);
74 this.widget_dict[name] = new notificationwidget.NotificationWidget('#notification_'+name);
75 this.widget_dict[name] = new notificationwidget.NotificationWidget('#notification_'+name);
75 return this.widget_dict[name];
76 return this.widget_dict[name];
76 };
77 };
77
78
78 NotificationArea.prototype.init_notification_widgets = function() {
79 NotificationArea.prototype.init_notification_widgets = function() {
79 var that = this;
80 var that = this;
80 var knw = this.new_notification_widget('kernel');
81 var knw = this.new_notification_widget('kernel');
81 var $kernel_ind_icon = $("#kernel_indicator_icon");
82 var $kernel_ind_icon = $("#kernel_indicator_icon");
82 var $modal_ind_icon = $("#modal_indicator_icon");
83 var $modal_ind_icon = $("#modal_indicator_icon");
83
84
84 // Command/Edit mode
85 // Command/Edit mode
85 this.events.on('edit_mode.Notebook',function () {
86 this.events.on('edit_mode.Notebook',function () {
86 that.save_widget.update_document_title();
87 that.save_widget.update_document_title();
87 $modal_ind_icon.attr('class','edit_mode_icon').attr('title','Edit Mode');
88 $modal_ind_icon.attr('class','edit_mode_icon').attr('title','Edit Mode');
88 });
89 });
89
90
90 this.events.on('command_mode.Notebook',function () {
91 this.events.on('command_mode.Notebook',function () {
91 that.save_widget.update_document_title();
92 that.save_widget.update_document_title();
92 $modal_ind_icon.attr('class','command_mode_icon').attr('title','Command Mode');
93 $modal_ind_icon.attr('class','command_mode_icon').attr('title','Command Mode');
93 });
94 });
94
95
95 // Implicitly start off in Command mode, switching to Edit mode will trigger event
96 // Implicitly start off in Command mode, switching to Edit mode will trigger event
96 $modal_ind_icon.attr('class','command-mode_icon').attr('title','Command Mode');
97 $modal_ind_icon.attr('class','command-mode_icon').attr('title','Command Mode');
97
98
98 // Kernel events
99 // Kernel events
99 this.events.on('status_idle.Kernel',function () {
100 this.events.on('status_idle.Kernel',function () {
100 that.save_widget.update_document_title();
101 that.save_widget.update_document_title();
101 $kernel_ind_icon.attr('class','kernel_idle_icon').attr('title','Kernel Idle');
102 $kernel_ind_icon.attr('class','kernel_idle_icon').attr('title','Kernel Idle');
102 });
103 });
103
104
104 this.events.on('status_busy.Kernel',function () {
105 this.events.on('status_busy.Kernel',function () {
105 window.document.title='(Busy) '+window.document.title;
106 window.document.title='(Busy) '+window.document.title;
106 $kernel_ind_icon.attr('class','kernel_busy_icon').attr('title','Kernel Busy');
107 $kernel_ind_icon.attr('class','kernel_busy_icon').attr('title','Kernel Busy');
107 });
108 });
108
109
109 this.events.on('status_restarting.Kernel',function () {
110 this.events.on('status_restarting.Kernel',function () {
110 that.save_widget.update_document_title();
111 that.save_widget.update_document_title();
111 knw.set_message("Restarting kernel", 2000);
112 knw.set_message("Restarting kernel", 2000);
112 });
113 });
113
114
114 this.events.on('status_interrupting.Kernel',function () {
115 this.events.on('status_interrupting.Kernel',function () {
115 knw.set_message("Interrupting kernel", 2000);
116 knw.set_message("Interrupting kernel", 2000);
116 });
117 });
117
118
118 // Start the kernel indicator in the busy state, and send a kernel_info request.
119 // Start the kernel indicator in the busy state, and send a kernel_info request.
119 // When the kernel_info reply arrives, the kernel is idle.
120 // When the kernel_info reply arrives, the kernel is idle.
120 $kernel_ind_icon.attr('class','kernel_busy_icon').attr('title','Kernel Busy');
121 $kernel_ind_icon.attr('class','kernel_busy_icon').attr('title','Kernel Busy');
121
122
122 this.events.on('status_started.Kernel', function (evt, data) {
123 this.events.on('status_started.Kernel', function (evt, data) {
123 data.kernel.kernel_info(function () {
124 data.kernel.kernel_info(function () {
124 that.events.trigger('status_idle.Kernel');
125 that.events.trigger('status_idle.Kernel');
125 });
126 });
126 });
127 });
127
128
128 this.events.on('status_dead.Kernel',function () {
129 this.events.on('status_dead.Kernel',function () {
129 var msg = 'The kernel has died, and the automatic restart has failed.' +
130 var msg = 'The kernel has died, and the automatic restart has failed.' +
130 ' It is possible the kernel cannot be restarted.' +
131 ' It is possible the kernel cannot be restarted.' +
131 ' If you are not able to restart the kernel, you will still be able to save' +
132 ' If you are not able to restart the kernel, you will still be able to save' +
132 ' the notebook, but running code will no longer work until the notebook' +
133 ' the notebook, but running code will no longer work until the notebook' +
133 ' is reopened.';
134 ' is reopened.';
134
135
135 dialog.modal({
136 dialog.modal({
136 title: "Dead kernel",
137 title: "Dead kernel",
137 body : msg,
138 body : msg,
139 keyboard_manager: that.keyboard_manager,
140 notebook: that.notebook,
138 buttons : {
141 buttons : {
139 "Manual Restart": {
142 "Manual Restart": {
140 class: "btn-danger",
143 class: "btn-danger",
141 click: function () {
144 click: function () {
142 that.events.trigger('status_restarting.Kernel');
145 that.events.trigger('status_restarting.Kernel');
143 that.notebook.start_kernel();
146 that.notebook.start_kernel();
144 }
147 }
145 },
148 },
146 "Don't restart": {}
149 "Don't restart": {}
147 }
150 }
148 });
151 });
149 });
152 });
150
153
151 this.events.on('websocket_closed.Kernel', function (event, data) {
154 this.events.on('websocket_closed.Kernel', function (event, data) {
152 var kernel = data.kernel;
155 var kernel = data.kernel;
153 var ws_url = data.ws_url;
156 var ws_url = data.ws_url;
154 var early = data.early;
157 var early = data.early;
155 var msg;
158 var msg;
156 if (!early) {
159 if (!early) {
157 knw.set_message('Reconnecting WebSockets', 1000);
160 knw.set_message('Reconnecting WebSockets', 1000);
158 setTimeout(function () {
161 setTimeout(function () {
159 kernel.start_channels();
162 kernel.start_channels();
160 }, 5000);
163 }, 5000);
161 return;
164 return;
162 }
165 }
163 console.log('WebSocket connection failed: ', ws_url);
166 console.log('WebSocket connection failed: ', ws_url);
164 msg = "A WebSocket connection could not be established." +
167 msg = "A WebSocket connection could not be established." +
165 " You will NOT be able to run code. Check your" +
168 " You will NOT be able to run code. Check your" +
166 " network connection or notebook server configuration.";
169 " network connection or notebook server configuration.";
167 dialog.modal({
170 dialog.modal({
168 title: "WebSocket connection failed",
171 title: "WebSocket connection failed",
169 body: msg,
172 body: msg,
173 keyboard_manager: that.keyboard_manager,
174 notebook: that.notebook,
170 buttons : {
175 buttons : {
171 "OK": {},
176 "OK": {},
172 "Reconnect": {
177 "Reconnect": {
173 click: function () {
178 click: function () {
174 knw.set_message('Reconnecting WebSockets', 1000);
179 knw.set_message('Reconnecting WebSockets', 1000);
175 setTimeout(function () {
180 setTimeout(function () {
176 kernel.start_channels();
181 kernel.start_channels();
177 }, 5000);
182 }, 5000);
178 }
183 }
179 }
184 }
180 }
185 }
181 });
186 });
182 });
187 });
183
188
184
189
185 var nnw = this.new_notification_widget('notebook');
190 var nnw = this.new_notification_widget('notebook');
186
191
187 // Notebook events
192 // Notebook events
188 this.events.on('notebook_loading.Notebook', function () {
193 this.events.on('notebook_loading.Notebook', function () {
189 nnw.set_message("Loading notebook",500);
194 nnw.set_message("Loading notebook",500);
190 });
195 });
191 this.events.on('notebook_loaded.Notebook', function () {
196 this.events.on('notebook_loaded.Notebook', function () {
192 nnw.set_message("Notebook loaded",500);
197 nnw.set_message("Notebook loaded",500);
193 });
198 });
194 this.events.on('notebook_saving.Notebook', function () {
199 this.events.on('notebook_saving.Notebook', function () {
195 nnw.set_message("Saving notebook",500);
200 nnw.set_message("Saving notebook",500);
196 });
201 });
197 this.events.on('notebook_saved.Notebook', function () {
202 this.events.on('notebook_saved.Notebook', function () {
198 nnw.set_message("Notebook saved",2000);
203 nnw.set_message("Notebook saved",2000);
199 });
204 });
200 this.events.on('notebook_save_failed.Notebook', function (evt, xhr, status, data) {
205 this.events.on('notebook_save_failed.Notebook', function (evt, xhr, status, data) {
201 nnw.set_message(data || "Notebook save failed");
206 nnw.set_message(data || "Notebook save failed");
202 });
207 });
203
208
204 // Checkpoint events
209 // Checkpoint events
205 this.events.on('checkpoint_created.Notebook', function (evt, data) {
210 this.events.on('checkpoint_created.Notebook', function (evt, data) {
206 var msg = "Checkpoint created";
211 var msg = "Checkpoint created";
207 if (data.last_modified) {
212 if (data.last_modified) {
208 var d = new Date(data.last_modified);
213 var d = new Date(data.last_modified);
209 msg = msg + ": " + d.format("HH:MM:ss");
214 msg = msg + ": " + d.format("HH:MM:ss");
210 }
215 }
211 nnw.set_message(msg, 2000);
216 nnw.set_message(msg, 2000);
212 });
217 });
213 this.events.on('checkpoint_failed.Notebook', function () {
218 this.events.on('checkpoint_failed.Notebook', function () {
214 nnw.set_message("Checkpoint failed");
219 nnw.set_message("Checkpoint failed");
215 });
220 });
216 this.events.on('checkpoint_deleted.Notebook', function () {
221 this.events.on('checkpoint_deleted.Notebook', function () {
217 nnw.set_message("Checkpoint deleted", 500);
222 nnw.set_message("Checkpoint deleted", 500);
218 });
223 });
219 this.events.on('checkpoint_delete_failed.Notebook', function () {
224 this.events.on('checkpoint_delete_failed.Notebook', function () {
220 nnw.set_message("Checkpoint delete failed");
225 nnw.set_message("Checkpoint delete failed");
221 });
226 });
222 this.events.on('checkpoint_restoring.Notebook', function () {
227 this.events.on('checkpoint_restoring.Notebook', function () {
223 nnw.set_message("Restoring to checkpoint...", 500);
228 nnw.set_message("Restoring to checkpoint...", 500);
224 });
229 });
225 this.events.on('checkpoint_restore_failed.Notebook', function () {
230 this.events.on('checkpoint_restore_failed.Notebook', function () {
226 nnw.set_message("Checkpoint restore failed");
231 nnw.set_message("Checkpoint restore failed");
227 });
232 });
228
233
229 // Autosave events
234 // Autosave events
230 this.events.on('autosave_disabled.Notebook', function () {
235 this.events.on('autosave_disabled.Notebook', function () {
231 nnw.set_message("Autosave disabled", 2000);
236 nnw.set_message("Autosave disabled", 2000);
232 });
237 });
233 this.events.on('autosave_enabled.Notebook', function (evt, interval) {
238 this.events.on('autosave_enabled.Notebook', function (evt, interval) {
234 nnw.set_message("Saving every " + interval / 1000 + "s", 1000);
239 nnw.set_message("Saving every " + interval / 1000 + "s", 1000);
235 });
240 });
236
241
237 };
242 };
238
243
239 IPython.NotificationArea = NotificationArea;
244 IPython.NotificationArea = NotificationArea;
240
245
241 return {'NotificationArea': NotificationArea};
246 return {'NotificationArea': NotificationArea};
242 });
247 });
@@ -1,180 +1,184
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 // Constructor
15 //
15 //
16 // Parameters:
16 // Parameters:
17 // options: dictionary
17 // options: dictionary
18 // Dictionary of keyword arguments.
18 // Dictionary of keyword arguments.
19 // events: $(Events) instance
19 // events: $(Events) instance
20 // keyboard_manager: KeyboardManager instance
20 // keyboard_manager: KeyboardManager instance
21 // notebook: Notebook instance
21 this.keyboard_manager = options.keyboard_manager;
22 this.keyboard_manager = options.keyboard_manager;
23 this.notebook = options.notebook;
22 this.keyboard_manager.quick_help = this;
24 this.keyboard_manager.quick_help = this;
23 this.events = options.events;
25 this.events = options.events;
24 };
26 };
25
27
26 var cmd_ctrl = 'Ctrl-';
28 var cmd_ctrl = 'Ctrl-';
27 var platform_specific;
29 var platform_specific;
28
30
29 if (platform === 'MacOS') {
31 if (platform === 'MacOS') {
30 // Mac OS X specific
32 // Mac OS X specific
31 cmd_ctrl = 'Cmd-';
33 cmd_ctrl = 'Cmd-';
32 platform_specific = [
34 platform_specific = [
33 { shortcut: "Cmd-Up", help:"go to cell start" },
35 { shortcut: "Cmd-Up", help:"go to cell start" },
34 { shortcut: "Cmd-Down", help:"go to cell end" },
36 { shortcut: "Cmd-Down", help:"go to cell end" },
35 { shortcut: "Opt-Left", help:"go one word left" },
37 { shortcut: "Opt-Left", help:"go one word left" },
36 { shortcut: "Opt-Right", help:"go one word right" },
38 { shortcut: "Opt-Right", help:"go one word right" },
37 { shortcut: "Opt-Backspace", help:"del word before" },
39 { shortcut: "Opt-Backspace", help:"del word before" },
38 { shortcut: "Opt-Delete", help:"del word after" },
40 { shortcut: "Opt-Delete", help:"del word after" },
39 ];
41 ];
40 } else {
42 } else {
41 // PC specific
43 // PC specific
42 platform_specific = [
44 platform_specific = [
43 { shortcut: "Ctrl-Home", help:"go to cell start" },
45 { shortcut: "Ctrl-Home", help:"go to cell start" },
44 { shortcut: "Ctrl-Up", help:"go to cell start" },
46 { shortcut: "Ctrl-Up", help:"go to cell start" },
45 { shortcut: "Ctrl-End", help:"go to cell end" },
47 { shortcut: "Ctrl-End", help:"go to cell end" },
46 { shortcut: "Ctrl-Down", help:"go to cell end" },
48 { shortcut: "Ctrl-Down", help:"go to cell end" },
47 { shortcut: "Ctrl-Left", help:"go one word left" },
49 { shortcut: "Ctrl-Left", help:"go one word left" },
48 { shortcut: "Ctrl-Right", help:"go one word right" },
50 { shortcut: "Ctrl-Right", help:"go one word right" },
49 { shortcut: "Ctrl-Backspace", help:"del word before" },
51 { shortcut: "Ctrl-Backspace", help:"del word before" },
50 { shortcut: "Ctrl-Delete", help:"del word after" },
52 { shortcut: "Ctrl-Delete", help:"del word after" },
51 ];
53 ];
52 }
54 }
53
55
54 var cm_shortcuts = [
56 var cm_shortcuts = [
55 { shortcut:"Tab", help:"code completion or indent" },
57 { shortcut:"Tab", help:"code completion or indent" },
56 { shortcut:"Shift-Tab", help:"tooltip" },
58 { shortcut:"Shift-Tab", help:"tooltip" },
57 { shortcut: cmd_ctrl + "]", help:"indent" },
59 { shortcut: cmd_ctrl + "]", help:"indent" },
58 { shortcut: cmd_ctrl + "[", help:"dedent" },
60 { shortcut: cmd_ctrl + "[", help:"dedent" },
59 { shortcut: cmd_ctrl + "a", help:"select all" },
61 { shortcut: cmd_ctrl + "a", help:"select all" },
60 { shortcut: cmd_ctrl + "z", help:"undo" },
62 { shortcut: cmd_ctrl + "z", help:"undo" },
61 { shortcut: cmd_ctrl + "Shift-z", help:"redo" },
63 { shortcut: cmd_ctrl + "Shift-z", help:"redo" },
62 { shortcut: cmd_ctrl + "y", help:"redo" },
64 { shortcut: cmd_ctrl + "y", help:"redo" },
63 ].concat( platform_specific );
65 ].concat( platform_specific );
64
66
65
67
66
68
67
69
68
70
69
71
70 QuickHelp.prototype.show_keyboard_shortcuts = function () {
72 QuickHelp.prototype.show_keyboard_shortcuts = function () {
71 // toggles display of keyboard shortcut dialog
73 // toggles display of keyboard shortcut dialog
72 var that = this;
74 var that = this;
73 if ( this.force_rebuild ) {
75 if ( this.force_rebuild ) {
74 this.shortcut_dialog.remove();
76 this.shortcut_dialog.remove();
75 delete(this.shortcut_dialog);
77 delete(this.shortcut_dialog);
76 this.force_rebuild = false;
78 this.force_rebuild = false;
77 }
79 }
78 if ( this.shortcut_dialog ){
80 if ( this.shortcut_dialog ){
79 // if dialog is already shown, close it
81 // if dialog is already shown, close it
80 $(this.shortcut_dialog).modal("toggle");
82 $(this.shortcut_dialog).modal("toggle");
81 return;
83 return;
82 }
84 }
83 var command_shortcuts = this.keyboard_manager.command_shortcuts.help();
85 var command_shortcuts = this.keyboard_manager.command_shortcuts.help();
84 var edit_shortcuts = this.keyboard_manager.edit_shortcuts.help();
86 var edit_shortcuts = this.keyboard_manager.edit_shortcuts.help();
85 var help, shortcut;
87 var help, shortcut;
86 var i, half, n;
88 var i, half, n;
87 var element = $('<div/>');
89 var element = $('<div/>');
88
90
89 // The documentation
91 // The documentation
90 var doc = $('<div/>').addClass('alert alert-warning');
92 var doc = $('<div/>').addClass('alert alert-warning');
91 doc.append(
93 doc.append(
92 $('<button/>').addClass('close').attr('data-dismiss','alert').html('&times;')
94 $('<button/>').addClass('close').attr('data-dismiss','alert').html('&times;')
93 ).append(
95 ).append(
94 '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> '+
95 '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 '+
96 '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 '+
97 'and is indicated by a grey cell border.'
99 'and is indicated by a grey cell border.'
98 );
100 );
99 element.append(doc);
101 element.append(doc);
100
102
101 // Command mode
103 // Command mode
102 var cmd_div = this.build_command_help();
104 var cmd_div = this.build_command_help();
103 element.append(cmd_div);
105 element.append(cmd_div);
104
106
105 // Edit mode
107 // Edit mode
106 var edit_div = this.build_edit_help(cm_shortcuts);
108 var edit_div = this.build_edit_help(cm_shortcuts);
107 element.append(edit_div);
109 element.append(edit_div);
108
110
109 this.shortcut_dialog = dialog.modal({
111 this.shortcut_dialog = dialog.modal({
110 title : "Keyboard shortcuts",
112 title : "Keyboard shortcuts",
111 body : element,
113 body : element,
112 destroy : false,
114 destroy : false,
113 buttons : {
115 buttons : {
114 Close : {}
116 Close : {}
115 }
117 },
118 notebook: this.notebook,
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 keys[i] = ( special_case[k] ? special_case[k] : k.charAt(0).toUpperCase() + k.slice(1) );
142 keys[i] = ( special_case[k] ? special_case[k] : k.charAt(0).toUpperCase() + k.slice(1) );
139 keys[i] = "<code><strong>" + keys[i] + "</strong></code>";
143 keys[i] = "<code><strong>" + keys[i] + "</strong></code>";
140 }
144 }
141 return keys.join('-');
145 return keys.join('-');
142
146
143
147
144 };
148 };
145
149
146 QuickHelp.prototype.build_edit_help = function (cm_shortcuts) {
150 QuickHelp.prototype.build_edit_help = function (cm_shortcuts) {
147 var edit_shortcuts = this.keyboard_manager.edit_shortcuts.help();
151 var edit_shortcuts = this.keyboard_manager.edit_shortcuts.help();
148 jQuery.merge(cm_shortcuts, edit_shortcuts);
152 jQuery.merge(cm_shortcuts, edit_shortcuts);
149 return build_div('<h4>Edit Mode (press <code>Enter</code> to enable)</h4>', cm_shortcuts);
153 return build_div('<h4>Edit Mode (press <code>Enter</code> to enable)</h4>', cm_shortcuts);
150 };
154 };
151
155
152 var build_one = function (s) {
156 var build_one = function (s) {
153 var help = s.help;
157 var help = s.help;
154 var shortcut = prettify(s.shortcut);
158 var shortcut = prettify(s.shortcut);
155 return $('<div>').addClass('quickhelp').
159 return $('<div>').addClass('quickhelp').
156 append($('<span/>').addClass('shortcut_key').append($(shortcut))).
160 append($('<span/>').addClass('shortcut_key').append($(shortcut))).
157 append($('<span/>').addClass('shortcut_descr').text(' : ' + help));
161 append($('<span/>').addClass('shortcut_descr').text(' : ' + help));
158
162
159 };
163 };
160
164
161 var build_div = function (title, shortcuts) {
165 var build_div = function (title, shortcuts) {
162 var i, half, n;
166 var i, half, n;
163 var div = $('<div/>').append($(title));
167 var div = $('<div/>').append($(title));
164 var sub_div = $('<div/>').addClass('hbox');
168 var sub_div = $('<div/>').addClass('hbox');
165 var col1 = $('<div/>').addClass('box-flex1');
169 var col1 = $('<div/>').addClass('box-flex1');
166 var col2 = $('<div/>').addClass('box-flex1');
170 var col2 = $('<div/>').addClass('box-flex1');
167 n = shortcuts.length;
171 n = shortcuts.length;
168 half = ~~(n/2); // Truncate :)
172 half = ~~(n/2); // Truncate :)
169 for (i=0; i<half; i++) { col1.append( build_one(shortcuts[i]) ); }
173 for (i=0; i<half; i++) { col1.append( build_one(shortcuts[i]) ); }
170 for (i=half; i<n; i++) { col2.append( build_one(shortcuts[i]) ); }
174 for (i=half; i<n; i++) { col2.append( build_one(shortcuts[i]) ); }
171 sub_div.append(col1).append(col2);
175 sub_div.append(col1).append(col2);
172 div.append(sub_div);
176 div.append(sub_div);
173 return div;
177 return div;
174 };
178 };
175
179
176 // Backwards compatability.
180 // Backwards compatability.
177 IPython.QuickHelp = QuickHelp;
181 IPython.QuickHelp = QuickHelp;
178
182
179 return {'QuickHelp': QuickHelp};
183 return {'QuickHelp': QuickHelp};
180 });
184 });
@@ -1,461 +1,461
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 define([
4 define([
5 'base/js/namespace',
5 'base/js/namespace',
6 'jquery',
6 'jquery',
7 'notebook/js/cell',
7 'notebook/js/cell',
8 'base/js/security',
8 'base/js/security',
9 'notebook/js/mathjaxutils',
9 'notebook/js/mathjaxutils',
10 'notebook/js/celltoolbar',
10 'notebook/js/celltoolbar',
11 'components/marked/lib/marked',
11 'components/marked/lib/marked',
12 ], function(IPython, $, cell, security, mathjaxutils, celltoolbar, marked) {
12 ], function(IPython, $, cell, security, mathjaxutils, celltoolbar, marked) {
13 "use strict";
13 "use strict";
14 var Cell = cell.Cell;
14 var Cell = cell.Cell;
15
15
16 var TextCell = function (options) {
16 var TextCell = function (options) {
17 // Constructor
17 // Constructor
18 //
18 //
19 // Construct a new TextCell, codemirror mode is by default 'htmlmixed',
19 // Construct a new TextCell, codemirror mode is by default 'htmlmixed',
20 // and cell type is 'text' cell start as not redered.
20 // and cell type is 'text' cell start as not redered.
21 //
21 //
22 // Parameters:
22 // Parameters:
23 // options: dictionary
23 // options: dictionary
24 // Dictionary of keyword arguments.
24 // Dictionary of keyword arguments.
25 // events: $(Events) instance
25 // events: $(Events) instance
26 // config: dictionary
26 // config: dictionary
27 // keyboard_manager: KeyboardManager instance
27 // keyboard_manager: KeyboardManager instance
28 // notebook: Notebook instance
28 // notebook: Notebook instance
29 options = options || {};
29 options = options || {};
30
30
31 // in all TextCell/Cell subclasses
31 // in all TextCell/Cell subclasses
32 // do not assign most of members here, just pass it down
32 // do not assign most of members here, just pass it down
33 // in the options dict potentially overwriting what you wish.
33 // in the options dict potentially overwriting what you wish.
34 // they will be assigned in the base class.
34 // they will be assigned in the base class.
35 this.notebook = options.notebook;
35 this.notebook = options.notebook;
36 this.events = options.events;
36 this.events = options.events;
37 this.config = options.config;
37 this.config = options.config;
38
38
39 // we cannot put this as a class key as it has handle to "this".
39 // we cannot put this as a class key as it has handle to "this".
40 var cm_overwrite_options = {
40 var cm_overwrite_options = {
41 onKeyEvent: $.proxy(this.handle_keyevent,this)
41 onKeyEvent: $.proxy(this.handle_keyevent,this)
42 };
42 };
43 var config = this.mergeopt(TextCell, this.config, {cm_config:cm_overwrite_options});
43 var config = this.mergeopt(TextCell, this.config, {cm_config:cm_overwrite_options});
44 Cell.apply(this, [{
44 Cell.apply(this, [{
45 config: config,
45 config: config,
46 keyboard_manager: options.keyboard_manager,
46 keyboard_manager: options.keyboard_manager,
47 events: events}]);
47 events: this.events}]);
48
48
49 this.cell_type = this.cell_type || 'text';
49 this.cell_type = this.cell_type || 'text';
50 mathjaxutils = mathjaxutils;
50 mathjaxutils = mathjaxutils;
51 this.rendered = false;
51 this.rendered = false;
52 };
52 };
53
53
54 TextCell.prototype = new Cell();
54 TextCell.prototype = new Cell();
55
55
56 TextCell.options_default = {
56 TextCell.options_default = {
57 cm_config : {
57 cm_config : {
58 extraKeys: {"Tab": "indentMore","Shift-Tab" : "indentLess"},
58 extraKeys: {"Tab": "indentMore","Shift-Tab" : "indentLess"},
59 mode: 'htmlmixed',
59 mode: 'htmlmixed',
60 lineWrapping : true,
60 lineWrapping : true,
61 }
61 }
62 };
62 };
63
63
64
64
65 /**
65 /**
66 * Create the DOM element of the TextCell
66 * Create the DOM element of the TextCell
67 * @method create_element
67 * @method create_element
68 * @private
68 * @private
69 */
69 */
70 TextCell.prototype.create_element = function () {
70 TextCell.prototype.create_element = function () {
71 Cell.prototype.create_element.apply(this, arguments);
71 Cell.prototype.create_element.apply(this, arguments);
72
72
73 var cell = $("<div>").addClass('cell text_cell border-box-sizing');
73 var cell = $("<div>").addClass('cell text_cell border-box-sizing');
74 cell.attr('tabindex','2');
74 cell.attr('tabindex','2');
75
75
76 var prompt = $('<div/>').addClass('prompt input_prompt');
76 var prompt = $('<div/>').addClass('prompt input_prompt');
77 cell.append(prompt);
77 cell.append(prompt);
78 var inner_cell = $('<div/>').addClass('inner_cell');
78 var inner_cell = $('<div/>').addClass('inner_cell');
79 this.celltoolbar = new celltoolbar.CellToolbar(this, this.events, this.notebook);
79 this.celltoolbar = new celltoolbar.CellToolbar(this, this.events, this.notebook);
80 inner_cell.append(this.celltoolbar.element);
80 inner_cell.append(this.celltoolbar.element);
81 var input_area = $('<div/>').addClass('input_area');
81 var input_area = $('<div/>').addClass('input_area');
82 this.code_mirror = new CodeMirror(input_area.get(0), this.cm_config);
82 this.code_mirror = new CodeMirror(input_area.get(0), this.cm_config);
83 // The tabindex=-1 makes this div focusable.
83 // The tabindex=-1 makes this div focusable.
84 var render_area = $('<div/>').addClass('text_cell_render border-box-sizing').
84 var render_area = $('<div/>').addClass('text_cell_render border-box-sizing').
85 addClass('rendered_html').attr('tabindex','-1');
85 addClass('rendered_html').attr('tabindex','-1');
86 inner_cell.append(input_area).append(render_area);
86 inner_cell.append(input_area).append(render_area);
87 cell.append(inner_cell);
87 cell.append(inner_cell);
88 this.element = cell;
88 this.element = cell;
89 };
89 };
90
90
91
91
92 /**
92 /**
93 * Bind the DOM evet to cell actions
93 * Bind the DOM evet to cell actions
94 * Need to be called after TextCell.create_element
94 * Need to be called after TextCell.create_element
95 * @private
95 * @private
96 * @method bind_event
96 * @method bind_event
97 */
97 */
98 TextCell.prototype.bind_events = function () {
98 TextCell.prototype.bind_events = function () {
99 Cell.prototype.bind_events.apply(this);
99 Cell.prototype.bind_events.apply(this);
100 var that = this;
100 var that = this;
101
101
102 this.element.dblclick(function () {
102 this.element.dblclick(function () {
103 if (that.selected === false) {
103 if (that.selected === false) {
104 this.events.trigger('select.Cell', {'cell':that});
104 this.events.trigger('select.Cell', {'cell':that});
105 }
105 }
106 var cont = that.unrender();
106 var cont = that.unrender();
107 if (cont) {
107 if (cont) {
108 that.focus_editor();
108 that.focus_editor();
109 }
109 }
110 });
110 });
111 };
111 };
112
112
113 // Cell level actions
113 // Cell level actions
114
114
115 TextCell.prototype.select = function () {
115 TextCell.prototype.select = function () {
116 var cont = Cell.prototype.select.apply(this);
116 var cont = Cell.prototype.select.apply(this);
117 if (cont) {
117 if (cont) {
118 if (this.mode === 'edit') {
118 if (this.mode === 'edit') {
119 this.code_mirror.refresh();
119 this.code_mirror.refresh();
120 }
120 }
121 }
121 }
122 return cont;
122 return cont;
123 };
123 };
124
124
125 TextCell.prototype.unrender = function () {
125 TextCell.prototype.unrender = function () {
126 if (this.read_only) return;
126 if (this.read_only) return;
127 var cont = Cell.prototype.unrender.apply(this);
127 var cont = Cell.prototype.unrender.apply(this);
128 if (cont) {
128 if (cont) {
129 var text_cell = this.element;
129 var text_cell = this.element;
130 var output = text_cell.find("div.text_cell_render");
130 var output = text_cell.find("div.text_cell_render");
131 output.hide();
131 output.hide();
132 text_cell.find('div.input_area').show();
132 text_cell.find('div.input_area').show();
133 if (this.get_text() === this.placeholder) {
133 if (this.get_text() === this.placeholder) {
134 this.set_text('');
134 this.set_text('');
135 }
135 }
136 this.refresh();
136 this.refresh();
137 }
137 }
138 if (this.celltoolbar.ui_controls_list.length) {
138 if (this.celltoolbar.ui_controls_list.length) {
139 this.celltoolbar.show();
139 this.celltoolbar.show();
140 }
140 }
141 return cont;
141 return cont;
142 };
142 };
143
143
144 TextCell.prototype.execute = function () {
144 TextCell.prototype.execute = function () {
145 this.render();
145 this.render();
146 };
146 };
147
147
148 /**
148 /**
149 * setter: {{#crossLink "TextCell/set_text"}}{{/crossLink}}
149 * setter: {{#crossLink "TextCell/set_text"}}{{/crossLink}}
150 * @method get_text
150 * @method get_text
151 * @retrun {string} CodeMirror current text value
151 * @retrun {string} CodeMirror current text value
152 */
152 */
153 TextCell.prototype.get_text = function() {
153 TextCell.prototype.get_text = function() {
154 return this.code_mirror.getValue();
154 return this.code_mirror.getValue();
155 };
155 };
156
156
157 /**
157 /**
158 * @param {string} text - Codemiror text value
158 * @param {string} text - Codemiror text value
159 * @see TextCell#get_text
159 * @see TextCell#get_text
160 * @method set_text
160 * @method set_text
161 * */
161 * */
162 TextCell.prototype.set_text = function(text) {
162 TextCell.prototype.set_text = function(text) {
163 this.code_mirror.setValue(text);
163 this.code_mirror.setValue(text);
164 this.code_mirror.refresh();
164 this.code_mirror.refresh();
165 };
165 };
166
166
167 /**
167 /**
168 * setter :{{#crossLink "TextCell/set_rendered"}}{{/crossLink}}
168 * setter :{{#crossLink "TextCell/set_rendered"}}{{/crossLink}}
169 * @method get_rendered
169 * @method get_rendered
170 * */
170 * */
171 TextCell.prototype.get_rendered = function() {
171 TextCell.prototype.get_rendered = function() {
172 return this.element.find('div.text_cell_render').html();
172 return this.element.find('div.text_cell_render').html();
173 };
173 };
174
174
175 /**
175 /**
176 * @method set_rendered
176 * @method set_rendered
177 */
177 */
178 TextCell.prototype.set_rendered = function(text) {
178 TextCell.prototype.set_rendered = function(text) {
179 this.element.find('div.text_cell_render').html(text);
179 this.element.find('div.text_cell_render').html(text);
180 this.celltoolbar.hide();
180 this.celltoolbar.hide();
181 };
181 };
182
182
183
183
184 /**
184 /**
185 * Create Text cell from JSON
185 * Create Text cell from JSON
186 * @param {json} data - JSON serialized text-cell
186 * @param {json} data - JSON serialized text-cell
187 * @method fromJSON
187 * @method fromJSON
188 */
188 */
189 TextCell.prototype.fromJSON = function (data) {
189 TextCell.prototype.fromJSON = function (data) {
190 Cell.prototype.fromJSON.apply(this, arguments);
190 Cell.prototype.fromJSON.apply(this, arguments);
191 if (data.cell_type === this.cell_type) {
191 if (data.cell_type === this.cell_type) {
192 if (data.source !== undefined) {
192 if (data.source !== undefined) {
193 this.set_text(data.source);
193 this.set_text(data.source);
194 // make this value the starting point, so that we can only undo
194 // make this value the starting point, so that we can only undo
195 // to this state, instead of a blank cell
195 // to this state, instead of a blank cell
196 this.code_mirror.clearHistory();
196 this.code_mirror.clearHistory();
197 // TODO: This HTML needs to be treated as potentially dangerous
197 // TODO: This HTML needs to be treated as potentially dangerous
198 // user input and should be handled before set_rendered.
198 // user input and should be handled before set_rendered.
199 this.set_rendered(data.rendered || '');
199 this.set_rendered(data.rendered || '');
200 this.rendered = false;
200 this.rendered = false;
201 this.render();
201 this.render();
202 }
202 }
203 }
203 }
204 };
204 };
205
205
206 /** Generate JSON from cell
206 /** Generate JSON from cell
207 * @return {object} cell data serialised to json
207 * @return {object} cell data serialised to json
208 */
208 */
209 TextCell.prototype.toJSON = function () {
209 TextCell.prototype.toJSON = function () {
210 var data = Cell.prototype.toJSON.apply(this);
210 var data = Cell.prototype.toJSON.apply(this);
211 data.source = this.get_text();
211 data.source = this.get_text();
212 if (data.source == this.placeholder) {
212 if (data.source == this.placeholder) {
213 data.source = "";
213 data.source = "";
214 }
214 }
215 return data;
215 return data;
216 };
216 };
217
217
218
218
219 var MarkdownCell = function (options) {
219 var MarkdownCell = function (options) {
220 // Constructor
220 // Constructor
221 //
221 //
222 // Parameters:
222 // Parameters:
223 // options: dictionary
223 // options: dictionary
224 // Dictionary of keyword arguments.
224 // Dictionary of keyword arguments.
225 // events: $(Events) instance
225 // events: $(Events) instance
226 // config: dictionary
226 // config: dictionary
227 // keyboard_manager: KeyboardManager instance
227 // keyboard_manager: KeyboardManager instance
228 // notebook: Notebook instance
228 // notebook: Notebook instance
229 options = options || {};
229 options = options || {};
230 var config = this.mergeopt(MarkdownCell, options.config);
230 var config = this.mergeopt(MarkdownCell, options.config);
231 TextCell.apply(this, [$.extend({}, options, {config: config})]);
231 TextCell.apply(this, [$.extend({}, options, {config: config})]);
232
232
233 this.cell_type = 'markdown';
233 this.cell_type = 'markdown';
234 };
234 };
235
235
236 MarkdownCell.options_default = {
236 MarkdownCell.options_default = {
237 cm_config: {
237 cm_config: {
238 mode: 'ipythongfm'
238 mode: 'ipythongfm'
239 },
239 },
240 placeholder: "Type *Markdown* and LaTeX: $\\alpha^2$"
240 placeholder: "Type *Markdown* and LaTeX: $\\alpha^2$"
241 };
241 };
242
242
243 MarkdownCell.prototype = new TextCell();
243 MarkdownCell.prototype = new TextCell();
244
244
245 /**
245 /**
246 * @method render
246 * @method render
247 */
247 */
248 MarkdownCell.prototype.render = function () {
248 MarkdownCell.prototype.render = function () {
249 var cont = TextCell.prototype.render.apply(this);
249 var cont = TextCell.prototype.render.apply(this);
250 if (cont) {
250 if (cont) {
251 var text = this.get_text();
251 var text = this.get_text();
252 var math = null;
252 var math = null;
253 if (text === "") { text = this.placeholder; }
253 if (text === "") { text = this.placeholder; }
254 var text_and_math = mathjaxutils.remove_math(text);
254 var text_and_math = mathjaxutils.remove_math(text);
255 text = text_and_math[0];
255 text = text_and_math[0];
256 math = text_and_math[1];
256 math = text_and_math[1];
257 var html = marked.parser(marked.lexer(text));
257 var html = marked.parser(marked.lexer(text));
258 html = mathjaxutils.replace_math(html, math);
258 html = mathjaxutils.replace_math(html, math);
259 html = security.sanitize_html(html);
259 html = security.sanitize_html(html);
260 html = $($.parseHTML(html));
260 html = $($.parseHTML(html));
261 // links in markdown cells should open in new tabs
261 // links in markdown cells should open in new tabs
262 html.find("a[href]").not('[href^="#"]').attr("target", "_blank");
262 html.find("a[href]").not('[href^="#"]').attr("target", "_blank");
263 this.set_rendered(html);
263 this.set_rendered(html);
264 this.element.find('div.input_area').hide();
264 this.element.find('div.input_area').hide();
265 this.element.find("div.text_cell_render").show();
265 this.element.find("div.text_cell_render").show();
266 this.typeset();
266 this.typeset();
267 }
267 }
268 return cont;
268 return cont;
269 };
269 };
270
270
271
271
272 var RawCell = function (options) {
272 var RawCell = function (options) {
273 // Constructor
273 // Constructor
274 //
274 //
275 // Parameters:
275 // Parameters:
276 // options: dictionary
276 // options: dictionary
277 // Dictionary of keyword arguments.
277 // Dictionary of keyword arguments.
278 // events: $(Events) instance
278 // events: $(Events) instance
279 // config: dictionary
279 // config: dictionary
280 // keyboard_manager: KeyboardManager instance
280 // keyboard_manager: KeyboardManager instance
281 // notebook: Notebook instance
281 // notebook: Notebook instance
282 options = options || {};
282 options = options || {};
283 var config = this.mergeopt(RawCell, options.config);
283 var config = this.mergeopt(RawCell, options.config);
284 TextCell.apply(this, [$.extend({}, options, {config: config})]);
284 TextCell.apply(this, [$.extend({}, options, {config: config})]);
285
285
286 // RawCell should always hide its rendered div
286 // RawCell should always hide its rendered div
287 this.element.find('div.text_cell_render').hide();
287 this.element.find('div.text_cell_render').hide();
288 this.cell_type = 'raw';
288 this.cell_type = 'raw';
289 };
289 };
290
290
291 RawCell.options_default = {
291 RawCell.options_default = {
292 placeholder : "Write raw LaTeX or other formats here, for use with nbconvert. " +
292 placeholder : "Write raw LaTeX or other formats here, for use with nbconvert. " +
293 "It will not be rendered in the notebook. " +
293 "It will not be rendered in the notebook. " +
294 "When passing through nbconvert, a Raw Cell's content is added to the output unmodified."
294 "When passing through nbconvert, a Raw Cell's content is added to the output unmodified."
295 };
295 };
296
296
297 RawCell.prototype = new TextCell();
297 RawCell.prototype = new TextCell();
298
298
299 /** @method bind_events **/
299 /** @method bind_events **/
300 RawCell.prototype.bind_events = function () {
300 RawCell.prototype.bind_events = function () {
301 TextCell.prototype.bind_events.apply(this);
301 TextCell.prototype.bind_events.apply(this);
302 var that = this;
302 var that = this;
303 this.element.focusout(function() {
303 this.element.focusout(function() {
304 that.auto_highlight();
304 that.auto_highlight();
305 that.render();
305 that.render();
306 });
306 });
307
307
308 this.code_mirror.on('focus', function() { that.unrender(); });
308 this.code_mirror.on('focus', function() { that.unrender(); });
309 };
309 };
310
310
311 /**
311 /**
312 * Trigger autodetection of highlight scheme for current cell
312 * Trigger autodetection of highlight scheme for current cell
313 * @method auto_highlight
313 * @method auto_highlight
314 */
314 */
315 RawCell.prototype.auto_highlight = function () {
315 RawCell.prototype.auto_highlight = function () {
316 this._auto_highlight(this.config.raw_cell_highlight);
316 this._auto_highlight(this.config.raw_cell_highlight);
317 };
317 };
318
318
319 /** @method render **/
319 /** @method render **/
320 RawCell.prototype.render = function () {
320 RawCell.prototype.render = function () {
321 var cont = TextCell.prototype.render.apply(this);
321 var cont = TextCell.prototype.render.apply(this);
322 if (cont){
322 if (cont){
323 var text = this.get_text();
323 var text = this.get_text();
324 if (text === "") { text = this.placeholder; }
324 if (text === "") { text = this.placeholder; }
325 this.set_text(text);
325 this.set_text(text);
326 this.element.removeClass('rendered');
326 this.element.removeClass('rendered');
327 }
327 }
328 return cont;
328 return cont;
329 };
329 };
330
330
331
331
332 var HeadingCell = function (options) {
332 var HeadingCell = function (options) {
333 // Constructor
333 // Constructor
334 //
334 //
335 // Parameters:
335 // Parameters:
336 // options: dictionary
336 // options: dictionary
337 // Dictionary of keyword arguments.
337 // Dictionary of keyword arguments.
338 // events: $(Events) instance
338 // events: $(Events) instance
339 // config: dictionary
339 // config: dictionary
340 // keyboard_manager: KeyboardManager instance
340 // keyboard_manager: KeyboardManager instance
341 // notebook: Notebook instance
341 // notebook: Notebook instance
342 options = options || {};
342 options = options || {};
343 var config = this.mergeopt(HeadingCell, options.config);
343 var config = this.mergeopt(HeadingCell, options.config);
344 TextCell.apply(this, [$.extend({}, options, {config: config})]);
344 TextCell.apply(this, [$.extend({}, options, {config: config})]);
345
345
346 this.level = 1;
346 this.level = 1;
347 this.cell_type = 'heading';
347 this.cell_type = 'heading';
348 };
348 };
349
349
350 HeadingCell.options_default = {
350 HeadingCell.options_default = {
351 placeholder: "Type Heading Here"
351 placeholder: "Type Heading Here"
352 };
352 };
353
353
354 HeadingCell.prototype = new TextCell();
354 HeadingCell.prototype = new TextCell();
355
355
356 /** @method fromJSON */
356 /** @method fromJSON */
357 HeadingCell.prototype.fromJSON = function (data) {
357 HeadingCell.prototype.fromJSON = function (data) {
358 if (data.level !== undefined){
358 if (data.level !== undefined){
359 this.level = data.level;
359 this.level = data.level;
360 }
360 }
361 TextCell.prototype.fromJSON.apply(this, arguments);
361 TextCell.prototype.fromJSON.apply(this, arguments);
362 };
362 };
363
363
364
364
365 /** @method toJSON */
365 /** @method toJSON */
366 HeadingCell.prototype.toJSON = function () {
366 HeadingCell.prototype.toJSON = function () {
367 var data = TextCell.prototype.toJSON.apply(this);
367 var data = TextCell.prototype.toJSON.apply(this);
368 data.level = this.get_level();
368 data.level = this.get_level();
369 return data;
369 return data;
370 };
370 };
371
371
372 /**
372 /**
373 * can the cell be split into two cells
373 * can the cell be split into two cells
374 * @method is_splittable
374 * @method is_splittable
375 **/
375 **/
376 HeadingCell.prototype.is_splittable = function () {
376 HeadingCell.prototype.is_splittable = function () {
377 return false;
377 return false;
378 };
378 };
379
379
380
380
381 /**
381 /**
382 * can the cell be merged with other cells
382 * can the cell be merged with other cells
383 * @method is_mergeable
383 * @method is_mergeable
384 **/
384 **/
385 HeadingCell.prototype.is_mergeable = function () {
385 HeadingCell.prototype.is_mergeable = function () {
386 return false;
386 return false;
387 };
387 };
388
388
389 /**
389 /**
390 * Change heading level of cell, and re-render
390 * Change heading level of cell, and re-render
391 * @method set_level
391 * @method set_level
392 */
392 */
393 HeadingCell.prototype.set_level = function (level) {
393 HeadingCell.prototype.set_level = function (level) {
394 this.level = level;
394 this.level = level;
395 if (this.rendered) {
395 if (this.rendered) {
396 this.rendered = false;
396 this.rendered = false;
397 this.render();
397 this.render();
398 }
398 }
399 };
399 };
400
400
401 /** The depth of header cell, based on html (h1 to h6)
401 /** The depth of header cell, based on html (h1 to h6)
402 * @method get_level
402 * @method get_level
403 * @return {integer} level - for 1 to 6
403 * @return {integer} level - for 1 to 6
404 */
404 */
405 HeadingCell.prototype.get_level = function () {
405 HeadingCell.prototype.get_level = function () {
406 return this.level;
406 return this.level;
407 };
407 };
408
408
409
409
410 HeadingCell.prototype.get_rendered = function () {
410 HeadingCell.prototype.get_rendered = function () {
411 var r = this.element.find("div.text_cell_render");
411 var r = this.element.find("div.text_cell_render");
412 return r.children().first().html();
412 return r.children().first().html();
413 };
413 };
414
414
415 HeadingCell.prototype.render = function () {
415 HeadingCell.prototype.render = function () {
416 var cont = TextCell.prototype.render.apply(this);
416 var cont = TextCell.prototype.render.apply(this);
417 if (cont) {
417 if (cont) {
418 var text = this.get_text();
418 var text = this.get_text();
419 var math = null;
419 var math = null;
420 // Markdown headings must be a single line
420 // Markdown headings must be a single line
421 text = text.replace(/\n/g, ' ');
421 text = text.replace(/\n/g, ' ');
422 if (text === "") { text = this.placeholder; }
422 if (text === "") { text = this.placeholder; }
423 text = new Array(this.level + 1).join("#") + " " + text;
423 text = new Array(this.level + 1).join("#") + " " + text;
424 var text_and_math = mathjaxutils.remove_math(text);
424 var text_and_math = mathjaxutils.remove_math(text);
425 text = text_and_math[0];
425 text = text_and_math[0];
426 math = text_and_math[1];
426 math = text_and_math[1];
427 var html = marked.parser(marked.lexer(text));
427 var html = marked.parser(marked.lexer(text));
428 html = mathjaxutils.replace_math(html, math);
428 html = mathjaxutils.replace_math(html, math);
429 html = security.sanitize_html(html);
429 html = security.sanitize_html(html);
430 var h = $($.parseHTML(html));
430 var h = $($.parseHTML(html));
431 // add id and linkback anchor
431 // add id and linkback anchor
432 var hash = h.text().replace(/ /g, '-');
432 var hash = h.text().replace(/ /g, '-');
433 h.attr('id', hash);
433 h.attr('id', hash);
434 h.append(
434 h.append(
435 $('<a/>')
435 $('<a/>')
436 .addClass('anchor-link')
436 .addClass('anchor-link')
437 .attr('href', '#' + hash)
437 .attr('href', '#' + hash)
438 .text('ΒΆ')
438 .text('ΒΆ')
439 );
439 );
440 this.set_rendered(h);
440 this.set_rendered(h);
441 this.element.find('div.input_area').hide();
441 this.element.find('div.input_area').hide();
442 this.element.find("div.text_cell_render").show();
442 this.element.find("div.text_cell_render").show();
443 this.typeset();
443 this.typeset();
444 }
444 }
445 return cont;
445 return cont;
446 };
446 };
447
447
448 // Backwards compatability.
448 // Backwards compatability.
449 IPython.TextCell = TextCell;
449 IPython.TextCell = TextCell;
450 IPython.MarkdownCell = MarkdownCell;
450 IPython.MarkdownCell = MarkdownCell;
451 IPython.RawCell = RawCell;
451 IPython.RawCell = RawCell;
452 IPython.HeadingCell = HeadingCell;
452 IPython.HeadingCell = HeadingCell;
453
453
454 var Cells = {
454 var Cells = {
455 'TextCell': TextCell,
455 'TextCell': TextCell,
456 'MarkdownCell': MarkdownCell,
456 'MarkdownCell': MarkdownCell,
457 'RawCell': RawCell,
457 'RawCell': RawCell,
458 'HeadingCell': HeadingCell,
458 'HeadingCell': HeadingCell,
459 };
459 };
460 return Cells;
460 return Cells;
461 });
461 });
@@ -1,173 +1,173
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/bootstrap-tour/build/js/bootstrap-tour.min',
7 'bootstraptour',
8 ], function(IPython, $, Tour) {
8 ], function(IPython, $, Tour) {
9 "use strict";
9 "use strict";
10
10
11 var tour_style = "<div class='popover tour'>\n" +
11 var tour_style = "<div class='popover tour'>\n" +
12 "<div class='arrow'></div>\n" +
12 "<div class='arrow'></div>\n" +
13 "<div style='position:absolute; top:7px; right:7px'>\n" +
13 "<div style='position:absolute; top:7px; right:7px'>\n" +
14 "<button class='btn btn-default btn-sm icon-remove' data-role='end'></button>\n" +
14 "<button class='btn btn-default btn-sm icon-remove' data-role='end'></button>\n" +
15 "</div><h3 class='popover-title'></h3>\n" +
15 "</div><h3 class='popover-title'></h3>\n" +
16 "<div class='popover-content'></div>\n" +
16 "<div class='popover-content'></div>\n" +
17 "<div class='popover-navigation'>\n" +
17 "<div class='popover-navigation'>\n" +
18 "<button class='btn btn-default icon-step-backward' data-role='prev'></button>\n" +
18 "<button class='btn btn-default icon-step-backward' data-role='prev'></button>\n" +
19 "<button class='btn btn-default icon-step-forward pull-right' data-role='next'></button>\n" +
19 "<button class='btn btn-default icon-step-forward pull-right' data-role='next'></button>\n" +
20 "<button id='tour-pause' class='btn btn-sm btn-default icon-pause' data-resume-text='' data-pause-text='' data-role='pause-resume'></button>\n" +
20 "<button id='tour-pause' class='btn btn-sm btn-default icon-pause' data-resume-text='' data-pause-text='' data-role='pause-resume'></button>\n" +
21 "</div>\n" +
21 "</div>\n" +
22 "</div>";
22 "</div>";
23
23
24 var NotebookTour = function (notebook, events) {
24 var NotebookTour = function (notebook, events) {
25 var that = this;
25 var that = this;
26 this.notebook = notebook;
26 this.notebook = notebook;
27 this.step_duration = 0;
27 this.step_duration = 0;
28 this.events = events;
28 this.events = events;
29 this.tour_steps = [
29 this.tour_steps = [
30 {
30 {
31 title: "Welcome to the Notebook Tour",
31 title: "Welcome to the Notebook Tour",
32 placement: 'bottom',
32 placement: 'bottom',
33 orphan: true,
33 orphan: true,
34 content: "You can use the left and right arrow keys to go backwards and forwards.",
34 content: "You can use the left and right arrow keys to go backwards and forwards.",
35 }, {
35 }, {
36 element: "#notebook_name",
36 element: "#notebook_name",
37 title: "Filename",
37 title: "Filename",
38 placement: 'bottom',
38 placement: 'bottom',
39 content: "Click here to change the filename for this notebook."
39 content: "Click here to change the filename for this notebook."
40 }, {
40 }, {
41 element: $("#menus").parent(),
41 element: $("#menus").parent(),
42 placement: 'bottom',
42 placement: 'bottom',
43 backdrop: true,
43 backdrop: true,
44 title: "Notebook Menubar",
44 title: "Notebook Menubar",
45 content: "The menubar has menus for actions on the notebook, its cells, and the kernel it communicates with."
45 content: "The menubar has menus for actions on the notebook, its cells, and the kernel it communicates with."
46 }, {
46 }, {
47 element: "#maintoolbar",
47 element: "#maintoolbar",
48 placement: 'bottom',
48 placement: 'bottom',
49 backdrop: true,
49 backdrop: true,
50 title: "Notebook Toolbar",
50 title: "Notebook Toolbar",
51 content: "The toolbar has buttons for the most common actions. Hover your mouse over each button for more information."
51 content: "The toolbar has buttons for the most common actions. Hover your mouse over each button for more information."
52 }, {
52 }, {
53 element: "#modal_indicator",
53 element: "#modal_indicator",
54 title: "Mode Indicator",
54 title: "Mode Indicator",
55 placement: 'bottom',
55 placement: 'bottom',
56 content: "The Notebook has two modes: Edit Mode and Command Mode. In this area, an indicator can appear to tell you which mode you are in.",
56 content: "The Notebook has two modes: Edit Mode and Command Mode. In this area, an indicator can appear to tell you which mode you are in.",
57 onShow: function(tour) { that.command_icon_hack(); }
57 onShow: function(tour) { that.command_icon_hack(); }
58 }, {
58 }, {
59 element: "#modal_indicator",
59 element: "#modal_indicator",
60 title: "Command Mode",
60 title: "Command Mode",
61 placement: 'bottom',
61 placement: 'bottom',
62 onShow: function(tour) { notebook.command_mode(); that.command_icon_hack(); },
62 onShow: function(tour) { notebook.command_mode(); that.command_icon_hack(); },
63 onNext: function(tour) { that.edit_mode(); },
63 onNext: function(tour) { that.edit_mode(); },
64 content: "Right now you are in Command Mode, and many keyboard shortcuts are available. In this mode, no icon is displayed in the indicator area."
64 content: "Right now you are in Command Mode, and many keyboard shortcuts are available. In this mode, no icon is displayed in the indicator area."
65 }, {
65 }, {
66 element: "#modal_indicator",
66 element: "#modal_indicator",
67 title: "Edit Mode",
67 title: "Edit Mode",
68 placement: 'bottom',
68 placement: 'bottom',
69 onShow: function(tour) { that.edit_mode(); },
69 onShow: function(tour) { that.edit_mode(); },
70 content: "Pressing <code>Enter</code> or clicking in the input text area of the cell switches to Edit Mode."
70 content: "Pressing <code>Enter</code> or clicking in the input text area of the cell switches to Edit Mode."
71 }, {
71 }, {
72 element: '.selected',
72 element: '.selected',
73 title: "Edit Mode",
73 title: "Edit Mode",
74 placement: 'bottom',
74 placement: 'bottom',
75 onShow: function(tour) { that.edit_mode(); },
75 onShow: function(tour) { that.edit_mode(); },
76 content: "Notice that the border around the currently active cell changed color. Typing will insert text into the currently active cell."
76 content: "Notice that the border around the currently active cell changed color. Typing will insert text into the currently active cell."
77 }, {
77 }, {
78 element: '.selected',
78 element: '.selected',
79 title: "Back to Command Mode",
79 title: "Back to Command Mode",
80 placement: 'bottom',
80 placement: 'bottom',
81 onShow: function(tour) { notebook.command_mode(); },
81 onShow: function(tour) { notebook.command_mode(); },
82 onHide: function(tour) { $('#help_menu').parent().children('a').click(); },
82 onHide: function(tour) { $('#help_menu').parent().children('a').click(); },
83 content: "Pressing <code>Esc</code> or clicking outside of the input text area takes you back to Command Mode."
83 content: "Pressing <code>Esc</code> or clicking outside of the input text area takes you back to Command Mode."
84 }, {
84 }, {
85 element: '#keyboard_shortcuts',
85 element: '#keyboard_shortcuts',
86 title: "Keyboard Shortcuts",
86 title: "Keyboard Shortcuts",
87 placement: 'bottom',
87 placement: 'bottom',
88 onHide: function(tour) { $('#help_menu').parent().children('a').click(); },
88 onHide: function(tour) { $('#help_menu').parent().children('a').click(); },
89 content: "You can click here to get a list of all of the keyboard shortcuts."
89 content: "You can click here to get a list of all of the keyboard shortcuts."
90 }, {
90 }, {
91 element: "#kernel_indicator",
91 element: "#kernel_indicator",
92 title: "Kernel Indicator",
92 title: "Kernel Indicator",
93 placement: 'bottom',
93 placement: 'bottom',
94 onShow: function(tour) { events.trigger('status_idle.Kernel');},
94 onShow: function(tour) { events.trigger('status_idle.Kernel');},
95 content: "This is the Kernel indicator. It looks like this when the Kernel is idle.",
95 content: "This is the Kernel indicator. It looks like this when the Kernel is idle.",
96 }, {
96 }, {
97 element: "#kernel_indicator",
97 element: "#kernel_indicator",
98 title: "Kernel Indicator",
98 title: "Kernel Indicator",
99 placement: 'bottom',
99 placement: 'bottom',
100 onShow: function(tour) { events.trigger('status_busy.Kernel'); },
100 onShow: function(tour) { events.trigger('status_busy.Kernel'); },
101 content: "The Kernel indicator looks like this when the Kernel is busy.",
101 content: "The Kernel indicator looks like this when the Kernel is busy.",
102 }, {
102 }, {
103 element: ".icon-stop",
103 element: ".icon-stop",
104 placement: 'bottom',
104 placement: 'bottom',
105 title: "Interrupting the Kernel",
105 title: "Interrupting the Kernel",
106 onHide: function(tour) { events.trigger('status_idle.Kernel'); },
106 onHide: function(tour) { events.trigger('status_idle.Kernel'); },
107 content: "To cancel a computation in progress, you can click here."
107 content: "To cancel a computation in progress, you can click here."
108 }, {
108 }, {
109 element: "#notification_kernel",
109 element: "#notification_kernel",
110 placement: 'bottom',
110 placement: 'bottom',
111 onShow: function(tour) { $('.icon-stop').click(); },
111 onShow: function(tour) { $('.icon-stop').click(); },
112 title: "Notification Area",
112 title: "Notification Area",
113 content: "Messages in response to user actions (Save, Interrupt, etc) appear here."
113 content: "Messages in response to user actions (Save, Interrupt, etc) appear here."
114 }, {
114 }, {
115 title: "Fin.",
115 title: "Fin.",
116 placement: 'bottom',
116 placement: 'bottom',
117 orphan: true,
117 orphan: true,
118 content: "This concludes the IPython Notebook User Interface tour.Tour. Happy hacking!",
118 content: "This concludes the IPython Notebook User Interface tour.Tour. Happy hacking!",
119 }
119 }
120 ];
120 ];
121
121
122 this.tour = new Tour({
122 this.tour = new Tour({
123 //orphan: true,
123 //orphan: true,
124 storage: false, // start tour from beginning every time
124 storage: false, // start tour from beginning every time
125 //element: $("#ipython_notebook"),
125 //element: $("#ipython_notebook"),
126 debug: true,
126 debug: true,
127 reflex: true, // click on element to continue tour
127 reflex: true, // click on element to continue tour
128 //backdrop: true, // show dark behind popover
128 //backdrop: true, // show dark behind popover
129 animation: false,
129 animation: false,
130 duration: this.step_duration,
130 duration: this.step_duration,
131 onStart: function() { console.log('tour started'); },
131 onStart: function() { console.log('tour started'); },
132 // TODO: remove the onPause/onResume logic once pi's patch has been
132 // TODO: remove the onPause/onResume logic once pi's patch has been
133 // merged upstream to make this work via data-resume-class and
133 // merged upstream to make this work via data-resume-class and
134 // data-resume-text attributes.
134 // data-resume-text attributes.
135 onPause: this.toggle_pause_play,
135 onPause: this.toggle_pause_play,
136 onResume: this.toggle_pause_play,
136 onResume: this.toggle_pause_play,
137 steps: this.tour_steps,
137 steps: this.tour_steps,
138 template: tour_style,
138 template: tour_style,
139 orphan: true
139 orphan: true
140 });
140 });
141
141
142 };
142 };
143
143
144 NotebookTour.prototype.start = function () {
144 NotebookTour.prototype.start = function () {
145 console.log("let's start the tour");
145 console.log("let's start the tour");
146 this.tour.init();
146 this.tour.init();
147 this.tour.start();
147 this.tour.start();
148 if (this.tour.ended())
148 if (this.tour.ended())
149 {
149 {
150 this.tour.restart();
150 this.tour.restart();
151 }
151 }
152 };
152 };
153
153
154 NotebookTour.prototype.command_icon_hack = function() {
154 NotebookTour.prototype.command_icon_hack = function() {
155 $('#modal_indicator').css('min-height', 20);
155 $('#modal_indicator').css('min-height', 20);
156 };
156 };
157
157
158 NotebookTour.prototype.toggle_pause_play = function () {
158 NotebookTour.prototype.toggle_pause_play = function () {
159 $('#tour-pause').toggleClass('icon-pause icon-play');
159 $('#tour-pause').toggleClass('icon-pause icon-play');
160 };
160 };
161
161
162 NotebookTour.prototype.edit_mode = function() {
162 NotebookTour.prototype.edit_mode = function() {
163 this.notebook.focus_cell();
163 this.notebook.focus_cell();
164 this.notebook.edit_mode();
164 this.notebook.edit_mode();
165 };
165 };
166
166
167 // For backwards compatability.
167 // For backwards compatability.
168 IPython.NotebookTour = NotebookTour;
168 IPython.NotebookTour = NotebookTour;
169
169
170 return {'Tour': NotebookTour};
170 return {'Tour': NotebookTour};
171
171
172 });
172 });
173
173
@@ -1,116 +1,116
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 define([
4 define([
5 'base/js/namespace',
5 'base/js/namespace',
6 'jquery',
6 'jquery',
7 'base/js/utils',
7 'base/js/utils',
8 'services/kernels/js/kernel',
8 'services/kernels/js/kernel',
9 ], function(IPython, $, utils, kernel) {
9 ], function(IPython, $, utils, kernel) {
10 "use strict";
10 "use strict";
11
11
12 var Session = function(options){
12 var Session = function(options){
13 this.kernel = null;
13 this.kernel = null;
14 this.id = null;
14 this.id = null;
15 this.notebook = options.notebook;
15 this.notebook = options.notebook;
16 this.name = notebook.notebook_name;
16 this.name = options.notebook_name;
17 this.path = notebook.notebook_path;
17 this.path = options.notebook_path;
18 this.base_url = notebook.base_url;
18 this.base_url = options.base_url;
19 };
19 };
20
20
21 Session.prototype.start = function(callback) {
21 Session.prototype.start = function(callback) {
22 var that = this;
22 var that = this;
23 var model = {
23 var model = {
24 notebook : {
24 notebook : {
25 name : this.name,
25 name : this.name,
26 path : this.path
26 path : this.path
27 }
27 }
28 };
28 };
29 var settings = {
29 var settings = {
30 processData : false,
30 processData : false,
31 cache : false,
31 cache : false,
32 type : "POST",
32 type : "POST",
33 data: JSON.stringify(model),
33 data: JSON.stringify(model),
34 dataType : "json",
34 dataType : "json",
35 success : function (data, status, xhr) {
35 success : function (data, status, xhr) {
36 that._handle_start_success(data);
36 that._handle_start_success(data);
37 if (callback) {
37 if (callback) {
38 callback(data, status, xhr);
38 callback(data, status, xhr);
39 }
39 }
40 },
40 },
41 error : utils.log_ajax_error,
41 error : utils.log_ajax_error,
42 };
42 };
43 var url = utils.url_join_encode(this.base_url, 'api/sessions');
43 var url = utils.url_join_encode(this.base_url, 'api/sessions');
44 $.ajax(url, settings);
44 $.ajax(url, settings);
45 };
45 };
46
46
47 Session.prototype.rename_notebook = function (name, path) {
47 Session.prototype.rename_notebook = function (name, path) {
48 this.name = name;
48 this.name = name;
49 this.path = path;
49 this.path = path;
50 var model = {
50 var model = {
51 notebook : {
51 notebook : {
52 name : this.name,
52 name : this.name,
53 path : this.path
53 path : this.path
54 }
54 }
55 };
55 };
56 var settings = {
56 var settings = {
57 processData : false,
57 processData : false,
58 cache : false,
58 cache : false,
59 type : "PATCH",
59 type : "PATCH",
60 data: JSON.stringify(model),
60 data: JSON.stringify(model),
61 dataType : "json",
61 dataType : "json",
62 error : utils.log_ajax_error,
62 error : utils.log_ajax_error,
63 };
63 };
64 var url = utils.url_join_encode(this.base_url, 'api/sessions', this.id);
64 var url = utils.url_join_encode(this.base_url, 'api/sessions', this.id);
65 $.ajax(url, settings);
65 $.ajax(url, settings);
66 };
66 };
67
67
68 Session.prototype.delete = function() {
68 Session.prototype.delete = function() {
69 var settings = {
69 var settings = {
70 processData : false,
70 processData : false,
71 cache : false,
71 cache : false,
72 type : "DELETE",
72 type : "DELETE",
73 dataType : "json",
73 dataType : "json",
74 error : utils.log_ajax_error,
74 error : utils.log_ajax_error,
75 };
75 };
76 this.kernel.running = false;
76 this.kernel.running = false;
77 var url = utils.url_join_encode(this.base_url, 'api/sessions', this.id);
77 var url = utils.url_join_encode(this.base_url, 'api/sessions', this.id);
78 $.ajax(url, settings);
78 $.ajax(url, settings);
79 };
79 };
80
80
81 // Kernel related things
81 // Kernel related things
82 /**
82 /**
83 * Create the Kernel object associated with this Session.
83 * Create the Kernel object associated with this Session.
84 *
84 *
85 * @method _handle_start_success
85 * @method _handle_start_success
86 */
86 */
87 Session.prototype._handle_start_success = function (data, status, xhr) {
87 Session.prototype._handle_start_success = function (data, status, xhr) {
88 this.id = data.id;
88 this.id = data.id;
89 var kernel_service_url = utils.url_path_join(this.base_url, "api/kernels");
89 var kernel_service_url = utils.url_path_join(this.base_url, "api/kernels");
90 this.kernel = new kernel.Kernel(kernel_service_url, this.notebook);
90 this.kernel = new kernel.Kernel(kernel_service_url, this.notebook);
91 this.kernel._kernel_started(data.kernel);
91 this.kernel._kernel_started(data.kernel);
92 };
92 };
93
93
94 /**
94 /**
95 * Prompt the user to restart the IPython kernel.
95 * Prompt the user to restart the IPython kernel.
96 *
96 *
97 * @method restart_kernel
97 * @method restart_kernel
98 */
98 */
99 Session.prototype.restart_kernel = function () {
99 Session.prototype.restart_kernel = function () {
100 this.kernel.restart();
100 this.kernel.restart();
101 };
101 };
102
102
103 Session.prototype.interrupt_kernel = function() {
103 Session.prototype.interrupt_kernel = function() {
104 this.kernel.interrupt();
104 this.kernel.interrupt();
105 };
105 };
106
106
107
107
108 Session.prototype.kill_kernel = function() {
108 Session.prototype.kill_kernel = function() {
109 this.kernel.kill();
109 this.kernel.kill();
110 };
110 };
111
111
112 // For backwards compatability.
112 // For backwards compatability.
113 IPython.Session = Session;
113 IPython.Session = Session;
114
114
115 return {'Session': Session};
115 return {'Session': Session};
116 });
116 });
@@ -1,87 +1,91
1 <!DOCTYPE HTML>
1 <!DOCTYPE HTML>
2 <html>
2 <html>
3
3
4 <head>
4 <head>
5 <meta charset="utf-8">
5 <meta charset="utf-8">
6
6
7 <title>{% block title %}IPython Notebook{% endblock %}</title>
7 <title>{% block title %}IPython Notebook{% endblock %}</title>
8 <link rel="shortcut icon" type="image/x-icon" href="{{static_url("base/images/favicon.ico") }}">
8 <link rel="shortcut icon" type="image/x-icon" href="{{static_url("base/images/favicon.ico") }}">
9 <meta http-equiv="X-UA-Compatible" content="chrome=1">
9 <meta http-equiv="X-UA-Compatible" content="chrome=1">
10 <link rel="stylesheet" href="{{static_url("components/jquery-ui/themes/smoothness/jquery-ui.min.css") }}" type="text/css" />
10 <link rel="stylesheet" href="{{static_url("components/jquery-ui/themes/smoothness/jquery-ui.min.css") }}" type="text/css" />
11 <meta name="viewport" content="width=device-width, initial-scale=1.0">
11 <meta name="viewport" content="width=device-width, initial-scale=1.0">
12
12
13 {% block stylesheet %}
13 {% block stylesheet %}
14 <link rel="stylesheet" href="{{ static_url("style/style.min.css") }}" type="text/css"/>
14 <link rel="stylesheet" href="{{ static_url("style/style.min.css") }}" type="text/css"/>
15 {% endblock %}
15 {% endblock %}
16 <link rel="stylesheet" href="{{ static_url("custom/custom.css") }}" type="text/css" />
16 <link rel="stylesheet" href="{{ static_url("custom/custom.css") }}" type="text/css" />
17 <script src="{{static_url("components/requirejs/require.js") }}" type="text/javascript" charset="utf-8"></script>
17 <script src="{{static_url("components/requirejs/require.js") }}" type="text/javascript" charset="utf-8"></script>
18 <script>
18 <script>
19 require.config({
19 require.config({
20 baseUrl: '{{static_url("", include_version=False)}}',
20 baseUrl: '{{static_url("", include_version=False)}}',
21 paths: {
21 paths: {
22 nbextensions : '{{ base_url }}nbextensions',
22 nbextensions : '{{ base_url }}nbextensions',
23 underscore : 'components/underscore/underscore-min',
23 underscore : 'components/underscore/underscore-min',
24 backbone : 'components/backbone/backbone-min',
24 backbone : 'components/backbone/backbone-min',
25 jquery: 'components/jquery/jquery.min',
25 jquery: 'components/jquery/jquery.min',
26 bootstraptour: 'components/bootstrap-tour/build/js/bootstrap-tour.min',
26 },
27 },
27 shim: {
28 shim: {
28 underscore: {
29 underscore: {
29 exports: '_'
30 exports: '_'
30 },
31 },
31 backbone: {
32 backbone: {
32 deps: ["underscore", "jquery"],
33 deps: ["underscore", "jquery"],
33 exports: "Backbone"
34 exports: "Backbone"
35 },
36 bootstraptour: {
37 exports: "Tour"
34 }
38 }
35 }
39 }
36 });
40 });
37 </script>
41 </script>
38
42
39 {% block meta %}
43 {% block meta %}
40 {% endblock %}
44 {% endblock %}
41
45
42 </head>
46 </head>
43
47
44 <body {% block params %}{% endblock %}>
48 <body {% block params %}{% endblock %}>
45
49
46 <noscript>
50 <noscript>
47 <div id='noscript'>
51 <div id='noscript'>
48 IPython Notebook requires JavaScript.<br>
52 IPython Notebook requires JavaScript.<br>
49 Please enable it to proceed.
53 Please enable it to proceed.
50 </div>
54 </div>
51 </noscript>
55 </noscript>
52
56
53 <div id="header" class="navbar navbar-static-top">
57 <div id="header" class="navbar navbar-static-top">
54 <div class="container">
58 <div class="container">
55 <div id="ipython_notebook" class="nav navbar-brand pull-left"><a href="{{base_url}}tree/{{notebook_path}}" alt='dashboard'><img src='{{static_url("base/images/ipynblogo.png") }}' alt='IPython Notebook'/></a></div>
59 <div id="ipython_notebook" class="nav navbar-brand pull-left"><a href="{{base_url}}tree/{{notebook_path}}" alt='dashboard'><img src='{{static_url("base/images/ipynblogo.png") }}' alt='IPython Notebook'/></a></div>
56
60
57 {% block login_widget %}
61 {% block login_widget %}
58
62
59 <span id="login_widget">
63 <span id="login_widget">
60 {% if logged_in %}
64 {% if logged_in %}
61 <button id="logout">Logout</button>
65 <button id="logout">Logout</button>
62 {% elif login_available and not logged_in %}
66 {% elif login_available and not logged_in %}
63 <button id="login">Login</button>
67 <button id="login">Login</button>
64 {% endif %}
68 {% endif %}
65 </span>
69 </span>
66
70
67 {% endblock %}
71 {% endblock %}
68
72
69 {% block header %}
73 {% block header %}
70 {% endblock %}
74 {% endblock %}
71 </div>
75 </div>
72 </div>
76 </div>
73
77
74 <div id="site">
78 <div id="site">
75 {% block site %}
79 {% block site %}
76 {% endblock %}
80 {% endblock %}
77 </div>
81 </div>
78
82
79 {% block script %}
83 {% block script %}
80 <script src="{{static_url("base/js/pagemain.js") }}" type="text/javascript" charset="utf-8"></script>
84 <script src="{{static_url("base/js/pagemain.js") }}" type="text/javascript" charset="utf-8"></script>
81 {% endblock %}
85 {% endblock %}
82
86
83 <script src="{{static_url("custom/custom.js") }}" type="text/javascript" charset="utf-8"></script>
87 <script src="{{static_url("custom/custom.js") }}" type="text/javascript" charset="utf-8"></script>
84
88
85 </body>
89 </body>
86
90
87 </html>
91 </html>
@@ -1,536 +1,549
1 //
1 //
2 // Utility functions for the HTML notebook's CasperJS tests.
2 // Utility functions for the HTML notebook's CasperJS tests.
3 //
3 //
4 casper.get_notebook_server = function () {
4 casper.get_notebook_server = function () {
5 // Get the URL of a notebook server on which to run tests.
5 // Get the URL of a notebook server on which to run tests.
6 port = casper.cli.get("port");
6 port = casper.cli.get("port");
7 port = (typeof port === 'undefined') ? '8888' : port;
7 port = (typeof port === 'undefined') ? '8888' : port;
8 return 'http://127.0.0.1:' + port;
8 return 'http://127.0.0.1:' + port;
9 };
9 };
10
10
11 casper.open_new_notebook = function () {
11 casper.open_new_notebook = function () {
12 // Create and open a new notebook.
12 // Create and open a new notebook.
13 var baseUrl = this.get_notebook_server();
13 var baseUrl = this.get_notebook_server();
14 this.start(baseUrl);
14 this.start(baseUrl);
15 this.waitFor(this.page_loaded);
15 this.thenClick('button#new_notebook');
16 this.thenClick('button#new_notebook');
16 this.waitForPopup('');
17 this.waitForPopup('');
17
18
18 this.withPopup('', function () {this.waitForSelector('.CodeMirror-code');});
19 this.withPopup('', function () {this.waitForSelector('.CodeMirror-code');});
19 this.then(function () {
20 this.then(function () {
20 this.open(this.popups[0].url);
21 this.open(this.popups[0].url);
21 });
22 });
23 this.waitFor(this.page_loaded);
22
24
23 // Make sure the kernel has started
25 // Make sure the kernel has started
24 this.waitFor( this.kernel_running );
26 this.waitFor(this.kernel_running);
25 // track the IPython busy/idle state
27 // track the IPython busy/idle state
26 this.thenEvaluate(function () {
28 this.thenEvaluate(function () {
29 IPython._status = 'idle';
27 $([IPython.events]).on('status_idle.Kernel',function () {
30 $([IPython.events]).on('status_idle.Kernel',function () {
28 IPython._status = 'idle';
31 IPython._status = 'idle';
29 });
32 });
30 $([IPython.events]).on('status_busy.Kernel',function () {
33 $([IPython.events]).on('status_busy.Kernel',function () {
31 IPython._status = 'busy';
34 IPython._status = 'busy';
32 });
35 });
33 });
36 });
34
37
35 // Because of the asynchronous nature of SlimerJS (Gecko), we need to make
38 // Because of the asynchronous nature of SlimerJS (Gecko), we need to make
36 // sure the notebook has actually been loaded into the IPython namespace
39 // sure the notebook has actually been loaded into the IPython namespace
37 // before running any tests.
40 // before running any tests.
38 this.waitFor(function() {
41 this.waitFor(function() {
39 return this.evaluate(function () {
42 return this.evaluate(function () {
40 return IPython.notebook;
43 return IPython.notebook;
41 });
44 });
42 });
45 });
43 };
46 };
44
47
45 casper.kernel_running = function kernel_running() {
48 casper.page_loaded = function() {
46 // Return whether or not the kernel is running.
49 // Return whether or not the kernel is running.
47 return this.evaluate(function kernel_running() {
50 return this.evaluate(function() {
51 return IPython !== undefined &&
52 IPython.page !== undefined &&
53 IPython.events !== undefined;
54 });
55 };
56
57 casper.kernel_running = function() {
58 // Return whether or not the kernel is running.
59 return this.evaluate(function() {
48 return IPython.notebook.kernel.running;
60 return IPython.notebook.kernel.running;
49 });
61 });
50 };
62 };
51
63
52 casper.shutdown_current_kernel = function () {
64 casper.shutdown_current_kernel = function () {
53 // Shut down the current notebook's kernel.
65 // Shut down the current notebook's kernel.
54 this.thenEvaluate(function() {
66 this.thenEvaluate(function() {
55 IPython.notebook.kernel.kill();
67 IPython.notebook.kernel.kill();
56 });
68 });
57 // We close the page right after this so we need to give it time to complete.
69 // We close the page right after this so we need to give it time to complete.
58 this.wait(1000);
70 this.wait(1000);
59 };
71 };
60
72
61 casper.delete_current_notebook = function () {
73 casper.delete_current_notebook = function () {
62 // Delete created notebook.
74 // Delete created notebook.
63
75
64 // For some unknown reason, this doesn't work?!?
76 // For some unknown reason, this doesn't work?!?
65 this.thenEvaluate(function() {
77 this.thenEvaluate(function() {
66 IPython.notebook.delete();
78 IPython.notebook.delete();
67 });
79 });
68 };
80 };
69
81
70 casper.wait_for_busy = function () {
82 casper.wait_for_busy = function () {
71 // Waits for the notebook to enter a busy state.
83 // Waits for the notebook to enter a busy state.
72 this.waitFor(function () {
84 this.waitFor(function () {
73 return this.evaluate(function () {
85 return this.evaluate(function () {
74 return IPython._status == 'busy';
86 return IPython._status == 'busy';
75 });
87 });
76 });
88 });
77 };
89 };
78
90
79 casper.wait_for_idle = function () {
91 casper.wait_for_idle = function () {
80 // Waits for the notebook to idle.
92 // Waits for the notebook to idle.
81 this.waitFor(function () {
93 this.waitFor(function () {
82 return this.evaluate(function () {
94 return this.evaluate(function () {
83 return IPython._status == 'idle';
95 return IPython._status == 'idle';
84 });
96 });
85 });
97 });
86 };
98 };
87
99
88 casper.wait_for_output = function (cell_num, out_num) {
100 casper.wait_for_output = function (cell_num, out_num) {
89 // wait for the nth output in a given cell
101 // wait for the nth output in a given cell
90 this.wait_for_idle();
102 this.wait_for_idle();
91 out_num = out_num || 0;
103 out_num = out_num || 0;
92 this.then(function() {
104 this.then(function() {
93 this.waitFor(function (c, o) {
105 this.waitFor(function (c, o) {
94 return this.evaluate(function get_output(c, o) {
106 return this.evaluate(function get_output(c, o) {
95 var cell = IPython.notebook.get_cell(c);
107 var cell = IPython.notebook.get_cell(c);
96 return cell.output_area.outputs.length > o;
108 return cell.output_area.outputs.length > o;
97 },
109 },
98 // pass parameter from the test suite js to the browser code js
110 // pass parameter from the test suite js to the browser code js
99 {c : cell_num, o : out_num});
111 {c : cell_num, o : out_num});
100 });
112 });
101 },
113 },
102 function then() { },
114 function then() { },
103 function timeout() {
115 function timeout() {
104 this.echo("wait_for_output timed out!");
116 this.echo("wait_for_output timed out!");
105 });
117 });
106 };
118 };
107
119
108 casper.wait_for_widget = function (widget_info) {
120 casper.wait_for_widget = function (widget_info) {
109 // wait for a widget msg que to reach 0
121 // wait for a widget msg que to reach 0
110 //
122 //
111 // Parameters
123 // Parameters
112 // ----------
124 // ----------
113 // widget_info : object
125 // widget_info : object
114 // Object which contains info related to the widget. The model_id property
126 // Object which contains info related to the widget. The model_id property
115 // is used to identify the widget.
127 // is used to identify the widget.
116 this.waitFor(function () {
128 this.waitFor(function () {
117 var pending = this.evaluate(function (m) {
129 var pending = this.evaluate(function (m) {
118 return IPython.notebook.kernel.widget_manager.get_model(m).pending_msgs;
130 return IPython.notebook.kernel.widget_manager.get_model(m).pending_msgs;
119 }, {m: widget_info.model_id});
131 }, {m: widget_info.model_id});
120
132
121 if (pending === 0) {
133 if (pending === 0) {
122 return true;
134 return true;
123 } else {
135 } else {
124 return false;
136 return false;
125 }
137 }
126 });
138 });
127 };
139 };
128
140
129 casper.get_output_cell = function (cell_num, out_num) {
141 casper.get_output_cell = function (cell_num, out_num) {
130 // return an output of a given cell
142 // return an output of a given cell
131 out_num = out_num || 0;
143 out_num = out_num || 0;
132 var result = casper.evaluate(function (c, o) {
144 var result = casper.evaluate(function (c, o) {
133 var cell = IPython.notebook.get_cell(c);
145 var cell = IPython.notebook.get_cell(c);
134 return cell.output_area.outputs[o];
146 return cell.output_area.outputs[o];
135 },
147 },
136 {c : cell_num, o : out_num});
148 {c : cell_num, o : out_num});
137 if (!result) {
149 if (!result) {
138 var num_outputs = casper.evaluate(function (c) {
150 var num_outputs = casper.evaluate(function (c) {
139 var cell = IPython.notebook.get_cell(c);
151 var cell = IPython.notebook.get_cell(c);
140 return cell.output_area.outputs.length;
152 return cell.output_area.outputs.length;
141 },
153 },
142 {c : cell_num});
154 {c : cell_num});
143 this.test.assertTrue(false,
155 this.test.assertTrue(false,
144 "Cell " + cell_num + " has no output #" + out_num + " (" + num_outputs + " total)"
156 "Cell " + cell_num + " has no output #" + out_num + " (" + num_outputs + " total)"
145 );
157 );
146 } else {
158 } else {
147 return result;
159 return result;
148 }
160 }
149 };
161 };
150
162
151 casper.get_cells_length = function () {
163 casper.get_cells_length = function () {
152 // return the number of cells in the notebook
164 // return the number of cells in the notebook
153 var result = casper.evaluate(function () {
165 var result = casper.evaluate(function () {
154 return IPython.notebook.get_cells().length;
166 return IPython.notebook.get_cells().length;
155 });
167 });
156 return result;
168 return result;
157 };
169 };
158
170
159 casper.set_cell_text = function(index, text){
171 casper.set_cell_text = function(index, text){
160 // Set the text content of a cell.
172 // Set the text content of a cell.
161 this.evaluate(function (index, text) {
173 this.evaluate(function (index, text) {
162 var cell = IPython.notebook.get_cell(index);
174 var cell = IPython.notebook.get_cell(index);
163 cell.set_text(text);
175 cell.set_text(text);
164 }, index, text);
176 }, index, text);
165 };
177 };
166
178
167 casper.get_cell_text = function(index){
179 casper.get_cell_text = function(index){
168 // Get the text content of a cell.
180 // Get the text content of a cell.
169 return this.evaluate(function (index) {
181 return this.evaluate(function (index) {
170 var cell = IPython.notebook.get_cell(index);
182 var cell = IPython.notebook.get_cell(index);
171 return cell.get_text();
183 return cell.get_text();
172 }, index);
184 }, index);
173 };
185 };
174
186
175 casper.insert_cell_at_bottom = function(cell_type){
187 casper.insert_cell_at_bottom = function(cell_type){
176 // Inserts a cell at the bottom of the notebook
188 // Inserts a cell at the bottom of the notebook
177 // Returns the new cell's index.
189 // Returns the new cell's index.
178 return this.evaluate(function (cell_type) {
190 return this.evaluate(function (cell_type) {
179 var cell = IPython.notebook.insert_cell_at_bottom(cell_type);
191 var cell = IPython.notebook.insert_cell_at_bottom(cell_type);
180 return IPython.notebook.find_cell_index(cell);
192 return IPython.notebook.find_cell_index(cell);
181 }, cell_type);
193 }, cell_type);
182 };
194 };
183
195
184 casper.append_cell = function(text, cell_type) {
196 casper.append_cell = function(text, cell_type) {
185 // Insert a cell at the bottom of the notebook and set the cells text.
197 // Insert a cell at the bottom of the notebook and set the cells text.
186 // Returns the new cell's index.
198 // Returns the new cell's index.
187 var index = this.insert_cell_at_bottom(cell_type);
199 var index = this.insert_cell_at_bottom(cell_type);
188 if (text !== undefined) {
200 if (text !== undefined) {
189 this.set_cell_text(index, text);
201 this.set_cell_text(index, text);
190 }
202 }
191 return index;
203 return index;
192 };
204 };
193
205
194 casper.execute_cell = function(index){
206 casper.execute_cell = function(index){
195 // Asynchronously executes a cell by index.
207 // Asynchronously executes a cell by index.
196 // Returns the cell's index.
208 // Returns the cell's index.
197 var that = this;
209 var that = this;
198 this.then(function(){
210 this.then(function(){
199 that.evaluate(function (index) {
211 that.evaluate(function (index) {
200 var cell = IPython.notebook.get_cell(index);
212 var cell = IPython.notebook.get_cell(index);
201 cell.execute();
213 cell.execute();
202 }, index);
214 }, index);
203 });
215 });
204 return index;
216 return index;
205 };
217 };
206
218
207 casper.execute_cell_then = function(index, then_callback) {
219 casper.execute_cell_then = function(index, then_callback) {
208 // Synchronously executes a cell by index.
220 // Synchronously executes a cell by index.
209 // Optionally accepts a then_callback parameter. then_callback will get called
221 // Optionally accepts a then_callback parameter. then_callback will get called
210 // when the cell has finished executing.
222 // when the cell has finished executing.
211 // Returns the cell's index.
223 // Returns the cell's index.
212 var return_val = this.execute_cell(index);
224 var return_val = this.execute_cell(index);
213
225
214 this.wait_for_idle();
226 this.wait_for_idle();
215
227
216 var that = this;
228 var that = this;
217 this.then(function(){
229 this.then(function(){
218 if (then_callback!==undefined) {
230 if (then_callback!==undefined) {
219 then_callback.apply(that, [index]);
231 then_callback.apply(that, [index]);
220 }
232 }
221 });
233 });
222
234
223 return return_val;
235 return return_val;
224 };
236 };
225
237
226 casper.cell_element_exists = function(index, selector){
238 casper.cell_element_exists = function(index, selector){
227 // Utility function that allows us to easily check if an element exists
239 // Utility function that allows us to easily check if an element exists
228 // within a cell. Uses JQuery selector to look for the element.
240 // within a cell. Uses JQuery selector to look for the element.
229 return casper.evaluate(function (index, selector) {
241 return casper.evaluate(function (index, selector) {
230 var $cell = IPython.notebook.get_cell(index).element;
242 var $cell = IPython.notebook.get_cell(index).element;
231 return $cell.find(selector).length > 0;
243 return $cell.find(selector).length > 0;
232 }, index, selector);
244 }, index, selector);
233 };
245 };
234
246
235 casper.cell_element_function = function(index, selector, function_name, function_args){
247 casper.cell_element_function = function(index, selector, function_name, function_args){
236 // Utility function that allows us to execute a jQuery function on an
248 // Utility function that allows us to execute a jQuery function on an
237 // element within a cell.
249 // element within a cell.
238 return casper.evaluate(function (index, selector, function_name, function_args) {
250 return casper.evaluate(function (index, selector, function_name, function_args) {
239 var $cell = IPython.notebook.get_cell(index).element;
251 var $cell = IPython.notebook.get_cell(index).element;
240 var $el = $cell.find(selector);
252 var $el = $cell.find(selector);
241 return $el[function_name].apply($el, function_args);
253 return $el[function_name].apply($el, function_args);
242 }, index, selector, function_name, function_args);
254 }, index, selector, function_name, function_args);
243 };
255 };
244
256
245 casper.validate_notebook_state = function(message, mode, cell_index) {
257 casper.validate_notebook_state = function(message, mode, cell_index) {
246 // Validate the entire dual mode state of the notebook. Make sure no more than
258 // Validate the entire dual mode state of the notebook. Make sure no more than
247 // one cell is selected, focused, in edit mode, etc...
259 // one cell is selected, focused, in edit mode, etc...
248
260
249 // General tests.
261 // General tests.
250 this.test.assertEquals(this.get_keyboard_mode(), this.get_notebook_mode(),
262 this.test.assertEquals(this.get_keyboard_mode(), this.get_notebook_mode(),
251 message + '; keyboard and notebook modes match');
263 message + '; keyboard and notebook modes match');
252 // Is the selected cell the only cell that is selected?
264 // Is the selected cell the only cell that is selected?
253 if (cell_index!==undefined) {
265 if (cell_index!==undefined) {
254 this.test.assert(this.is_only_cell_selected(cell_index),
266 this.test.assert(this.is_only_cell_selected(cell_index),
255 message + '; cell ' + cell_index + ' is the only cell selected');
267 message + '; cell ' + cell_index + ' is the only cell selected');
256 }
268 }
257
269
258 // Mode specific tests.
270 // Mode specific tests.
259 if (mode==='command') {
271 if (mode==='command') {
260 // Are the notebook and keyboard manager in command mode?
272 // Are the notebook and keyboard manager in command mode?
261 this.test.assertEquals(this.get_keyboard_mode(), 'command',
273 this.test.assertEquals(this.get_keyboard_mode(), 'command',
262 message + '; in command mode');
274 message + '; in command mode');
263 // Make sure there isn't a single cell in edit mode.
275 // Make sure there isn't a single cell in edit mode.
264 this.test.assert(this.is_only_cell_edit(null),
276 this.test.assert(this.is_only_cell_edit(null),
265 message + '; all cells in command mode');
277 message + '; all cells in command mode');
266 this.test.assert(this.is_cell_editor_focused(null),
278 this.test.assert(this.is_cell_editor_focused(null),
267 message + '; no cell editors are focused while in command mode');
279 message + '; no cell editors are focused while in command mode');
268
280
269 } else if (mode==='edit') {
281 } else if (mode==='edit') {
270 // Are the notebook and keyboard manager in edit mode?
282 // Are the notebook and keyboard manager in edit mode?
271 this.test.assertEquals(this.get_keyboard_mode(), 'edit',
283 this.test.assertEquals(this.get_keyboard_mode(), 'edit',
272 message + '; in edit mode');
284 message + '; in edit mode');
273 if (cell_index!==undefined) {
285 if (cell_index!==undefined) {
274 // Is the specified cell the only cell in edit mode?
286 // Is the specified cell the only cell in edit mode?
275 this.test.assert(this.is_only_cell_edit(cell_index),
287 this.test.assert(this.is_only_cell_edit(cell_index),
276 message + '; cell ' + cell_index + ' is the only cell in edit mode');
288 message + '; cell ' + cell_index + ' is the only cell in edit mode');
277 // Is the specified cell the only cell with a focused code mirror?
289 // Is the specified cell the only cell with a focused code mirror?
278 this.test.assert(this.is_cell_editor_focused(cell_index),
290 this.test.assert(this.is_cell_editor_focused(cell_index),
279 message + '; cell ' + cell_index + '\'s editor is appropriately focused');
291 message + '; cell ' + cell_index + '\'s editor is appropriately focused');
280 }
292 }
281
293
282 } else {
294 } else {
283 this.test.assert(false, message + '; ' + mode + ' is an unknown mode');
295 this.test.assert(false, message + '; ' + mode + ' is an unknown mode');
284 }
296 }
285 };
297 };
286
298
287 casper.select_cell = function(index) {
299 casper.select_cell = function(index) {
288 // Select a cell in the notebook.
300 // Select a cell in the notebook.
289 this.evaluate(function (i) {
301 this.evaluate(function (i) {
290 IPython.notebook.select(i);
302 IPython.notebook.select(i);
291 }, {i: index});
303 }, {i: index});
292 };
304 };
293
305
294 casper.click_cell_editor = function(index) {
306 casper.click_cell_editor = function(index) {
295 // Emulate a click on a cell's editor.
307 // Emulate a click on a cell's editor.
296
308
297 // Code Mirror does not play nicely with emulated brower events.
309 // Code Mirror does not play nicely with emulated brower events.
298 // Instead of trying to emulate a click, here we run code similar to
310 // Instead of trying to emulate a click, here we run code similar to
299 // the code used in Code Mirror that handles the mousedown event on a
311 // the code used in Code Mirror that handles the mousedown event on a
300 // region of codemirror that the user can focus.
312 // region of codemirror that the user can focus.
301 this.evaluate(function (i) {
313 this.evaluate(function (i) {
302 var cm = IPython.notebook.get_cell(i).code_mirror;
314 var cm = IPython.notebook.get_cell(i).code_mirror;
303 if (cm.options.readOnly != "nocursor" && (document.activeElement != cm.display.input))
315 if (cm.options.readOnly != "nocursor" && (document.activeElement != cm.display.input))
304 cm.display.input.focus();
316 cm.display.input.focus();
305 }, {i: index});
317 }, {i: index});
306 };
318 };
307
319
308 casper.set_cell_editor_cursor = function(index, line_index, char_index) {
320 casper.set_cell_editor_cursor = function(index, line_index, char_index) {
309 // Set the Code Mirror instance cursor's location.
321 // Set the Code Mirror instance cursor's location.
310 this.evaluate(function (i, l, c) {
322 this.evaluate(function (i, l, c) {
311 IPython.notebook.get_cell(i).code_mirror.setCursor(l, c);
323 IPython.notebook.get_cell(i).code_mirror.setCursor(l, c);
312 }, {i: index, l: line_index, c: char_index});
324 }, {i: index, l: line_index, c: char_index});
313 };
325 };
314
326
315 casper.focus_notebook = function() {
327 casper.focus_notebook = function() {
316 // Focus the notebook div.
328 // Focus the notebook div.
317 this.evaluate(function (){
329 this.evaluate(function (){
318 $('#notebook').focus();
330 $('#notebook').focus();
319 }, {});
331 }, {});
320 };
332 };
321
333
322 casper.trigger_keydown = function() {
334 casper.trigger_keydown = function() {
323 // Emulate a keydown in the notebook.
335 // Emulate a keydown in the notebook.
324 for (var i = 0; i < arguments.length; i++) {
336 for (var i = 0; i < arguments.length; i++) {
325 this.evaluate(function (k) {
337 this.evaluate(function (k) {
326 var element = $(document);
338 var element = $(document);
327 var event = IPython.keyboard.shortcut_to_event(k, 'keydown');
339 var event = IPython.keyboard.shortcut_to_event(k, 'keydown');
328 element.trigger(event);
340 element.trigger(event);
329 }, {k: arguments[i]});
341 }, {k: arguments[i]});
330 }
342 }
331 };
343 };
332
344
333 casper.get_keyboard_mode = function() {
345 casper.get_keyboard_mode = function() {
334 // Get the mode of the keyboard manager.
346 // Get the mode of the keyboard manager.
335 return this.evaluate(function() {
347 return this.evaluate(function() {
336 return IPython.keyboard_manager.mode;
348 return IPython.keyboard_manager.mode;
337 }, {});
349 }, {});
338 };
350 };
339
351
340 casper.get_notebook_mode = function() {
352 casper.get_notebook_mode = function() {
341 // Get the mode of the notebook.
353 // Get the mode of the notebook.
342 return this.evaluate(function() {
354 return this.evaluate(function() {
343 return IPython.notebook.mode;
355 return IPython.notebook.mode;
344 }, {});
356 }, {});
345 };
357 };
346
358
347 casper.get_cell = function(index) {
359 casper.get_cell = function(index) {
348 // Get a single cell.
360 // Get a single cell.
349 //
361 //
350 // Note: Handles to DOM elements stored in the cell will be useless once in
362 // Note: Handles to DOM elements stored in the cell will be useless once in
351 // CasperJS context.
363 // CasperJS context.
352 return this.evaluate(function(i) {
364 return this.evaluate(function(i) {
353 var cell = IPython.notebook.get_cell(i);
365 var cell = IPython.notebook.get_cell(i);
354 if (cell) {
366 if (cell) {
355 return cell;
367 return cell;
356 }
368 }
357 return null;
369 return null;
358 }, {i : index});
370 }, {i : index});
359 };
371 };
360
372
361 casper.is_cell_editor_focused = function(index) {
373 casper.is_cell_editor_focused = function(index) {
362 // Make sure a cell's editor is the only editor focused on the page.
374 // Make sure a cell's editor is the only editor focused on the page.
363 return this.evaluate(function(i) {
375 return this.evaluate(function(i) {
364 var focused_textarea = $('#notebook .CodeMirror-focused textarea');
376 var focused_textarea = $('#notebook .CodeMirror-focused textarea');
365 if (focused_textarea.length > 1) { throw 'More than one Code Mirror editor is focused at once!'; }
377 if (focused_textarea.length > 1) { throw 'More than one Code Mirror editor is focused at once!'; }
366 if (i === null) {
378 if (i === null) {
367 return focused_textarea.length === 0;
379 return focused_textarea.length === 0;
368 } else {
380 } else {
369 var cell = IPython.notebook.get_cell(i);
381 var cell = IPython.notebook.get_cell(i);
370 if (cell) {
382 if (cell) {
371 return cell.code_mirror.getInputField() == focused_textarea[0];
383 return cell.code_mirror.getInputField() == focused_textarea[0];
372 }
384 }
373 }
385 }
374 return false;
386 return false;
375 }, {i : index});
387 }, {i : index});
376 };
388 };
377
389
378 casper.is_only_cell_selected = function(index) {
390 casper.is_only_cell_selected = function(index) {
379 // Check if a cell is the only cell selected.
391 // Check if a cell is the only cell selected.
380 // Pass null as the index to check if no cells are selected.
392 // Pass null as the index to check if no cells are selected.
381 return this.is_only_cell_on(index, 'selected', 'unselected');
393 return this.is_only_cell_on(index, 'selected', 'unselected');
382 };
394 };
383
395
384 casper.is_only_cell_edit = function(index) {
396 casper.is_only_cell_edit = function(index) {
385 // Check if a cell is the only cell in edit mode.
397 // Check if a cell is the only cell in edit mode.
386 // Pass null as the index to check if all of the cells are in command mode.
398 // Pass null as the index to check if all of the cells are in command mode.
387 return this.is_only_cell_on(index, 'edit_mode', 'command_mode');
399 return this.is_only_cell_on(index, 'edit_mode', 'command_mode');
388 };
400 };
389
401
390 casper.is_only_cell_on = function(i, on_class, off_class) {
402 casper.is_only_cell_on = function(i, on_class, off_class) {
391 // Check if a cell is the only cell with the `on_class` DOM class applied to it.
403 // Check if a cell is the only cell with the `on_class` DOM class applied to it.
392 // All of the other cells are checked for the `off_class` DOM class.
404 // All of the other cells are checked for the `off_class` DOM class.
393 // Pass null as the index to check if all of the cells have the `off_class`.
405 // Pass null as the index to check if all of the cells have the `off_class`.
394 var cells_length = this.get_cells_length();
406 var cells_length = this.get_cells_length();
395 for (var j = 0; j < cells_length; j++) {
407 for (var j = 0; j < cells_length; j++) {
396 if (j === i) {
408 if (j === i) {
397 if (this.cell_has_class(j, off_class) || !this.cell_has_class(j, on_class)) {
409 if (this.cell_has_class(j, off_class) || !this.cell_has_class(j, on_class)) {
398 return false;
410 return false;
399 }
411 }
400 } else {
412 } else {
401 if (!this.cell_has_class(j, off_class) || this.cell_has_class(j, on_class)) {
413 if (!this.cell_has_class(j, off_class) || this.cell_has_class(j, on_class)) {
402 return false;
414 return false;
403 }
415 }
404 }
416 }
405 }
417 }
406 return true;
418 return true;
407 };
419 };
408
420
409 casper.cell_has_class = function(index, classes) {
421 casper.cell_has_class = function(index, classes) {
410 // Check if a cell has a class.
422 // Check if a cell has a class.
411 return this.evaluate(function(i, c) {
423 return this.evaluate(function(i, c) {
412 var cell = IPython.notebook.get_cell(i);
424 var cell = IPython.notebook.get_cell(i);
413 if (cell) {
425 if (cell) {
414 return cell.element.hasClass(c);
426 return cell.element.hasClass(c);
415 }
427 }
416 return false;
428 return false;
417 }, {i : index, c: classes});
429 }, {i : index, c: classes});
418 };
430 };
419
431
420 casper.is_cell_rendered = function (index) {
432 casper.is_cell_rendered = function (index) {
421 return this.evaluate(function(i) {
433 return this.evaluate(function(i) {
422 return !!IPython.notebook.get_cell(i).rendered;
434 return !!IPython.notebook.get_cell(i).rendered;
423 }, {i:index});
435 }, {i:index});
424 };
436 };
425
437
426 casper.assert_colors_equal = function (hex_color, local_color, msg) {
438 casper.assert_colors_equal = function (hex_color, local_color, msg) {
427 // Tests to see if two colors are equal.
439 // Tests to see if two colors are equal.
428 //
440 //
429 // Parameters
441 // Parameters
430 // hex_color: string
442 // hex_color: string
431 // Hexadecimal color code, with or without preceeding hash character.
443 // Hexadecimal color code, with or without preceeding hash character.
432 // local_color: string
444 // local_color: string
433 // Local color representation. Can either be hexadecimal (default for
445 // Local color representation. Can either be hexadecimal (default for
434 // phantom) or rgb (default for slimer).
446 // phantom) or rgb (default for slimer).
435
447
436 // Remove parentheses, hashes, semi-colons, and space characters.
448 // Remove parentheses, hashes, semi-colons, and space characters.
437 hex_color = hex_color.replace(/[\(\); #]/, '');
449 hex_color = hex_color.replace(/[\(\); #]/, '');
438 local_color = local_color.replace(/[\(\); #]/, '');
450 local_color = local_color.replace(/[\(\); #]/, '');
439
451
440 // If the local color is rgb, clean it up and replace
452 // If the local color is rgb, clean it up and replace
441 if (local_color.substr(0,3).toLowerCase() == 'rgb') {
453 if (local_color.substr(0,3).toLowerCase() == 'rgb') {
442 components = local_color.substr(3).split(',');
454 components = local_color.substr(3).split(',');
443 local_color = '';
455 local_color = '';
444 for (var i = 0; i < components.length; i++) {
456 for (var i = 0; i < components.length; i++) {
445 var part = parseInt(components[i]).toString(16);
457 var part = parseInt(components[i]).toString(16);
446 while (part.length < 2) part = '0' + part;
458 while (part.length < 2) part = '0' + part;
447 local_color += part;
459 local_color += part;
448 }
460 }
449 }
461 }
450
462
451 this.test.assertEquals(hex_color.toUpperCase(), local_color.toUpperCase(), msg);
463 this.test.assertEquals(hex_color.toUpperCase(), local_color.toUpperCase(), msg);
452 };
464 };
453
465
454 casper.notebook_test = function(test) {
466 casper.notebook_test = function(test) {
455 // Wrap a notebook test to reduce boilerplate.
467 // Wrap a notebook test to reduce boilerplate.
456 this.open_new_notebook();
468 this.open_new_notebook();
457
469
458 // Echo whether or not we are running this test using SlimerJS
470 // Echo whether or not we are running this test using SlimerJS
459 if (this.evaluate(function(){
471 if (this.evaluate(function(){
460 return typeof InstallTrigger !== 'undefined'; // Firefox 1.0+
472 return typeof InstallTrigger !== 'undefined'; // Firefox 1.0+
461 })) {
473 })) {
462 console.log('This test is running in SlimerJS.');
474 console.log('This test is running in SlimerJS.');
463 this.slimerjs = true;
475 this.slimerjs = true;
464 }
476 }
465
477
466 // Make sure to remove the onbeforeunload callback. This callback is
478 // Make sure to remove the onbeforeunload callback. This callback is
467 // responsible for the "Are you sure you want to quit?" type messages.
479 // responsible for the "Are you sure you want to quit?" type messages.
468 // PhantomJS ignores these prompts, SlimerJS does not which causes hangs.
480 // PhantomJS ignores these prompts, SlimerJS does not which causes hangs.
469 this.then(function(){
481 this.then(function(){
470 this.evaluate(function(){
482 this.evaluate(function(){
471 window.onbeforeunload = function(){};
483 window.onbeforeunload = function(){};
472 });
484 });
473 });
485 });
474
486
475 this.then(test);
487 this.then(test);
476
488
477 // Kill the kernel and delete the notebook.
489 // Kill the kernel and delete the notebook.
478 this.shutdown_current_kernel();
490 this.shutdown_current_kernel();
479 // This is still broken but shouldn't be a problem for now.
491 // This is still broken but shouldn't be a problem for now.
480 // this.delete_current_notebook();
492 // this.delete_current_notebook();
481
493
482 // This is required to clean up the page we just finished with. If we don't call this
494 // This is required to clean up the page we just finished with. If we don't call this
483 // casperjs will leak file descriptors of all the open WebSockets in that page. We
495 // casperjs will leak file descriptors of all the open WebSockets in that page. We
484 // have to set this.page=null so that next time casper.start runs, it will create a
496 // have to set this.page=null so that next time casper.start runs, it will create a
485 // new page from scratch.
497 // new page from scratch.
486 this.then(function () {
498 this.then(function () {
487 this.page.close();
499 this.page.close();
488 this.page = null;
500 this.page = null;
489 });
501 });
490
502
491 // Run the browser automation.
503 // Run the browser automation.
492 this.run(function() {
504 this.run(function() {
493 this.test.done();
505 this.test.done();
494 });
506 });
495 };
507 };
496
508
497 casper.wait_for_dashboard = function () {
509 casper.wait_for_dashboard = function () {
498 // Wait for the dashboard list to load.
510 // Wait for the dashboard list to load.
499 casper.waitForSelector('.list_item');
511 casper.waitForSelector('.list_item');
500 };
512 };
501
513
502 casper.open_dashboard = function () {
514 casper.open_dashboard = function () {
503 // Start casper by opening the dashboard page.
515 // Start casper by opening the dashboard page.
504 var baseUrl = this.get_notebook_server();
516 var baseUrl = this.get_notebook_server();
505 this.start(baseUrl);
517 this.start(baseUrl);
518 this.waitFor(this.page_loaded);
506 this.wait_for_dashboard();
519 this.wait_for_dashboard();
507 };
520 };
508
521
509 casper.dashboard_test = function (test) {
522 casper.dashboard_test = function (test) {
510 // Open the dashboard page and run a test.
523 // Open the dashboard page and run a test.
511 this.open_dashboard();
524 this.open_dashboard();
512 this.then(test);
525 this.then(test);
513
526
514 this.then(function () {
527 this.then(function () {
515 this.page.close();
528 this.page.close();
516 this.page = null;
529 this.page = null;
517 });
530 });
518
531
519 // Run the browser automation.
532 // Run the browser automation.
520 this.run(function() {
533 this.run(function() {
521 this.test.done();
534 this.test.done();
522 });
535 });
523 };
536 };
524
537
525 casper.options.waitTimeout=10000;
538 casper.options.waitTimeout=10000;
526 casper.on('waitFor.timeout', function onWaitForTimeout(timeout) {
539 casper.on('waitFor.timeout', function onWaitForTimeout(timeout) {
527 this.echo("Timeout for " + casper.get_notebook_server());
540 this.echo("Timeout for " + casper.get_notebook_server());
528 this.echo("Is the notebook server running?");
541 this.echo("Is the notebook server running?");
529 });
542 });
530
543
531 casper.print_log = function () {
544 casper.print_log = function () {
532 // Pass `console.log` calls from page JS to casper.
545 // Pass `console.log` calls from page JS to casper.
533 this.on('remote.message', function(msg) {
546 this.on('remote.message', function(msg) {
534 this.echo('Remote message caught: ' + msg);
547 this.echo('Remote message caught: ' + msg);
535 });
548 });
536 };
549 };
General Comments 0
You need to be logged in to leave comments. Login now