##// END OF EJS Templates
Merge pull request #3393 from minrk/bootstrap...
Brian E. Granger -
r10974:20c70fc4 merge
parent child Browse files
Show More

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

@@ -0,0 +1,75 b''
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2013 The IPython Development Team
3 //
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
7
8 //============================================================================
9 // Utility for modal dialogs with bootstrap
10 //============================================================================
11
12 IPython.namespace('IPython.dialog');
13
14 IPython.dialog = (function (IPython) {
15
16 var modal = function (options) {
17 var dialog = $("<div/>").addClass("modal").attr("role", "dialog");
18 dialog.append(
19 $("<div/>")
20 .addClass("modal-header")
21 .append($("<button>")
22 .addClass("close")
23 .attr("data-dismiss", "modal")
24 .html("&times;")
25 ).append(
26 $("<h3/>").text(options.title || "")
27 )
28 ).append(
29 $("<div/>").addClass("modal-body").append(
30 options.body || $("<p/>")
31 )
32 );
33
34 var footer = $("<div/>").addClass("modal-footer");
35
36 for (var label in options.buttons) {
37 var btn_opts = options.buttons[label];
38 var button = $("<button/>")
39 .addClass("btn")
40 .attr("data-dismiss", "modal")
41 .text(label);
42 if (btn_opts.click) {
43 button.click($.proxy(btn_opts.click, dialog));
44 }
45 if (btn_opts.class) {
46 button.addClass(btn_opts.class);
47 }
48 footer.append(button);
49 }
50 dialog.append(footer);
51 // hook up on-open event
52 dialog.on("shown", function() {
53 setTimeout(function() {
54 footer.find("button").last().focus();
55 if (options.open) {
56 $.proxy(options.open, dialog)();
57 }
58 }, 0);
59 });
60
61 // destroy dialog on hide, unless explicitly asked not to
62 if (options.destroy == undefined || options.destroy) {
63 dialog.on("hidden", function () {
64 dialog.remove();
65 });
66 }
67
68 return dialog.modal(options);
69 }
70
71 return {
72 modal : modal,
73 };
74
75 }(IPython));
@@ -1,22 +1,21 b''
1 1 //----------------------------------------------------------------------------
2 2 // Copyright (C) 2008-2011 The IPython Development Team
3 3 //
4 4 // Distributed under the terms of the BSD License. The full license is in
5 5 // the file COPYING, distributed as part of this software.
6 6 //----------------------------------------------------------------------------
7 7
8 8 //============================================================================
9 9 // On document ready
10 10 //============================================================================
11 11
12 12
13 13 $(document).ready(function () {
14 14
15 15 IPython.page = new IPython.Page();
16 $('input#login_submit').button();
17 $('#ipython-main-app').addClass('border-box-sizing ui-widget');
16 $('button#login_submit').addClass("btn");
18 17 IPython.page.show();
19 18 $('input#password_input').focus();
20 19
21 20 });
22 21
@@ -1,46 +1,45 b''
1 1 //----------------------------------------------------------------------------
2 2 // Copyright (C) 2008-2011 The IPython Development Team
3 3 //
4 4 // Distributed under the terms of the BSD License. The full license is in
5 5 // the file COPYING, distributed as part of this software.
6 6 //----------------------------------------------------------------------------
7 7
8 8 //============================================================================
9 9 // Login button
10 10 //============================================================================
11 11
12 12 var IPython = (function (IPython) {
13 13
14 14 var LoginWidget = function (selector, options) {
15 15 var options = options || {};
16 16 this.base_url = options.baseProjectUrl || $('body').data('baseProjectUrl') ;
17 17 this.selector = selector;
18 18 if (this.selector !== undefined) {
19 19 this.element = $(selector);
20 20 this.style();
21 21 this.bind_events();
22 22 }
23 23 };
24 24
25 25 LoginWidget.prototype.style = function () {
26 this.element.find('button#logout').button();
27 this.element.find('button#login').button();
26 this.element.find("button").addClass("btn btn-small");
28 27 };
29 28
30 29
31 30 LoginWidget.prototype.bind_events = function () {
32 31 var that = this;
33 32 this.element.find("button#logout").click(function () {
34 33 window.location = that.base_url+"logout";
35 34 });
36 35 this.element.find("button#login").click(function () {
37 36 window.location = that.base_url+"login";
38 37 });
39 38 };
40 39
41 40 // Set module variables
42 41 IPython.LoginWidget = LoginWidget;
43 42
44 43 return IPython;
45 44
46 45 }(IPython));
@@ -1,20 +1,20 b''
1 1 //----------------------------------------------------------------------------
2 2 // Copyright (C) 2008-2011 The IPython Development Team
3 3 //
4 4 // Distributed under the terms of the BSD License. The full license is in
5 5 // the file COPYING, distributed as part of this software.
6 6 //----------------------------------------------------------------------------
7 7
8 8 //============================================================================
9 9 // On document ready
10 10 //============================================================================
11 11
12 12
13 13 $(document).ready(function () {
14 14
15 15 IPython.page = new IPython.Page();
16 $('#ipython-main-app').addClass('border-box-sizing ui-widget');
16 $('#ipython-main-app').addClass('border-box-sizing');
17 17 IPython.page.show();
18 18
19 19 });
20 20
@@ -1,1 +1,6 b''
1 // Custom styles for login.html No newline at end of file
1 // Custom styles for login.html
2 .center-nav {
3 display: inline-block;
4 // pull the lower margin back
5 margin-bottom: -4px;
6 } No newline at end of file
@@ -1,59 +1,55 b''
1 1 //----------------------------------------------------------------------------
2 2 // Copyright (C) 2008-2011 The IPython Development Team
3 3 //
4 4 // Distributed under the terms of the BSD License. The full license is in
5 5 // the file COPYING, distributed as part of this software.
6 6 //----------------------------------------------------------------------------
7 7
8 8 //============================================================================
9 9 // Global header/site setup.
10 10 //============================================================================
11 11
12 12 var IPython = (function (IPython) {
13 13
14 14 var Page = function () {
15 15 this.style();
16 16 this.bind_events();
17 17 };
18 18
19 19 Page.prototype.style = function () {
20 $('div#header').addClass('border-box-sizing').
21 addClass('ui-widget-content').
22 css('border-top-style','none').
23 css('border-left-style','none').
24 css('border-right-style','none');
25 $('div#site').addClass('border-box-sizing')
20 $('div#header').addClass('border-box-sizing');
21 $('div#site').addClass('border-box-sizing');
26 22 };
27 23
28 24
29 25 Page.prototype.bind_events = function () {
30 26 };
31 27
32 28
33 29 Page.prototype.show = function () {
34 30 // The header and site divs start out hidden to prevent FLOUC.
35 31 // Main scripts should call this method after styling everything.
36 32 this.show_header();
37 33 this.show_site();
38 34 };
39 35
40 36
41 37 Page.prototype.show_header = function () {
42 38 // The header and site divs start out hidden to prevent FLOUC.
43 39 // Main scripts should call this method after styling everything.
44 40 $('div#header').css('display','block');
45 41 };
46 42
47 43
48 44 Page.prototype.show_site = function () {
49 45 // The header and site divs start out hidden to prevent FLOUC.
50 46 // Main scripts should call this method after styling everything.
51 47 $('div#site').css('display','block');
52 48 };
53 49
54 50
55 51 IPython.Page = Page;
56 52
57 53 return IPython;
58 54
59 55 }(IPython));
@@ -1,70 +1,73 b''
1 1 /**
2 2 * Primary styles
3 3 *
4 4 * Author: IPython Development Team
5 5 */
6 6
7 7
8 8 body {
9 9 background-color: white;
10 10 /* This makes sure that the body covers the entire window and needs to
11 11 be in a different element than the display: box in wrapper below */
12 12 position: absolute;
13 13 left: 0px;
14 14 right: 0px;
15 15 top: 0px;
16 16 bottom: 0px;
17 17 overflow: visible;
18 18 }
19 19
20
21 20 div#header {
22 21 /* Initially hidden to prevent FLOUC */
23 22 display: none;
24 position: relative;
25 height: 40px;
26 padding: 5px;
27 margin: 0px;
28 width: 100%;
29 23 }
30 24
31 span#ipython_notebook {
32 position: absolute;
33 padding: 2px 2px 2px 5px;
25 #ipython_notebook {
26 padding-left: 16px;
34 27 }
35 28
36 span#ipython_notebook img {
29 #ipython_notebook img {
37 30 font-family: Verdana, "Helvetica Neue", Arial, Helvetica, Geneva, sans-serif;
38 31 height: 24px;
39 32 text-decoration:none;
40 display: inline;
41 33 color: black;
42 34 }
43 35
44 36 #site {
45 37 width: 100%;
46 38 display: none;
47 39 }
48 40
49 /* We set the fonts by hand here to override the values in the theme */
50 .ui-widget {
51 font-family: "Lucinda Grande", "Lucinda Sans Unicode", Helvetica, Arial, Verdana, sans-serif;
52 }
53
54 .ui-widget input, .ui-widget select, .ui-widget textarea, .ui-widget button {
55 font-family: "Lucinda Grande", "Lucinda Sans Unicode", Helvetica, Arial, Verdana, sans-serif;
56 }
57
58 41 /* Smaller buttons */
59 42 .ui-button .ui-button-text {
60 43 padding: 0.2em 0.8em;
61 44 font-size: 77%;
62 45 }
63 46
64 47 input.ui-button {
65 48 padding: 0.3em 0.9em;
66 49 }
50 .navbar span {
51 margin-top: 3px;
52 }
67 53
68 54 span#login_widget {
69 55 float: right;
70 56 }
57
58 .nav-header {
59 text-transform: none;
60 }
61
62 .navbar-nobg {
63 background-color: transparent;
64 background-image: none;
65 }
66
67 #header > span {
68 margin-top: 10px;
69 }
70
71 .modal-body {
72 max-height: 500px;
73 }
@@ -1,4 +1,6 b''
1 1 @import "../base/less/variables.less";
2 2 @import "../base/less/mixins.less";
3 3 @import "../base/less/flexbox.less";
4 4 @import "../base/less/page.less";
5 @import "../components/font-awesome/build/assets/font-awesome/less/font-awesome.less";
6 @FontAwesomePath: "../components/font-awesome/build/assets/font-awesome/font";
@@ -1,10 +1,11 b''
1 1 // Our customizations to bootstrap go here.
2 2
3 3 @textColor: @black;
4 4 @baseFontSize: 13px;
5 @baseLineHeight: 1.231;
6 @monoFontFamily: monospace; // to allow user to customize their fonts
5 @monoFontFamily: monospace; // to allow user to customize their fonts
6 @navbarHeight: 36px;
7 7
8 8 // Our own global variables for all pages go here
9 9
10 @corner_radius: 4px; No newline at end of file
10 @corner_radius: 4px;
11 @code_line_height: 1.231em;
@@ -1,9 +1,8 b''
1 1 /*This file contains any manual css for this page that needs to override the global styles.
2 2 This is only required when different pages style the same element differently. This is just
3 3 a hack to deal with our current css styles and no new styling should be added in this file.*/
4 4
5 5 #ipython-main-app {
6 width: 100%;
7 6 position: relative;
8 7 font-size: 110%;
9 8 } No newline at end of file
@@ -1,94 +1,90 b''
1 1 //----------------------------------------------------------------------------
2 2 // Copyright (C) 2012 The IPython Development Team
3 3 //
4 4 // Distributed under the terms of the BSD License. The full license is in
5 5 // the file COPYING, distributed as part of this software.
6 6 //----------------------------------------------------------------------------
7 7
8 8 //============================================================================
9 9 // CellToolbar Default
10 10 //============================================================================
11 11
12 12 /**
13 13 * Example Use for the CellToolbar library
14 14 */
15 15 // IIFE without asignement, we don't modifiy the IPython namespace
16 16 (function (IPython) {
17 17 "use strict";
18 18
19 19 var CellToolbar = IPython.CellToolbar;
20 20
21 21 var raw_edit = function(cell){
22 22
23 23 var md = cell.metadata
24 24 var error_div = $('<div/>').css('color','red')
25 25
26 26 var textarea = $('<textarea/>')
27 27 .attr('rows','13')
28 28 .attr('cols','75')
29 29 .attr('name','metadata')
30 30 .text(JSON.stringify(md, null,4)||'');
31 31 var dialogform = $('<div/>').attr('title','Edit the metadata')
32 32 .append(
33 33 $('<form/>').append(
34 34 $('<fieldset/>').append(
35 35 $('<label/>')
36 36 .attr('for','metadata')
37 37 .text("Manually edit the JSON below to manipulate the metadata for this cell. This assumes you know what you are doing and won't complain if it breaks your notebook. We also recommend putting your metadata attributes in an appropriately named sub-structure, so they don't conflict with those of others.")
38 38 )
39 39 .append(error_div)
40 40 .append($('<br/>'))
41 41 .append(
42 42 textarea
43 43 )
44 44 )
45 45 );
46 46 var editor = CodeMirror.fromTextArea(textarea[0], {
47 47 lineNumbers: true,
48 48 matchBrackets: true,
49 49 });
50 $(dialogform).dialog({
51 autoOpen: true,
52 height: 300,
53 width: 650,
54 modal: true,
50 IPython.dialog.modal({
51 title: "Edit Cell Metadata",
52 body: dialogform,
55 53 buttons: {
56 "Ok": function() {
54 "OK": { class : "btn-primary",
55 click: function() {
57 56 //validate json and set it
58 57 try {
59 58 var json = JSON.parse(editor.getValue());
60 59 cell.metadata = json;
61 $( this ).dialog( "close" );
62 }
63 catch(e)
64 {
60 } catch(e) {
65 61 error_div.text('Warning, invalid json, not saved');
62 return false;
66 63 }
67 },
68 Cancel: function() {
69 $( this ).dialog( "close" );
70 }
71 },
72 close: function() {
73 //cleanup on close
74 $(this).remove();
64 }},
65 Cancel: {}
75 66 }
76 67 });
77 68 editor.refresh();
78 69 }
79 70
80 71 var add_raw_edit_button = function(div, cell) {
81 var button_container = div
82 var button = $('<div/>').button({label:'Raw Edit'})
83 .click(function(){raw_edit(cell); return false;})
72 var button_container = div;
73 var button = $('<button/>')
74 .addClass("btn btn-mini")
75 .text("Raw Edit")
76 .click( function () {
77 raw_edit(cell);
78 return false;
79 });
84 80 button_container.append(button);
85 81 }
86 82
87 83 CellToolbar.register_callback('default.rawedit',add_raw_edit_button);
88 84 var example_preset = []
89 85 example_preset.push('default.rawedit');
90 86
91 87 CellToolbar.register_preset('Default',example_preset);
92 88 console.log('Default extension for metadata editing loaded.');
93 89
94 90 }(IPython));
@@ -1,439 +1,440 b''
1 1 //----------------------------------------------------------------------------
2 2 // Copyright (C) 2008-2011 The IPython Development Team
3 3 //
4 4 // Distributed under the terms of the BSD License. The full license is in
5 5 // the file COPYING, distributed as part of this software.
6 6 //----------------------------------------------------------------------------
7 7
8 8 //============================================================================
9 9 // CodeCell
10 10 //============================================================================
11 11 /**
12 12 * An extendable module that provide base functionnality to create cell for notebook.
13 13 * @module IPython
14 14 * @namespace IPython
15 15 * @submodule CodeCell
16 16 */
17 17
18 18
19 19 /* local util for codemirror */
20 20 var posEq = function(a, b) {return a.line == b.line && a.ch == b.ch;}
21 21
22 22 /**
23 23 *
24 24 * function to delete until previous non blanking space character
25 25 * or first multiple of 4 tabstop.
26 26 * @private
27 27 */
28 28 CodeMirror.commands.delSpaceToPrevTabStop = function(cm){
29 29 var from = cm.getCursor(true), to = cm.getCursor(false), sel = !posEq(from, to);
30 30 if (!posEq(from, to)) {cm.replaceRange("", from, to); return}
31 31 var cur = cm.getCursor(), line = cm.getLine(cur.line);
32 32 var tabsize = cm.getOption('tabSize');
33 33 var chToPrevTabStop = cur.ch-(Math.ceil(cur.ch/tabsize)-1)*tabsize;
34 34 var from = {ch:cur.ch-chToPrevTabStop,line:cur.line}
35 35 var select = cm.getRange(from,cur)
36 36 if( select.match(/^\ +$/) != null){
37 37 cm.replaceRange("",from,cur)
38 38 } else {
39 39 cm.deleteH(-1,"char")
40 40 }
41 41 };
42 42
43 43
44 44 var IPython = (function (IPython) {
45 45 "use strict";
46 46
47 47 var utils = IPython.utils;
48 48 var key = IPython.utils.keycodes;
49 49 CodeMirror.modeURL = "/static/components/codemirror/mode/%N/%N.js";
50 50
51 51 /**
52 52 * A Cell conceived to write code.
53 53 *
54 54 * The kernel doesn't have to be set at creation time, in that case
55 55 * it will be null and set_kernel has to be called later.
56 56 * @class CodeCell
57 57 * @extends IPython.Cell
58 58 *
59 59 * @constructor
60 60 * @param {Object|null} kernel
61 61 * @param {object|undefined} [options]
62 62 * @param [options.cm_config] {object} config to pass to CodeMirror
63 63 */
64 64 var CodeCell = function (kernel, options) {
65 65 this.kernel = kernel || null;
66 66 this.code_mirror = null;
67 67 this.input_prompt_number = null;
68 68 this.collapsed = false;
69 69 this.default_mode = 'ipython';
70 this.cell_type = "code";
70 71
71 72
72 73 var cm_overwrite_options = {
73 74 onKeyEvent: $.proxy(this.handle_codemirror_keyevent,this)
74 75 };
75 76
76 77 options = this.mergeopt(CodeCell, options, {cm_config:cm_overwrite_options});
77 78
78 79 IPython.Cell.apply(this,[options]);
79 80
80 81 var that = this;
81 82 this.element.focusout(
82 83 function() { that.auto_highlight(); }
83 84 );
84 85 };
85 86
86 87 CodeCell.options_default = {
87 88 cm_config : {
88 89 extraKeys: {"Tab": "indentMore","Shift-Tab" : "indentLess",'Backspace':"delSpaceToPrevTabStop"},
89 90 mode: 'ipython',
90 91 theme: 'ipython',
91 92 matchBrackets: true
92 93 }
93 94 };
94 95
95 96
96 97 CodeCell.prototype = new IPython.Cell();
97 98
98 99 /**
99 100 * @method auto_highlight
100 101 */
101 102 CodeCell.prototype.auto_highlight = function () {
102 103 this._auto_highlight(IPython.config.cell_magic_highlight)
103 104 };
104 105
105 106 /** @method create_element */
106 107 CodeCell.prototype.create_element = function () {
107 108 IPython.Cell.prototype.create_element.apply(this, arguments);
108 109
109 110 var cell = $('<div></div>').addClass('cell border-box-sizing code_cell');
110 111 cell.attr('tabindex','2');
111 112
112 113 this.celltoolbar = new IPython.CellToolbar(this);
113 114
114 115 var input = $('<div></div>').addClass('input');
115 116 var vbox = $('<div/>').addClass('vbox box-flex1')
116 117 input.append($('<div/>').addClass('prompt input_prompt'));
117 118 vbox.append(this.celltoolbar.element);
118 119 var input_area = $('<div/>').addClass('input_area');
119 120 this.code_mirror = CodeMirror(input_area.get(0), this.cm_config);
120 121 $(this.code_mirror.getInputField()).attr("spellcheck", "false");
121 122 vbox.append(input_area);
122 123 input.append(vbox);
123 124 var output = $('<div></div>');
124 125 cell.append(input).append(output);
125 126 this.element = cell;
126 127 this.output_area = new IPython.OutputArea(output, true);
127 128
128 129 // construct a completer only if class exist
129 130 // otherwise no print view
130 131 if (IPython.Completer !== undefined)
131 132 {
132 133 this.completer = new IPython.Completer(this);
133 134 }
134 135 };
135 136
136 137 /**
137 138 * This method gets called in CodeMirror's onKeyDown/onKeyPress
138 139 * handlers and is used to provide custom key handling. Its return
139 140 * value is used to determine if CodeMirror should ignore the event:
140 141 * true = ignore, false = don't ignore.
141 142 * @method handle_codemirror_keyevent
142 143 */
143 144 CodeCell.prototype.handle_codemirror_keyevent = function (editor, event) {
144 145
145 146 if (this.read_only){
146 147 return false;
147 148 }
148 149
149 150 var that = this;
150 151 // whatever key is pressed, first, cancel the tooltip request before
151 152 // they are sent, and remove tooltip if any, except for tab again
152 153 if (event.type === 'keydown' && event.which != key.TAB ) {
153 154 IPython.tooltip.remove_and_cancel_tooltip();
154 155 };
155 156
156 157 var cur = editor.getCursor();
157 158 if (event.keyCode === key.ENTER){
158 159 this.auto_highlight();
159 160 }
160 161
161 162 if (event.keyCode === key.ENTER && (event.shiftKey || event.ctrlKey)) {
162 163 // Always ignore shift-enter in CodeMirror as we handle it.
163 164 return true;
164 165 } else if (event.which === 40 && event.type === 'keypress' && IPython.tooltip.time_before_tooltip >= 0) {
165 166 // triger on keypress (!) otherwise inconsistent event.which depending on plateform
166 167 // browser and keyboard layout !
167 168 // Pressing '(' , request tooltip, don't forget to reappend it
168 169 IPython.tooltip.pending(that);
169 170 } else if (event.which === key.UPARROW && event.type === 'keydown') {
170 171 // If we are not at the top, let CM handle the up arrow and
171 172 // prevent the global keydown handler from handling it.
172 173 if (!that.at_top()) {
173 174 event.stop();
174 175 return false;
175 176 } else {
176 177 return true;
177 178 };
178 179 } else if (event.which === key.ESC) {
179 180 IPython.tooltip.remove_and_cancel_tooltip(true);
180 181 return true;
181 182 } else if (event.which === key.DOWNARROW && event.type === 'keydown') {
182 183 // If we are not at the bottom, let CM handle the down arrow and
183 184 // prevent the global keydown handler from handling it.
184 185 if (!that.at_bottom()) {
185 186 event.stop();
186 187 return false;
187 188 } else {
188 189 return true;
189 190 };
190 191 } else if (event.keyCode === key.TAB && event.type == 'keydown' && event.shiftKey) {
191 192 if (editor.somethingSelected()){
192 193 var anchor = editor.getCursor("anchor");
193 194 var head = editor.getCursor("head");
194 195 if( anchor.line != head.line){
195 196 return false;
196 197 }
197 198 }
198 199 IPython.tooltip.request(that);
199 200 event.stop();
200 201 return true;
201 202 } else if (event.keyCode === key.TAB && event.type == 'keydown') {
202 203 // Tab completion.
203 204 //Do not trim here because of tooltip
204 205 if (editor.somethingSelected()){return false}
205 206 var pre_cursor = editor.getRange({line:cur.line,ch:0},cur);
206 207 if (pre_cursor.trim() === "") {
207 208 // Don't autocomplete if the part of the line before the cursor
208 209 // is empty. In this case, let CodeMirror handle indentation.
209 210 return false;
210 211 } else if ((pre_cursor.substr(-1) === "("|| pre_cursor.substr(-1) === " ") && IPython.config.tooltip_on_tab ) {
211 212 IPython.tooltip.request(that);
212 213 // Prevent the event from bubbling up.
213 214 event.stop();
214 215 // Prevent CodeMirror from handling the tab.
215 216 return true;
216 217 } else {
217 218 event.stop();
218 219 this.completer.startCompletion();
219 220 return true;
220 221 };
221 222 } else {
222 223 // keypress/keyup also trigger on TAB press, and we don't want to
223 224 // use those to disable tab completion.
224 225 return false;
225 226 };
226 227 return false;
227 228 };
228 229
229 230
230 231 // Kernel related calls.
231 232
232 233 CodeCell.prototype.set_kernel = function (kernel) {
233 234 this.kernel = kernel;
234 235 }
235 236
236 237 /**
237 238 * Execute current code cell to the kernel
238 239 * @method execute
239 240 */
240 241 CodeCell.prototype.execute = function () {
241 242 this.output_area.clear_output(true, true, true);
242 243 this.set_input_prompt('*');
243 244 this.element.addClass("running");
244 245 var callbacks = {
245 246 'execute_reply': $.proxy(this._handle_execute_reply, this),
246 247 'output': $.proxy(this.output_area.handle_output, this.output_area),
247 248 'clear_output': $.proxy(this.output_area.handle_clear_output, this.output_area),
248 249 'set_next_input': $.proxy(this._handle_set_next_input, this),
249 250 'input_request': $.proxy(this._handle_input_request, this)
250 251 };
251 252 var msg_id = this.kernel.execute(this.get_text(), callbacks, {silent: false});
252 253 };
253 254
254 255 /**
255 256 * @method _handle_execute_reply
256 257 * @private
257 258 */
258 259 CodeCell.prototype._handle_execute_reply = function (content) {
259 260 this.set_input_prompt(content.execution_count);
260 261 this.element.removeClass("running");
261 262 $([IPython.events]).trigger('set_dirty.Notebook', {value: true});
262 263 }
263 264
264 265 /**
265 266 * @method _handle_set_next_input
266 267 * @private
267 268 */
268 269 CodeCell.prototype._handle_set_next_input = function (text) {
269 270 var data = {'cell': this, 'text': text}
270 271 $([IPython.events]).trigger('set_next_input.Notebook', data);
271 272 }
272 273
273 274 /**
274 275 * @method _handle_input_request
275 276 * @private
276 277 */
277 278 CodeCell.prototype._handle_input_request = function (content) {
278 279 this.output_area.append_raw_input(content);
279 280 }
280 281
281 282
282 283 // Basic cell manipulation.
283 284
284 285 CodeCell.prototype.select = function () {
285 286 IPython.Cell.prototype.select.apply(this);
286 287 this.code_mirror.refresh();
287 288 this.code_mirror.focus();
288 289 this.auto_highlight();
289 290 // We used to need an additional refresh() after the focus, but
290 291 // it appears that this has been fixed in CM. This bug would show
291 292 // up on FF when a newly loaded markdown cell was edited.
292 293 };
293 294
294 295
295 296 CodeCell.prototype.select_all = function () {
296 297 var start = {line: 0, ch: 0};
297 298 var nlines = this.code_mirror.lineCount();
298 299 var last_line = this.code_mirror.getLine(nlines-1);
299 300 var end = {line: nlines-1, ch: last_line.length};
300 301 this.code_mirror.setSelection(start, end);
301 302 };
302 303
303 304
304 305 CodeCell.prototype.collapse = function () {
305 306 this.collapsed = true;
306 307 this.output_area.collapse();
307 308 };
308 309
309 310
310 311 CodeCell.prototype.expand = function () {
311 312 this.collapsed = false;
312 313 this.output_area.expand();
313 314 };
314 315
315 316
316 317 CodeCell.prototype.toggle_output = function () {
317 318 this.collapsed = Boolean(1 - this.collapsed);
318 319 this.output_area.toggle_output();
319 320 };
320 321
321 322
322 323 CodeCell.prototype.toggle_output_scroll = function () {
323 324 this.output_area.toggle_scroll();
324 325 };
325 326
326 327
327 328 CodeCell.input_prompt_classical = function (prompt_value, lines_number) {
328 329 var ns = prompt_value || "&nbsp;";
329 330 return 'In&nbsp;[' + ns + ']:'
330 331 };
331 332
332 333 CodeCell.input_prompt_continuation = function (prompt_value, lines_number) {
333 334 var html = [CodeCell.input_prompt_classical(prompt_value, lines_number)];
334 335 for(var i=1; i < lines_number; i++){html.push(['...:'])};
335 336 return html.join('</br>')
336 337 };
337 338
338 339 CodeCell.input_prompt_function = CodeCell.input_prompt_classical;
339 340
340 341
341 342 CodeCell.prototype.set_input_prompt = function (number) {
342 343 var nline = 1
343 344 if( this.code_mirror != undefined) {
344 345 nline = this.code_mirror.lineCount();
345 346 }
346 347 this.input_prompt_number = number;
347 348 var prompt_html = CodeCell.input_prompt_function(this.input_prompt_number, nline);
348 349 this.element.find('div.input_prompt').html(prompt_html);
349 350 };
350 351
351 352
352 353 CodeCell.prototype.clear_input = function () {
353 354 this.code_mirror.setValue('');
354 355 };
355 356
356 357
357 358 CodeCell.prototype.get_text = function () {
358 359 return this.code_mirror.getValue();
359 360 };
360 361
361 362
362 363 CodeCell.prototype.set_text = function (code) {
363 364 return this.code_mirror.setValue(code);
364 365 };
365 366
366 367
367 368 CodeCell.prototype.at_top = function () {
368 369 var cursor = this.code_mirror.getCursor();
369 370 if (cursor.line === 0 && cursor.ch === 0) {
370 371 return true;
371 372 } else {
372 373 return false;
373 374 }
374 375 };
375 376
376 377
377 378 CodeCell.prototype.at_bottom = function () {
378 379 var cursor = this.code_mirror.getCursor();
379 380 if (cursor.line === (this.code_mirror.lineCount()-1) && cursor.ch === this.code_mirror.getLine(cursor.line).length) {
380 381 return true;
381 382 } else {
382 383 return false;
383 384 }
384 385 };
385 386
386 387
387 388 CodeCell.prototype.clear_output = function (stdout, stderr, other) {
388 389 this.output_area.clear_output(stdout, stderr, other);
389 390 };
390 391
391 392
392 393 // JSON serialization
393 394
394 395 CodeCell.prototype.fromJSON = function (data) {
395 396 IPython.Cell.prototype.fromJSON.apply(this, arguments);
396 397 if (data.cell_type === 'code') {
397 398 if (data.input !== undefined) {
398 399 this.set_text(data.input);
399 400 // make this value the starting point, so that we can only undo
400 401 // to this state, instead of a blank cell
401 402 this.code_mirror.clearHistory();
402 403 this.auto_highlight();
403 404 }
404 405 if (data.prompt_number !== undefined) {
405 406 this.set_input_prompt(data.prompt_number);
406 407 } else {
407 408 this.set_input_prompt();
408 409 };
409 410 this.output_area.fromJSON(data.outputs);
410 411 if (data.collapsed !== undefined) {
411 412 if (data.collapsed) {
412 413 this.collapse();
413 414 } else {
414 415 this.expand();
415 416 };
416 417 };
417 418 };
418 419 };
419 420
420 421
421 422 CodeCell.prototype.toJSON = function () {
422 423 var data = IPython.Cell.prototype.toJSON.apply(this);
423 424 data.input = this.get_text();
424 425 data.cell_type = 'code';
425 426 if (this.input_prompt_number) {
426 427 data.prompt_number = this.input_prompt_number;
427 428 };
428 429 var outputs = this.output_area.toJSON();
429 430 data.outputs = outputs;
430 431 data.language = 'python';
431 432 data.collapsed = this.collapsed;
432 433 return data;
433 434 };
434 435
435 436
436 437 IPython.CodeCell = CodeCell;
437 438
438 439 return IPython;
439 440 }(IPython));
@@ -1,62 +1,62 b''
1 1 //----------------------------------------------------------------------------
2 2 // Copyright (C) 2008-2011 The IPython Development Team
3 3 //
4 4 // Distributed under the terms of the BSD License. The full license is in
5 5 // the file COPYING, distributed as part of this software.
6 6 //----------------------------------------------------------------------------
7 7
8 8 //============================================================================
9 9 // Layout
10 10 //============================================================================
11 11
12 12 var IPython = (function (IPython) {
13 13
14 14 var LayoutManager = function () {
15 15 this.bind_events();
16 16 };
17 17
18 18
19 19 LayoutManager.prototype.bind_events = function () {
20 20 $(window).resize($.proxy(this.do_resize,this));
21 21 };
22 22
23 23 LayoutManager.prototype.app_height = function() {
24 24 var win = $(window);
25 25 var w = win.width();
26 26 var h = win.height();
27 27 var header_height;
28 28 if ($('div#header').css('display') === 'none') {
29 29 header_height = 0;
30 30 } else {
31 31 header_height = $('div#header').outerHeight(true);
32 32 }
33 33 var menubar_height = $('div#menubar').outerHeight(true);
34 34 var toolbar_height;
35 35 if ($('div#maintoolbar').css('display') === 'none') {
36 36 toolbar_height = 0;
37 37 } else {
38 38 toolbar_height = $('div#maintoolbar').outerHeight(true);
39 39 }
40 40 return h-header_height-menubar_height-toolbar_height; // content height
41 41 }
42 42
43 43 LayoutManager.prototype.do_resize = function () {
44 44 var app_height = this.app_height() // content height
45 45
46 46 $('#ipython-main-app').height(app_height); // content+padding+border height
47 47
48 48 var pager_height = IPython.pager.percentage_height*app_height;
49 49 var pager_splitter_height = $('div#pager_splitter').outerHeight(true);
50 $('div#pager').height(pager_height);
50 $('div#pager').outerHeight(pager_height);
51 51 if (IPython.pager.expanded) {
52 $('div#notebook').height(app_height-pager_height-pager_splitter_height);
52 $('div#notebook').outerHeight(app_height-pager_height-pager_splitter_height);
53 53 } else {
54 $('div#notebook').height(app_height-pager_splitter_height);
54 $('div#notebook').outerHeight(app_height-pager_splitter_height);
55 55 }
56 56 };
57 57
58 58 IPython.LayoutManager = LayoutManager;
59 59
60 60 return IPython;
61 61
62 62 }(IPython));
@@ -1,125 +1,123 b''
1 1 //----------------------------------------------------------------------------
2 2 // Copyright (C) 2011 The IPython Development Team
3 3 //
4 4 // Distributed under the terms of the BSD License. The full license is in
5 5 // the file COPYING, distributed as part of this software.
6 6 //----------------------------------------------------------------------------
7 7
8 8 //============================================================================
9 9 // On document ready
10 10 //============================================================================
11 11 "use strict";
12 12
13 13 // for the time beeing, we have to pass marked as a parameter here,
14 14 // as injecting require.js make marked not to put itself in the globals,
15 15 // which make both this file fail at setting marked configuration, and textcell.js
16 16 // which search marked into global.
17 17 require(['components/marked/lib/marked'],
18 18
19 19 function (marked) {
20 20
21 21 window.marked = marked
22 22
23 23 // monkey patch CM to be able to syntax highlight cell magics
24 24 // bug reported upstream,
25 25 // see https://github.com/marijnh/CodeMirror2/issues/670
26 26 if(CodeMirror.getMode(1,'text/plain').indent == undefined ){
27 27 console.log('patching CM for undefined indent');
28 28 CodeMirror.modes.null = function() {
29 29 return {token: function(stream) {stream.skipToEnd();},indent : function(){return 0}}
30 30 }
31 31 }
32 32
33 33 CodeMirror.patchedGetMode = function(config, mode){
34 34 var cmmode = CodeMirror.getMode(config, mode);
35 35 if(cmmode.indent == null)
36 36 {
37 37 console.log('patch mode "' , mode, '" on the fly');
38 38 cmmode.indent = function(){return 0};
39 39 }
40 40 return cmmode;
41 41 }
42 42 // end monkey patching CodeMirror
43 43
44 44 IPython.mathjaxutils.init();
45 45
46 46 IPython.read_only = $('body').data('readOnly') === 'True';
47 47 $('#ipython-main-app').addClass('border-box-sizing');
48 48 $('div#notebook_panel').addClass('border-box-sizing');
49 // The header's bottom border is provided by the menu bar so we remove it.
50 $('div#header').css('border-bottom-style','none');
51 49
52 50 var baseProjectUrl = $('body').data('baseProjectUrl')
53 51
54 52 IPython.page = new IPython.Page();
55 53 IPython.layout_manager = new IPython.LayoutManager();
56 54 IPython.pager = new IPython.Pager('div#pager', 'div#pager_splitter');
57 55 IPython.quick_help = new IPython.QuickHelp();
58 56 IPython.login_widget = new IPython.LoginWidget('span#login_widget',{baseProjectUrl:baseProjectUrl});
59 57 IPython.notebook = new IPython.Notebook('div#notebook',{baseProjectUrl:baseProjectUrl, read_only:IPython.read_only});
60 58 IPython.save_widget = new IPython.SaveWidget('span#save_widget');
61 59 IPython.menubar = new IPython.MenuBar('#menubar',{baseProjectUrl:baseProjectUrl})
62 IPython.toolbar = new IPython.MainToolBar('#maintoolbar')
60 IPython.toolbar = new IPython.MainToolBar('#maintoolbar-container')
63 61 IPython.tooltip = new IPython.Tooltip()
64 62 IPython.notification_area = new IPython.NotificationArea('#notification_area')
65 63 IPython.notification_area.init_notification_widgets();
66 64
67 65 IPython.layout_manager.do_resize();
68 66
69 67 $('body').append('<div id="fonttest"><pre><span id="test1">x</span>'+
70 68 '<span id="test2" style="font-weight: bold;">x</span>'+
71 69 '<span id="test3" style="font-style: italic;">x</span></pre></div>')
72 70 var nh = $('#test1').innerHeight();
73 71 var bh = $('#test2').innerHeight();
74 72 var ih = $('#test3').innerHeight();
75 73 if(nh != bh || nh != ih) {
76 74 $('head').append('<style>.CodeMirror span { vertical-align: bottom; }</style>');
77 75 }
78 76 $('#fonttest').remove();
79 77
80 78 if(IPython.read_only){
81 79 // hide various elements from read-only view
82 80 $('div#pager').remove();
83 81 $('div#pager_splitter').remove();
84 82
85 83 // set the notebook name field as not modifiable
86 84 $('#notebook_name').attr('disabled','disabled')
87 85 }
88 86
89 87 IPython.page.show();
90 88
91 89 IPython.layout_manager.do_resize();
92 90 var first_load = function () {
93 91 IPython.layout_manager.do_resize();
94 92 var hash = document.location.hash;
95 93 if (hash) {
96 94 document.location.hash = '';
97 95 document.location.hash = hash;
98 96 }
99 97 IPython.notebook.set_autosave_interval(IPython.notebook.minimum_autosave_interval);
100 98 // only do this once
101 99 $([IPython.events]).off('notebook_loaded.Notebook', first_load);
102 100 };
103 101
104 102 $([IPython.events]).on('notebook_loaded.Notebook', first_load);
105 103 IPython.notebook.load_notebook($('body').data('notebookId'));
106 104
107 105 if (marked) {
108 106 marked.setOptions({
109 107 gfm : true,
110 108 tables: true,
111 109 langPrefix: "language-",
112 110 highlight: function(code, lang) {
113 111 var highlighted;
114 112 try {
115 113 highlighted = hljs.highlight(lang, code, false);
116 114 } catch(err) {
117 115 highlighted = hljs.highlightAuto(code);
118 116 }
119 117 return highlighted.value;
120 118 }
121 119 })
122 120 }
123 121 }
124 122
125 123 );
@@ -1,206 +1,206 b''
1 1 //----------------------------------------------------------------------------
2 2 // Copyright (C) 2011 The IPython Development Team
3 3 //
4 4 // Distributed under the terms of the BSD License. The full license is in
5 5 // the file COPYING, distributed as part of this software.
6 6 //----------------------------------------------------------------------------
7 7
8 8 //============================================================================
9 9 // ToolBar
10 10 //============================================================================
11 11
12 12 var IPython = (function (IPython) {
13 13
14 14 var MainToolBar = function (selector) {
15 15 IPython.ToolBar.apply(this, arguments);
16 16 this.construct();
17 17 this.add_celltype_list();
18 18 this.add_celltoolbar_list();
19 19 this.bind_events();
20 20 };
21 21
22 22 MainToolBar.prototype = new IPython.ToolBar();
23 23
24 24 MainToolBar.prototype.construct = function () {
25 25 this.add_buttons_group([
26 26 {
27 27 id : 'save_b',
28 28 label : 'Save and Checkpoint',
29 icon : 'ui-icon-disk',
29 icon : 'icon-hdd',
30 30 callback : function () {
31 31 IPython.notebook.save_checkpoint();
32 32 }
33 33 }
34 34 ]);
35 35 this.add_buttons_group([
36 36 {
37 37 id : 'cut_b',
38 38 label : 'Cut Cell',
39 icon : 'ui-icon-scissors',
39 icon : 'icon-cut',
40 40 callback : function () {
41 41 IPython.notebook.cut_cell();
42 42 }
43 43 },
44 44 {
45 45 id : 'copy_b',
46 46 label : 'Copy Cell',
47 icon : 'ui-icon-copy',
47 icon : 'icon-copy',
48 48 callback : function () {
49 49 IPython.notebook.copy_cell();
50 50 }
51 51 },
52 52 {
53 53 id : 'paste_b',
54 54 label : 'Paste Cell Below',
55 icon : 'ui-icon-clipboard',
55 icon : 'icon-paste',
56 56 callback : function () {
57 57 IPython.notebook.paste_cell_below();
58 58 }
59 59 }
60 60 ],'cut_copy_paste');
61 61
62 62 this.add_buttons_group([
63 63 {
64 64 id : 'move_up_b',
65 65 label : 'Move Cell Up',
66 icon : 'ui-icon-arrowthick-1-n',
66 icon : 'icon-arrow-up',
67 67 callback : function () {
68 68 IPython.notebook.move_cell_up();
69 69 }
70 70 },
71 71 {
72 72 id : 'move_down_b',
73 73 label : 'Move Cell Down',
74 icon : 'ui-icon-arrowthick-1-s',
74 icon : 'icon-arrow-down',
75 75 callback : function () {
76 76 IPython.notebook.move_cell_down();
77 77 }
78 78 }
79 79 ],'move_up_down');
80 80
81 81 this.add_buttons_group([
82 82 {
83 83 id : 'insert_above_b',
84 84 label : 'Insert Cell Above',
85 icon : 'ui-icon-arrowthickstop-1-n',
85 icon : 'icon-circle-arrow-up',
86 86 callback : function () {
87 87 IPython.notebook.insert_cell_above('code');
88 88 }
89 89 },
90 90 {
91 91 id : 'insert_below_b',
92 92 label : 'Insert Cell Below',
93 icon : 'ui-icon-arrowthickstop-1-s',
93 icon : 'icon-circle-arrow-down',
94 94 callback : function () {
95 95 IPython.notebook.insert_cell_below('code');
96 96 }
97 97 }
98 98 ],'insert_above_below');
99 99
100 100 this.add_buttons_group([
101 101 {
102 102 id : 'run_b',
103 103 label : 'Run Cell',
104 icon : 'ui-icon-play',
104 icon : 'icon-play',
105 105 callback : function () {
106 106 IPython.notebook.execute_selected_cell();
107 107 }
108 108 },
109 109 {
110 110 id : 'interrupt_b',
111 111 label : 'Interrupt',
112 icon : 'ui-icon-stop',
112 icon : 'icon-stop',
113 113 callback : function () {
114 114 IPython.notebook.kernel.interrupt();
115 115 }
116 116 }
117 117 ],'run_int');
118 118 };
119 119
120 120 MainToolBar.prototype.add_celltype_list = function () {
121 121 this.element
122 122 .append($('<select/>')
123 123 .attr('id','cell_type')
124 .addClass('ui-widget-content')
124 // .addClass('ui-widget-content')
125 125 .append($('<option/>').attr('value','code').text('Code'))
126 126 .append($('<option/>').attr('value','markdown').text('Markdown'))
127 127 .append($('<option/>').attr('value','raw').text('Raw Text'))
128 128 .append($('<option/>').attr('value','heading1').text('Heading 1'))
129 129 .append($('<option/>').attr('value','heading2').text('Heading 2'))
130 130 .append($('<option/>').attr('value','heading3').text('Heading 3'))
131 131 .append($('<option/>').attr('value','heading4').text('Heading 4'))
132 132 .append($('<option/>').attr('value','heading5').text('Heading 5'))
133 133 .append($('<option/>').attr('value','heading6').text('Heading 6'))
134 134 );
135 135 };
136 136
137 137
138 138 MainToolBar.prototype.add_celltoolbar_list = function () {
139 var label = $('<label/>').text('Cell Toolbar:');
139 var label = $('<span/>').addClass("navbar-text").text('Cell Toolbar:');
140 140 var select = $('<select/>')
141 .addClass('ui-widget-content')
141 // .addClass('ui-widget-content')
142 142 .attr('id', 'ctb_select')
143 143 .append($('<option/>').attr('value', '').text('None'));
144 144 this.element.append(label).append(select);
145 145 select.change(function() {
146 146 var val = $(this).val()
147 147 if (val =='') {
148 148 IPython.CellToolbar.global_hide();
149 149 } else {
150 150 IPython.CellToolbar.global_show();
151 151 IPython.CellToolbar.activate_preset(val);
152 152 }
153 153 });
154 154 // Setup the currently registered presets.
155 155 var presets = IPython.CellToolbar.list_presets();
156 156 for (var i=0; i<presets.length; i++) {
157 157 var name = presets[i];
158 158 select.append($('<option/>').attr('value', name).text(name));
159 159 }
160 160 // Setup future preset registrations.
161 161 $([IPython.events]).on('preset_added.CellToolbar', function (event, data) {
162 162 var name = data.name;
163 163 select.append($('<option/>').attr('value', name).text(name));
164 164 });
165 165 };
166 166
167 167
168 168 MainToolBar.prototype.bind_events = function () {
169 169 var that = this;
170 170
171 171 this.element.find('#cell_type').change(function () {
172 172 var cell_type = $(this).val();
173 173 if (cell_type === 'code') {
174 174 IPython.notebook.to_code();
175 175 } else if (cell_type === 'markdown') {
176 176 IPython.notebook.to_markdown();
177 177 } else if (cell_type === 'raw') {
178 178 IPython.notebook.to_raw();
179 179 } else if (cell_type === 'heading1') {
180 180 IPython.notebook.to_heading(undefined, 1);
181 181 } else if (cell_type === 'heading2') {
182 182 IPython.notebook.to_heading(undefined, 2);
183 183 } else if (cell_type === 'heading3') {
184 184 IPython.notebook.to_heading(undefined, 3);
185 185 } else if (cell_type === 'heading4') {
186 186 IPython.notebook.to_heading(undefined, 4);
187 187 } else if (cell_type === 'heading5') {
188 188 IPython.notebook.to_heading(undefined, 5);
189 189 } else if (cell_type === 'heading6') {
190 190 IPython.notebook.to_heading(undefined, 6);
191 191 }
192 192 });
193 193 $([IPython.events]).on('selected_cell_type_changed.Notebook', function (event, data) {
194 194 if (data.cell_type === 'heading') {
195 195 that.element.find('#cell_type').val(data.cell_type+data.level);
196 196 } else {
197 197 that.element.find('#cell_type').val(data.cell_type);
198 198 }
199 199 });
200 200 };
201 201
202 202 IPython.MainToolBar = MainToolBar;
203 203
204 204 return IPython;
205 205
206 206 }(IPython));
@@ -1,242 +1,245 b''
1 1 //----------------------------------------------------------------------------
2 2 // Copyright (C) 2008-2012 The IPython Development Team
3 3 //
4 4 // Distributed under the terms of the BSD License. The full license is in
5 5 // the file COPYING, distributed as part of this software.
6 6 //----------------------------------------------------------------------------
7 7
8 8 //============================================================================
9 9 // MathJax utility functions
10 10 //============================================================================
11 11
12 12 IPython.namespace('IPython.mathjaxutils');
13 13
14 14 IPython.mathjaxutils = (function (IPython) {
15 15
16 16 var init = function () {
17 17 if (window.MathJax) {
18 18 // MathJax loaded
19 19 MathJax.Hub.Config({
20 20 tex2jax: {
21 21 inlineMath: [ ['$','$'], ["\\(","\\)"] ],
22 22 displayMath: [ ['$$','$$'], ["\\[","\\]"] ],
23 23 processEscapes: true,
24 24 processEnvironments: true
25 25 },
26 26 displayAlign: 'left', // Change this to 'center' to center equations.
27 27 "HTML-CSS": {
28 28 styles: {'.MathJax_Display': {"margin": 0}}
29 29 }
30 30 });
31 31 MathJax.Hub.Configured();
32 32 } else if (window.mathjax_url != "") {
33 33 // Don't have MathJax, but should. Show dialog.
34 var dialog = $('<div></div>')
34 var message = $('<div/>')
35 35 .append(
36 $("<p></p>").addClass('dialog').html(
36 $("<p/></p>").addClass('dialog').html(
37 37 "Math/LaTeX rendering will be disabled."
38 38 )
39 39 ).append(
40 40 $("<p></p>").addClass('dialog').html(
41 41 "If you have administrative access to the notebook server and" +
42 42 " a working internet connection, you can install a local copy" +
43 43 " of MathJax for offline use with the following command on the server" +
44 44 " at a Python or IPython prompt:"
45 45 )
46 46 ).append(
47 47 $("<pre></pre>").addClass('dialog').html(
48 48 ">>> from IPython.external import mathjax; mathjax.install_mathjax()"
49 49 )
50 50 ).append(
51 51 $("<p></p>").addClass('dialog').html(
52 52 "This will try to install MathJax into the IPython source directory."
53 53 )
54 54 ).append(
55 55 $("<p></p>").addClass('dialog').html(
56 56 "If IPython is installed to a location that requires" +
57 57 " administrative privileges to write, you will need to make this call as" +
58 58 " an administrator, via 'sudo'."
59 59 )
60 60 ).append(
61 61 $("<p></p>").addClass('dialog').html(
62 62 "When you start the notebook server, you can instruct it to disable MathJax support altogether:"
63 63 )
64 64 ).append(
65 65 $("<pre></pre>").addClass('dialog').html(
66 66 "$ ipython notebook --no-mathjax"
67 67 )
68 68 ).append(
69 69 $("<p></p>").addClass('dialog').html(
70 70 "which will prevent this dialog from appearing."
71 71 )
72 ).dialog({
73 title: "Failed to retrieve MathJax from '" + window.mathjax_url + "'",
74 width: "70%",
75 modal: true,
76 })
72 )
73 IPython.dialog.modal({
74 title : "Failed to retrieve MathJax from '" + window.mathjax_url + "'",
75 body : message,
76 buttons : {
77 OK : {class: "btn-danger"}
78 }
79 });
77 80 } else {
78 81 // No MathJax, but none expected. No dialog.
79 82 };
80 83 };
81 84
82 85 // Some magic for deferring mathematical expressions to MathJax
83 86 // by hiding them from the Markdown parser.
84 87 // Some of the code here is adapted with permission from Davide Cervone
85 88 // under the terms of the Apache2 license governing the MathJax project.
86 89 // Other minor modifications are also due to StackExchange and are used with
87 90 // permission.
88 91
89 92 var inline = "$"; // the inline math delimiter
90 93 var blocks, start, end, last, braces; // used in searching for math
91 94 var math; // stores math until pagedown (Markdown parser) is done
92 95
93 96 // MATHSPLIT contains the pattern for math delimiters and special symbols
94 97 // needed for searching for math in the text input.
95 98 var MATHSPLIT = /(\$\$?|\\(?:begin|end)\{[a-z]*\*?\}|\\[\\{}$]|[{}]|(?:\n\s*)+|@@\d+@@)/i;
96 99
97 100 // The math is in blocks i through j, so
98 101 // collect it into one block and clear the others.
99 102 // Replace &, <, and > by named entities.
100 103 // For IE, put <br> at the ends of comments since IE removes \n.
101 104 // Clear the current math positions and store the index of the
102 105 // math, then push the math string onto the storage array.
103 106 // The preProcess function is called on all blocks if it has been passed in
104 107 var process_math = function (i, j, pre_process) {
105 108 var hub = MathJax.Hub;
106 109 var block = blocks.slice(i, j + 1).join("").replace(/&/g, "&amp;") // use HTML entity for &
107 110 .replace(/</g, "&lt;") // use HTML entity for <
108 111 .replace(/>/g, "&gt;") // use HTML entity for >
109 112 ;
110 113 if (hub.Browser.isMSIE) {
111 114 block = block.replace(/(%[^\n]*)\n/g, "$1<br/>\n")
112 115 }
113 116 while (j > i) {
114 117 blocks[j] = "";
115 118 j--;
116 119 }
117 120 blocks[i] = "@@" + math.length + "@@"; // replace the current block text with a unique tag to find later
118 121 if (pre_process)
119 122 block = pre_process(block);
120 123 math.push(block);
121 124 start = end = last = null;
122 125 }
123 126
124 127 // Break up the text into its component parts and search
125 128 // through them for math delimiters, braces, linebreaks, etc.
126 129 // Math delimiters must match and braces must balance.
127 130 // Don't allow math to pass through a double linebreak
128 131 // (which will be a paragraph).
129 132 //
130 133 var remove_math = function (text) {
131 134 if (!window.MathJax) {
132 135 return text;
133 136 }
134 137
135 138 start = end = last = null; // for tracking math delimiters
136 139 math = []; // stores math strings for later
137 140
138 141 // Except for extreme edge cases, this should catch precisely those pieces of the markdown
139 142 // source that will later be turned into code spans. While MathJax will not TeXify code spans,
140 143 // we still have to consider them at this point; the following issue has happened several times:
141 144 //
142 145 // `$foo` and `$bar` are varibales. --> <code>$foo ` and `$bar</code> are variables.
143 146
144 147 var hasCodeSpans = /`/.test(text),
145 148 de_tilde;
146 149 if (hasCodeSpans) {
147 150 text = text.replace(/~/g, "~T").replace(/(^|[^\\])(`+)([^\n]*?[^`\n])\2(?!`)/gm, function (wholematch) {
148 151 return wholematch.replace(/\$/g, "~D");
149 152 });
150 153 de_tilde = function (text) { return text.replace(/~([TD])/g, function (wholematch, character) { return { T: "~", D: "$" }[character]; }) };
151 154 } else {
152 155 de_tilde = function (text) { return text; };
153 156 }
154 157
155 158 blocks = IPython.utils.regex_split(text.replace(/\r\n?/g, "\n"),MATHSPLIT);
156 159
157 160 for (var i = 1, m = blocks.length; i < m; i += 2) {
158 161 var block = blocks[i];
159 162 if (block.charAt(0) === "@") {
160 163 //
161 164 // Things that look like our math markers will get
162 165 // stored and then retrieved along with the math.
163 166 //
164 167 blocks[i] = "@@" + math.length + "@@";
165 168 math.push(block);
166 169 }
167 170 else if (start) {
168 171 //
169 172 // If we are in math, look for the end delimiter,
170 173 // but don't go past double line breaks, and
171 174 // and balance braces within the math.
172 175 //
173 176 if (block === end) {
174 177 if (braces) {
175 178 last = i
176 179 }
177 180 else {
178 181 process_math(start, i, de_tilde)
179 182 }
180 183 }
181 184 else if (block.match(/\n.*\n/)) {
182 185 if (last) {
183 186 i = last;
184 187 process_math(start, i, de_tilde)
185 188 }
186 189 start = end = last = null;
187 190 braces = 0;
188 191 }
189 192 else if (block === "{") {
190 193 braces++
191 194 }
192 195 else if (block === "}" && braces) {
193 196 braces--
194 197 }
195 198 }
196 199 else {
197 200 //
198 201 // Look for math start delimiters and when
199 202 // found, set up the end delimiter.
200 203 //
201 204 if (block === inline || block === "$$") {
202 205 start = i;
203 206 end = block;
204 207 braces = 0;
205 208 }
206 209 else if (block.substr(1, 5) === "begin") {
207 210 start = i;
208 211 end = "\\end" + block.substr(6);
209 212 braces = 0;
210 213 }
211 214 }
212 215 }
213 216 if (last) {
214 217 process_math(start, last, de_tilde)
215 218 }
216 219 return de_tilde(blocks.join(""));
217 220 }
218 221
219 222 //
220 223 // Put back the math strings that were saved,
221 224 // and clear the math array (no need to keep it around).
222 225 //
223 226 var replace_math = function (text) {
224 227 if (!window.MathJax) {
225 228 return text;
226 229 }
227 230
228 231 text = text.replace(/@@(\d+)@@/g, function (match, n) {
229 232 return math[n]
230 233 });
231 234 math = null;
232 235 return text;
233 236 }
234 237
235 238 return {
236 239 init : init,
237 240 process_math : process_math,
238 241 remove_math : remove_math,
239 242 replace_math : replace_math
240 243 };
241 244
242 245 }(IPython)); No newline at end of file
@@ -1,264 +1,263 b''
1 1 //----------------------------------------------------------------------------
2 2 // Copyright (C) 2008-2011 The IPython Development Team
3 3 //
4 4 // Distributed under the terms of the BSD License. The full license is in
5 5 // the file COPYING, distributed as part of this software.
6 6 //----------------------------------------------------------------------------
7 7
8 8 //============================================================================
9 9 // MenuBar
10 10 //============================================================================
11 11
12 12 /**
13 13 * @module IPython
14 14 * @namespace IPython
15 15 * @submodule MenuBar
16 16 */
17 17
18 18
19 19 var IPython = (function (IPython) {
20 20
21 21 /**
22 22 * A MenuBar Class to generate the menubar of IPython noteboko
23 23 * @Class MenuBar
24 24 *
25 25 * @constructor
26 26 *
27 27 *
28 28 * @param selector {string} selector for the menubar element in DOM
29 29 * @param {object} [options]
30 30 * @param [options.baseProjectUrl] {String} String to use for the
31 31 * Base Project url, default would be to inspect
32 32 * $('body').data('baseProjectUrl');
33 33 * does not support change for now is set through this option
34 34 */
35 35 var MenuBar = function (selector, options) {
36 36 var options = options || {};
37 37 if(options.baseProjectUrl!= undefined){
38 38 this._baseProjectUrl = options.baseProjectUrl;
39 39 }
40 40 this.selector = selector;
41 41 if (this.selector !== undefined) {
42 42 this.element = $(selector);
43 43 this.style();
44 44 this.bind_events();
45 45 }
46 46 };
47 47
48 48 MenuBar.prototype.baseProjectUrl = function(){
49 49 return this._baseProjectUrl || $('body').data('baseProjectUrl');
50 50 };
51 51
52 52
53 53 MenuBar.prototype.style = function () {
54 54 this.element.addClass('border-box-sizing');
55 $('ul#menus').menubar({
56 select : function (event, ui) {
55 this.element.find("li").click(function (event, ui) {
57 56 // The selected cell loses focus when the menu is entered, so we
58 57 // re-select it upon selection.
59 58 var i = IPython.notebook.get_selected_index();
60 59 IPython.notebook.select(i);
61 60 }
62 });
63 this.element.find("#restore_checkpoint").find("ul").find("li").hide();
61 );
64 62 };
65 63
66 64
67 65 MenuBar.prototype.bind_events = function () {
68 66 // File
69 67 var that = this;
70 68 this.element.find('#new_notebook').click(function () {
71 69 window.open(that.baseProjectUrl()+'new');
72 70 });
73 71 this.element.find('#open_notebook').click(function () {
74 72 window.open(that.baseProjectUrl());
75 73 });
76 74 this.element.find('#rename_notebook').click(function () {
77 75 IPython.save_widget.rename_notebook();
78 76 });
79 77 this.element.find('#copy_notebook').click(function () {
80 78 var notebook_id = IPython.notebook.get_notebook_id();
81 79 var url = that.baseProjectUrl() + notebook_id + '/copy';
82 80 window.open(url,'_blank');
83 81 return false;
84 82 });
85 83 this.element.find('#save_checkpoint').click(function () {
86 84 IPython.notebook.save_checkpoint();
87 85 });
88 86 this.element.find('#restore_checkpoint').click(function () {
89 87 });
90 88 this.element.find('#download_ipynb').click(function () {
91 89 var notebook_id = IPython.notebook.get_notebook_id();
92 90 var url = that.baseProjectUrl() + 'notebooks/' +
93 91 notebook_id + '?format=json';
94 92 window.location.assign(url);
95 93 });
96 94 this.element.find('#download_py').click(function () {
97 95 var notebook_id = IPython.notebook.get_notebook_id();
98 96 var url = that.baseProjectUrl() + 'notebooks/' +
99 97 notebook_id + '?format=py';
100 98 window.location.assign(url);
101 99 });
102 100 this.element.find('#kill_and_exit').click(function () {
103 101 IPython.notebook.kernel.kill();
104 102 setTimeout(function(){window.close();}, 200);
105 103 });
106 104 // Edit
107 105 this.element.find('#cut_cell').click(function () {
108 106 IPython.notebook.cut_cell();
109 107 });
110 108 this.element.find('#copy_cell').click(function () {
111 109 IPython.notebook.copy_cell();
112 110 });
113 111 this.element.find('#delete_cell').click(function () {
114 112 IPython.notebook.delete_cell();
115 113 });
116 114 this.element.find('#undelete_cell').click(function () {
117 115 IPython.notebook.undelete();
118 116 });
119 117 this.element.find('#split_cell').click(function () {
120 118 IPython.notebook.split_cell();
121 119 });
122 120 this.element.find('#merge_cell_above').click(function () {
123 121 IPython.notebook.merge_cell_above();
124 122 });
125 123 this.element.find('#merge_cell_below').click(function () {
126 124 IPython.notebook.merge_cell_below();
127 125 });
128 126 this.element.find('#move_cell_up').click(function () {
129 127 IPython.notebook.move_cell_up();
130 128 });
131 129 this.element.find('#move_cell_down').click(function () {
132 130 IPython.notebook.move_cell_down();
133 131 });
134 132 this.element.find('#select_previous').click(function () {
135 133 IPython.notebook.select_prev();
136 134 });
137 135 this.element.find('#select_next').click(function () {
138 136 IPython.notebook.select_next();
139 137 });
140 138 // View
141 139 this.element.find('#toggle_header').click(function () {
142 140 $('div#header').toggle();
143 141 IPython.layout_manager.do_resize();
144 142 });
145 143 this.element.find('#toggle_toolbar').click(function () {
146 IPython.toolbar.toggle();
144 $('div#maintoolbar').toggle();
145 IPython.layout_manager.do_resize();
147 146 });
148 147 // Insert
149 148 this.element.find('#insert_cell_above').click(function () {
150 149 IPython.notebook.insert_cell_above('code');
151 150 });
152 151 this.element.find('#insert_cell_below').click(function () {
153 152 IPython.notebook.insert_cell_below('code');
154 153 });
155 154 // Cell
156 155 this.element.find('#run_cell').click(function () {
157 156 IPython.notebook.execute_selected_cell();
158 157 });
159 158 this.element.find('#run_cell_in_place').click(function () {
160 159 IPython.notebook.execute_selected_cell({terminal:true});
161 160 });
162 161 this.element.find('#run_all_cells').click(function () {
163 162 IPython.notebook.execute_all_cells();
164 163 }).attr('title', 'Run all cells in the notebook');
165 164 this.element.find('#run_all_cells_above').click(function () {
166 165 IPython.notebook.execute_cells_above();
167 166 }).attr('title', 'Run all cells above (but not including) this cell');
168 167 this.element.find('#run_all_cells_below').click(function () {
169 168 IPython.notebook.execute_cells_below();
170 169 }).attr('title', 'Run this cell and all cells below it');
171 170 this.element.find('#to_code').click(function () {
172 171 IPython.notebook.to_code();
173 172 });
174 173 this.element.find('#to_markdown').click(function () {
175 174 IPython.notebook.to_markdown();
176 175 });
177 176 this.element.find('#to_raw').click(function () {
178 177 IPython.notebook.to_raw();
179 178 });
180 179 this.element.find('#to_heading1').click(function () {
181 180 IPython.notebook.to_heading(undefined, 1);
182 181 });
183 182 this.element.find('#to_heading2').click(function () {
184 183 IPython.notebook.to_heading(undefined, 2);
185 184 });
186 185 this.element.find('#to_heading3').click(function () {
187 186 IPython.notebook.to_heading(undefined, 3);
188 187 });
189 188 this.element.find('#to_heading4').click(function () {
190 189 IPython.notebook.to_heading(undefined, 4);
191 190 });
192 191 this.element.find('#to_heading5').click(function () {
193 192 IPython.notebook.to_heading(undefined, 5);
194 193 });
195 194 this.element.find('#to_heading6').click(function () {
196 195 IPython.notebook.to_heading(undefined, 6);
197 196 });
198 197 this.element.find('#toggle_output').click(function () {
199 198 IPython.notebook.toggle_output();
200 199 });
201 200 this.element.find('#collapse_all_output').click(function () {
202 201 IPython.notebook.collapse_all_output();
203 202 });
204 203 this.element.find('#scroll_all_output').click(function () {
205 204 IPython.notebook.scroll_all_output();
206 205 });
207 206 this.element.find('#expand_all_output').click(function () {
208 207 IPython.notebook.expand_all_output();
209 208 });
210 209 this.element.find('#clear_all_output').click(function () {
211 210 IPython.notebook.clear_all_output();
212 211 });
213 212 // Kernel
214 213 this.element.find('#int_kernel').click(function () {
215 214 IPython.notebook.kernel.interrupt();
216 215 });
217 216 this.element.find('#restart_kernel').click(function () {
218 217 IPython.notebook.restart_kernel();
219 218 });
220 219 // Help
221 220 this.element.find('#keyboard_shortcuts').click(function () {
222 221 IPython.quick_help.show_keyboard_shortcuts();
223 222 });
224 223
225 224 this.update_restore_checkpoint(null);
226 225
227 226 $([IPython.events]).on('checkpoints_listed.Notebook', function (event, data) {
228 227 that.update_restore_checkpoint(data);
229 228 });
230 229
231 230 $([IPython.events]).on('checkpoint_created.Notebook', function (event, data) {
232 231 that.update_restore_checkpoint([data]);
233 232 });
234 233 };
235 234
236 235 MenuBar.prototype.update_restore_checkpoint = function(checkpoints) {
237 236 if (! checkpoints) {
238 237 checkpoints = [];
239 238 };
240 239 this.element.find("#restore_checkpoint").find("ul").find("li").each(function(i) {
241 240 var li = $(this);
242 241 var a = li.find("a");
243 242 a.off("click");
244 243 if (checkpoints.length <= i) {
245 244 li.hide();
246 245 return;
247 246 } else {
248 247 li.show();
249 248 };
250 249 var checkpoint = checkpoints[i];
251 250 var d = new Date(checkpoint.last_modified);
252 251 li.find('a').text(
253 252 d.format("mmm dd HH:MM:ss")
254 253 ).click(function () {
255 254 IPython.notebook.restore_checkpoint_dialog(checkpoint);
256 255 });
257 256 });
258 257 };
259 258
260 259 IPython.MenuBar = MenuBar;
261 260
262 261 return IPython;
263 262
264 263 }(IPython));
@@ -1,2046 +1,2010 b''
1 1 //----------------------------------------------------------------------------
2 2 // Copyright (C) 2008-2011 The IPython Development Team
3 3 //
4 4 // Distributed under the terms of the BSD License. The full license is in
5 5 // the file COPYING, distributed as part of this software.
6 6 //----------------------------------------------------------------------------
7 7
8 8 //============================================================================
9 9 // Notebook
10 10 //============================================================================
11 11
12 12 var IPython = (function (IPython) {
13 13
14 14 var utils = IPython.utils;
15 15 var key = IPython.utils.keycodes;
16 16
17 17 /**
18 18 * A notebook contains and manages cells.
19 19 *
20 20 * @class Notebook
21 21 * @constructor
22 22 * @param {String} selector A jQuery selector for the notebook's DOM element
23 23 * @param {Object} [options] A config object
24 24 */
25 25 var Notebook = function (selector, options) {
26 26 var options = options || {};
27 27 this._baseProjectUrl = options.baseProjectUrl;
28 28 this.read_only = options.read_only || IPython.read_only;
29 29
30 30 this.element = $(selector);
31 31 this.element.scroll();
32 32 this.element.data("notebook", this);
33 33 this.next_prompt_number = 1;
34 34 this.kernel = null;
35 35 this.clipboard = null;
36 36 this.undelete_backup = null;
37 37 this.undelete_index = null;
38 38 this.undelete_below = false;
39 39 this.paste_enabled = false;
40 40 this.set_dirty(false);
41 41 this.metadata = {};
42 42 this._checkpoint_after_save = false;
43 43 this.last_checkpoint = null;
44 44 this.autosave_interval = 0;
45 45 this.autosave_timer = null;
46 46 // autosave *at most* every two minutes
47 47 this.minimum_autosave_interval = 120000;
48 48 // single worksheet for now
49 49 this.worksheet_metadata = {};
50 50 this.control_key_active = false;
51 51 this.notebook_id = null;
52 52 this.notebook_name = null;
53 53 this.notebook_name_blacklist_re = /[\/\\:]/;
54 54 this.nbformat = 3 // Increment this when changing the nbformat
55 55 this.nbformat_minor = 0 // Increment this when changing the nbformat
56 56 this.style();
57 57 this.create_elements();
58 58 this.bind_events();
59 59 };
60 60
61 61 /**
62 62 * Tweak the notebook's CSS style.
63 63 *
64 64 * @method style
65 65 */
66 66 Notebook.prototype.style = function () {
67 67 $('div#notebook').addClass('border-box-sizing');
68 68 };
69 69
70 70 /**
71 71 * Get the root URL of the notebook server.
72 72 *
73 73 * @method baseProjectUrl
74 74 * @return {String} The base project URL
75 75 */
76 76 Notebook.prototype.baseProjectUrl = function(){
77 77 return this._baseProjectUrl || $('body').data('baseProjectUrl');
78 78 };
79 79
80 80 /**
81 81 * Create an HTML and CSS representation of the notebook.
82 82 *
83 83 * @method create_elements
84 84 */
85 85 Notebook.prototype.create_elements = function () {
86 86 // We add this end_space div to the end of the notebook div to:
87 87 // i) provide a margin between the last cell and the end of the notebook
88 88 // ii) to prevent the div from scrolling up when the last cell is being
89 89 // edited, but is too low on the page, which browsers will do automatically.
90 90 var that = this;
91 var end_space = $('<div/>').addClass('end_space').height("30%");
91 this.container = $("<div/>").addClass("container").attr("id", "notebook-container");
92 var end_space = $('<div/>').addClass('end_space');
92 93 end_space.dblclick(function (e) {
93 94 if (that.read_only) return;
94 95 var ncells = that.ncells();
95 96 that.insert_cell_below('code',ncells-1);
96 97 });
97 this.element.append(end_space);
98 this.element.append(this.container);
99 this.container.append(end_space);
98 100 $('div#notebook').addClass('border-box-sizing');
99 101 };
100 102
101 103 /**
102 104 * Bind JavaScript events: key presses and custom IPython events.
103 105 *
104 106 * @method bind_events
105 107 */
106 108 Notebook.prototype.bind_events = function () {
107 109 var that = this;
108 110
109 111 $([IPython.events]).on('set_next_input.Notebook', function (event, data) {
110 112 var index = that.find_cell_index(data.cell);
111 113 var new_cell = that.insert_cell_below('code',index);
112 114 new_cell.set_text(data.text);
113 115 that.dirty = true;
114 116 });
115 117
116 118 $([IPython.events]).on('set_dirty.Notebook', function (event, data) {
117 119 that.dirty = data.value;
118 120 });
119 121
120 122 $([IPython.events]).on('select.Cell', function (event, data) {
121 123 var index = that.find_cell_index(data.cell);
122 124 that.select(index);
123 125 });
124 126
125 127
126 128 $(document).keydown(function (event) {
127 129 // console.log(event);
128 130 if (that.read_only) return true;
129 131
130 132 // Save (CTRL+S) or (AppleKey+S)
131 133 //metaKey = applekey on mac
132 134 if ((event.ctrlKey || event.metaKey) && event.keyCode==83) {
133 135 that.save_checkpoint();
134 136 event.preventDefault();
135 137 return false;
136 138 } else if (event.which === key.ESC) {
137 139 // Intercept escape at highest level to avoid closing
138 140 // websocket connection with firefox
139 141 IPython.pager.collapse();
140 142 event.preventDefault();
141 143 } else if (event.which === key.SHIFT) {
142 144 // ignore shift keydown
143 145 return true;
144 146 }
145 147 if (event.which === key.UPARROW && !event.shiftKey) {
146 148 var cell = that.get_selected_cell();
147 149 if (cell && cell.at_top()) {
148 150 event.preventDefault();
149 151 that.select_prev();
150 152 };
151 153 } else if (event.which === key.DOWNARROW && !event.shiftKey) {
152 154 var cell = that.get_selected_cell();
153 155 if (cell && cell.at_bottom()) {
154 156 event.preventDefault();
155 157 that.select_next();
156 158 };
157 159 } else if (event.which === key.ENTER && event.shiftKey) {
158 160 that.execute_selected_cell();
159 161 return false;
160 162 } else if (event.which === key.ENTER && event.altKey) {
161 163 // Execute code cell, and insert new in place
162 164 that.execute_selected_cell();
163 165 // Only insert a new cell, if we ended up in an already populated cell
164 166 if (/\S/.test(that.get_selected_cell().get_text()) == true) {
165 167 that.insert_cell_above('code');
166 168 }
167 169 return false;
168 170 } else if (event.which === key.ENTER && event.ctrlKey) {
169 171 that.execute_selected_cell({terminal:true});
170 172 return false;
171 173 } else if (event.which === 77 && event.ctrlKey && that.control_key_active == false) {
172 174 that.control_key_active = true;
173 175 return false;
174 176 } else if (event.which === 88 && that.control_key_active) {
175 177 // Cut selected cell = x
176 178 that.cut_cell();
177 179 that.control_key_active = false;
178 180 return false;
179 181 } else if (event.which === 67 && that.control_key_active) {
180 182 // Copy selected cell = c
181 183 that.copy_cell();
182 184 that.control_key_active = false;
183 185 return false;
184 186 } else if (event.which === 86 && that.control_key_active) {
185 187 // Paste below selected cell = v
186 188 that.paste_cell_below();
187 189 that.control_key_active = false;
188 190 return false;
189 191 } else if (event.which === 68 && that.control_key_active) {
190 192 // Delete selected cell = d
191 193 that.delete_cell();
192 194 that.control_key_active = false;
193 195 return false;
194 196 } else if (event.which === 65 && that.control_key_active) {
195 197 // Insert code cell above selected = a
196 198 that.insert_cell_above('code');
197 199 that.control_key_active = false;
198 200 return false;
199 201 } else if (event.which === 66 && that.control_key_active) {
200 202 // Insert code cell below selected = b
201 203 that.insert_cell_below('code');
202 204 that.control_key_active = false;
203 205 return false;
204 206 } else if (event.which === 89 && that.control_key_active) {
205 207 // To code = y
206 208 that.to_code();
207 209 that.control_key_active = false;
208 210 return false;
209 211 } else if (event.which === 77 && that.control_key_active) {
210 212 // To markdown = m
211 213 that.to_markdown();
212 214 that.control_key_active = false;
213 215 return false;
214 216 } else if (event.which === 84 && that.control_key_active) {
215 217 // To Raw = t
216 218 that.to_raw();
217 219 that.control_key_active = false;
218 220 return false;
219 221 } else if (event.which === 49 && that.control_key_active) {
220 222 // To Heading 1 = 1
221 223 that.to_heading(undefined, 1);
222 224 that.control_key_active = false;
223 225 return false;
224 226 } else if (event.which === 50 && that.control_key_active) {
225 227 // To Heading 2 = 2
226 228 that.to_heading(undefined, 2);
227 229 that.control_key_active = false;
228 230 return false;
229 231 } else if (event.which === 51 && that.control_key_active) {
230 232 // To Heading 3 = 3
231 233 that.to_heading(undefined, 3);
232 234 that.control_key_active = false;
233 235 return false;
234 236 } else if (event.which === 52 && that.control_key_active) {
235 237 // To Heading 4 = 4
236 238 that.to_heading(undefined, 4);
237 239 that.control_key_active = false;
238 240 return false;
239 241 } else if (event.which === 53 && that.control_key_active) {
240 242 // To Heading 5 = 5
241 243 that.to_heading(undefined, 5);
242 244 that.control_key_active = false;
243 245 return false;
244 246 } else if (event.which === 54 && that.control_key_active) {
245 247 // To Heading 6 = 6
246 248 that.to_heading(undefined, 6);
247 249 that.control_key_active = false;
248 250 return false;
249 251 } else if (event.which === 79 && that.control_key_active) {
250 252 // Toggle output = o
251 253 if (event.shiftKey){
252 254 that.toggle_output_scroll();
253 255 } else {
254 256 that.toggle_output();
255 257 }
256 258 that.control_key_active = false;
257 259 return false;
258 260 } else if (event.which === 83 && that.control_key_active) {
259 261 // Save notebook = s
260 262 that.save_checkpoint();
261 263 that.control_key_active = false;
262 264 return false;
263 265 } else if (event.which === 74 && that.control_key_active) {
264 266 // Move cell down = j
265 267 that.move_cell_down();
266 268 that.control_key_active = false;
267 269 return false;
268 270 } else if (event.which === 75 && that.control_key_active) {
269 271 // Move cell up = k
270 272 that.move_cell_up();
271 273 that.control_key_active = false;
272 274 return false;
273 275 } else if (event.which === 80 && that.control_key_active) {
274 276 // Select previous = p
275 277 that.select_prev();
276 278 that.control_key_active = false;
277 279 return false;
278 280 } else if (event.which === 78 && that.control_key_active) {
279 281 // Select next = n
280 282 that.select_next();
281 283 that.control_key_active = false;
282 284 return false;
283 285 } else if (event.which === 76 && that.control_key_active) {
284 286 // Toggle line numbers = l
285 287 that.cell_toggle_line_numbers();
286 288 that.control_key_active = false;
287 289 return false;
288 290 } else if (event.which === 73 && that.control_key_active) {
289 291 // Interrupt kernel = i
290 292 that.kernel.interrupt();
291 293 that.control_key_active = false;
292 294 return false;
293 295 } else if (event.which === 190 && that.control_key_active) {
294 296 // Restart kernel = . # matches qt console
295 297 that.restart_kernel();
296 298 that.control_key_active = false;
297 299 return false;
298 300 } else if (event.which === 72 && that.control_key_active) {
299 301 // Show keyboard shortcuts = h
300 302 IPython.quick_help.show_keyboard_shortcuts();
301 303 that.control_key_active = false;
302 304 return false;
303 305 } else if (event.which === 90 && that.control_key_active) {
304 306 // Undo last cell delete = z
305 307 that.undelete();
306 308 that.control_key_active = false;
307 309 return false;
308 310 } else if (that.control_key_active) {
309 311 that.control_key_active = false;
310 312 return true;
311 313 }
312 314 return true;
313 315 });
314 316
315 317 var collapse_time = function(time){
316 318 var app_height = $('#ipython-main-app').height(); // content height
317 319 var splitter_height = $('div#pager_splitter').outerHeight(true);
318 320 var new_height = app_height - splitter_height;
319 321 that.element.animate({height : new_height + 'px'}, time);
320 322 }
321 323
322 324 this.element.bind('collapse_pager', function (event,extrap) {
323 325 var time = (extrap != undefined) ? ((extrap.duration != undefined ) ? extrap.duration : 'fast') : 'fast';
324 326 collapse_time(time);
325 327 });
326 328
327 329 var expand_time = function(time) {
328 330 var app_height = $('#ipython-main-app').height(); // content height
329 331 var splitter_height = $('div#pager_splitter').outerHeight(true);
330 332 var pager_height = $('div#pager').outerHeight(true);
331 333 var new_height = app_height - pager_height - splitter_height;
332 334 that.element.animate({height : new_height + 'px'}, time);
333 335 }
334 336
335 337 this.element.bind('expand_pager', function (event, extrap) {
336 338 var time = (extrap != undefined) ? ((extrap.duration != undefined ) ? extrap.duration : 'fast') : 'fast';
337 339 expand_time(time);
338 340 });
339 341
340 342 $(window).bind('beforeunload', function () {
341 343 // TODO: Make killing the kernel configurable.
342 344 var kill_kernel = false;
343 345 if (kill_kernel) {
344 346 that.kernel.kill();
345 347 }
346 348 // if we are autosaving, trigger an autosave on nav-away
347 349 if (that.dirty && that.autosave_interval && ! that.read_only) {
348 350 that.save_notebook();
349 351 };
350 352 // Null is the *only* return value that will make the browser not
351 353 // pop up the "don't leave" dialog.
352 354 return null;
353 355 });
354 356 };
355 357
356 358 /**
357 359 * Set the dirty flag, and trigger the set_dirty.Notebook event
358 360 *
359 361 * @method set_dirty
360 362 */
361 363 Notebook.prototype.set_dirty = function (value) {
362 364 if (value === undefined) {
363 365 value = true;
364 366 }
365 367 if (this.dirty == value) {
366 368 return;
367 369 }
368 370 $([IPython.events]).trigger('set_dirty.Notebook', {value: value});
369 371 };
370 372
371 373 /**
372 374 * Scroll the top of the page to a given cell.
373 375 *
374 376 * @method scroll_to_cell
375 377 * @param {Number} cell_number An index of the cell to view
376 378 * @param {Number} time Animation time in milliseconds
377 379 * @return {Number} Pixel offset from the top of the container
378 380 */
379 381 Notebook.prototype.scroll_to_cell = function (cell_number, time) {
380 382 var cells = this.get_cells();
381 383 var time = time || 0;
382 384 cell_number = Math.min(cells.length-1,cell_number);
383 385 cell_number = Math.max(0 ,cell_number);
384 386 var scroll_value = cells[cell_number].element.position().top-cells[0].element.position().top ;
385 387 this.element.animate({scrollTop:scroll_value}, time);
386 388 return scroll_value;
387 389 };
388 390
389 391 /**
390 392 * Scroll to the bottom of the page.
391 393 *
392 394 * @method scroll_to_bottom
393 395 */
394 396 Notebook.prototype.scroll_to_bottom = function () {
395 397 this.element.animate({scrollTop:this.element.get(0).scrollHeight}, 0);
396 398 };
397 399
398 400 /**
399 401 * Scroll to the top of the page.
400 402 *
401 403 * @method scroll_to_top
402 404 */
403 405 Notebook.prototype.scroll_to_top = function () {
404 406 this.element.animate({scrollTop:0}, 0);
405 407 };
406 408
407 409
408 410 // Cell indexing, retrieval, etc.
409 411
410 412 /**
411 413 * Get all cell elements in the notebook.
412 414 *
413 415 * @method get_cell_elements
414 416 * @return {jQuery} A selector of all cell elements
415 417 */
416 418 Notebook.prototype.get_cell_elements = function () {
417 return this.element.children("div.cell");
419 return this.container.children("div.cell");
418 420 };
419 421
420 422 /**
421 423 * Get a particular cell element.
422 424 *
423 425 * @method get_cell_element
424 426 * @param {Number} index An index of a cell to select
425 427 * @return {jQuery} A selector of the given cell.
426 428 */
427 429 Notebook.prototype.get_cell_element = function (index) {
428 430 var result = null;
429 431 var e = this.get_cell_elements().eq(index);
430 432 if (e.length !== 0) {
431 433 result = e;
432 434 }
433 435 return result;
434 436 };
435 437
436 438 /**
437 439 * Count the cells in this notebook.
438 440 *
439 441 * @method ncells
440 442 * @return {Number} The number of cells in this notebook
441 443 */
442 444 Notebook.prototype.ncells = function () {
443 445 return this.get_cell_elements().length;
444 446 };
445 447
446 448 /**
447 449 * Get all Cell objects in this notebook.
448 450 *
449 451 * @method get_cells
450 452 * @return {Array} This notebook's Cell objects
451 453 */
452 454 // TODO: we are often calling cells as cells()[i], which we should optimize
453 455 // to cells(i) or a new method.
454 456 Notebook.prototype.get_cells = function () {
455 457 return this.get_cell_elements().toArray().map(function (e) {
456 458 return $(e).data("cell");
457 459 });
458 460 };
459 461
460 462 /**
461 463 * Get a Cell object from this notebook.
462 464 *
463 465 * @method get_cell
464 466 * @param {Number} index An index of a cell to retrieve
465 467 * @return {Cell} A particular cell
466 468 */
467 469 Notebook.prototype.get_cell = function (index) {
468 470 var result = null;
469 471 var ce = this.get_cell_element(index);
470 472 if (ce !== null) {
471 473 result = ce.data('cell');
472 474 }
473 475 return result;
474 476 }
475 477
476 478 /**
477 479 * Get the cell below a given cell.
478 480 *
479 481 * @method get_next_cell
480 482 * @param {Cell} cell The provided cell
481 483 * @return {Cell} The next cell
482 484 */
483 485 Notebook.prototype.get_next_cell = function (cell) {
484 486 var result = null;
485 487 var index = this.find_cell_index(cell);
486 488 if (this.is_valid_cell_index(index+1)) {
487 489 result = this.get_cell(index+1);
488 490 }
489 491 return result;
490 492 }
491 493
492 494 /**
493 495 * Get the cell above a given cell.
494 496 *
495 497 * @method get_prev_cell
496 498 * @param {Cell} cell The provided cell
497 499 * @return {Cell} The previous cell
498 500 */
499 501 Notebook.prototype.get_prev_cell = function (cell) {
500 502 // TODO: off-by-one
501 503 // nb.get_prev_cell(nb.get_cell(1)) is null
502 504 var result = null;
503 505 var index = this.find_cell_index(cell);
504 506 if (index !== null && index > 1) {
505 507 result = this.get_cell(index-1);
506 508 }
507 509 return result;
508 510 }
509 511
510 512 /**
511 513 * Get the numeric index of a given cell.
512 514 *
513 515 * @method find_cell_index
514 516 * @param {Cell} cell The provided cell
515 517 * @return {Number} The cell's numeric index
516 518 */
517 519 Notebook.prototype.find_cell_index = function (cell) {
518 520 var result = null;
519 521 this.get_cell_elements().filter(function (index) {
520 522 if ($(this).data("cell") === cell) {
521 523 result = index;
522 524 };
523 525 });
524 526 return result;
525 527 };
526 528
527 529 /**
528 530 * Get a given index , or the selected index if none is provided.
529 531 *
530 532 * @method index_or_selected
531 533 * @param {Number} index A cell's index
532 534 * @return {Number} The given index, or selected index if none is provided.
533 535 */
534 536 Notebook.prototype.index_or_selected = function (index) {
535 537 var i;
536 538 if (index === undefined || index === null) {
537 539 i = this.get_selected_index();
538 540 if (i === null) {
539 541 i = 0;
540 542 }
541 543 } else {
542 544 i = index;
543 545 }
544 546 return i;
545 547 };
546 548
547 549 /**
548 550 * Get the currently selected cell.
549 551 * @method get_selected_cell
550 552 * @return {Cell} The selected cell
551 553 */
552 554 Notebook.prototype.get_selected_cell = function () {
553 555 var index = this.get_selected_index();
554 556 return this.get_cell(index);
555 557 };
556 558
557 559 /**
558 560 * Check whether a cell index is valid.
559 561 *
560 562 * @method is_valid_cell_index
561 563 * @param {Number} index A cell index
562 564 * @return True if the index is valid, false otherwise
563 565 */
564 566 Notebook.prototype.is_valid_cell_index = function (index) {
565 567 if (index !== null && index >= 0 && index < this.ncells()) {
566 568 return true;
567 569 } else {
568 570 return false;
569 571 };
570 572 }
571 573
572 574 /**
573 575 * Get the index of the currently selected cell.
574 576
575 577 * @method get_selected_index
576 578 * @return {Number} The selected cell's numeric index
577 579 */
578 580 Notebook.prototype.get_selected_index = function () {
579 581 var result = null;
580 582 this.get_cell_elements().filter(function (index) {
581 583 if ($(this).data("cell").selected === true) {
582 584 result = index;
583 585 };
584 586 });
585 587 return result;
586 588 };
587 589
588 590
589 591 // Cell selection.
590 592
591 593 /**
592 594 * Programmatically select a cell.
593 595 *
594 596 * @method select
595 597 * @param {Number} index A cell's index
596 598 * @return {Notebook} This notebook
597 599 */
598 600 Notebook.prototype.select = function (index) {
599 601 if (this.is_valid_cell_index(index)) {
600 602 var sindex = this.get_selected_index()
601 603 if (sindex !== null && index !== sindex) {
602 604 this.get_cell(sindex).unselect();
603 605 };
604 606 var cell = this.get_cell(index);
605 607 cell.select();
606 608 if (cell.cell_type === 'heading') {
607 609 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
608 610 {'cell_type':cell.cell_type,level:cell.level}
609 611 );
610 612 } else {
611 613 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
612 614 {'cell_type':cell.cell_type}
613 615 );
614 616 };
615 617 };
616 618 return this;
617 619 };
618 620
619 621 /**
620 622 * Programmatically select the next cell.
621 623 *
622 624 * @method select_next
623 625 * @return {Notebook} This notebook
624 626 */
625 627 Notebook.prototype.select_next = function () {
626 628 var index = this.get_selected_index();
627 629 this.select(index+1);
628 630 return this;
629 631 };
630 632
631 633 /**
632 634 * Programmatically select the previous cell.
633 635 *
634 636 * @method select_prev
635 637 * @return {Notebook} This notebook
636 638 */
637 639 Notebook.prototype.select_prev = function () {
638 640 var index = this.get_selected_index();
639 641 this.select(index-1);
640 642 return this;
641 643 };
642 644
643 645
644 646 // Cell movement
645 647
646 648 /**
647 649 * Move given (or selected) cell up and select it.
648 650 *
649 651 * @method move_cell_up
650 652 * @param [index] {integer} cell index
651 653 * @return {Notebook} This notebook
652 654 **/
653 655 Notebook.prototype.move_cell_up = function (index) {
654 656 var i = this.index_or_selected(index);
655 657 if (this.is_valid_cell_index(i) && i > 0) {
656 658 var pivot = this.get_cell_element(i-1);
657 659 var tomove = this.get_cell_element(i);
658 660 if (pivot !== null && tomove !== null) {
659 661 tomove.detach();
660 662 pivot.before(tomove);
661 663 this.select(i-1);
662 664 };
663 665 this.set_dirty(true);
664 666 };
665 667 return this;
666 668 };
667 669
668 670
669 671 /**
670 672 * Move given (or selected) cell down and select it
671 673 *
672 674 * @method move_cell_down
673 675 * @param [index] {integer} cell index
674 676 * @return {Notebook} This notebook
675 677 **/
676 678 Notebook.prototype.move_cell_down = function (index) {
677 679 var i = this.index_or_selected(index);
678 680 if ( this.is_valid_cell_index(i) && this.is_valid_cell_index(i+1)) {
679 681 var pivot = this.get_cell_element(i+1);
680 682 var tomove = this.get_cell_element(i);
681 683 if (pivot !== null && tomove !== null) {
682 684 tomove.detach();
683 685 pivot.after(tomove);
684 686 this.select(i+1);
685 687 };
686 688 };
687 689 this.set_dirty();
688 690 return this;
689 691 };
690 692
691 693
692 694 // Insertion, deletion.
693 695
694 696 /**
695 697 * Delete a cell from the notebook.
696 698 *
697 699 * @method delete_cell
698 700 * @param [index] A cell's numeric index
699 701 * @return {Notebook} This notebook
700 702 */
701 703 Notebook.prototype.delete_cell = function (index) {
702 704 var i = this.index_or_selected(index);
703 705 var cell = this.get_selected_cell();
704 706 this.undelete_backup = cell.toJSON();
705 707 $('#undelete_cell').removeClass('ui-state-disabled');
706 708 if (this.is_valid_cell_index(i)) {
707 709 var ce = this.get_cell_element(i);
708 710 ce.remove();
709 711 if (i === (this.ncells())) {
710 712 this.select(i-1);
711 713 this.undelete_index = i - 1;
712 714 this.undelete_below = true;
713 715 } else {
714 716 this.select(i);
715 717 this.undelete_index = i;
716 718 this.undelete_below = false;
717 719 };
718 720 this.set_dirty(true);
719 721 };
720 722 return this;
721 723 };
722 724
723 725 /**
724 726 * Insert a cell so that after insertion the cell is at given index.
725 727 *
726 728 * Similar to insert_above, but index parameter is mandatory
727 729 *
728 730 * Index will be brought back into the accissible range [0,n]
729 731 *
730 732 * @method insert_cell_at_index
731 733 * @param type {string} in ['code','markdown','heading']
732 734 * @param [index] {int} a valid index where to inser cell
733 735 *
734 736 * @return cell {cell|null} created cell or null
735 737 **/
736 738 Notebook.prototype.insert_cell_at_index = function(type, index){
737 739
738 740 var ncells = this.ncells();
739 741 var index = Math.min(index,ncells);
740 742 index = Math.max(index,0);
741 743 var cell = null;
742 744
743 745 if (ncells === 0 || this.is_valid_cell_index(index) || index === ncells) {
744 746 if (type === 'code') {
745 747 cell = new IPython.CodeCell(this.kernel);
746 748 cell.set_input_prompt();
747 749 } else if (type === 'markdown') {
748 750 cell = new IPython.MarkdownCell();
749 751 } else if (type === 'raw') {
750 752 cell = new IPython.RawCell();
751 753 } else if (type === 'heading') {
752 754 cell = new IPython.HeadingCell();
753 755 }
754 756
755 757 if(this._insert_element_at_index(cell.element,index)){
756 758 cell.render();
757 759 this.select(this.find_cell_index(cell));
758 760 this.set_dirty(true);
759 761 }
760 762 }
761 763 return cell;
762 764
763 765 };
764 766
765 767 /**
766 768 * Insert an element at given cell index.
767 769 *
768 770 * @method _insert_element_at_index
769 771 * @param element {dom element} a cell element
770 772 * @param [index] {int} a valid index where to inser cell
771 773 * @private
772 774 *
773 775 * return true if everything whent fine.
774 776 **/
775 777 Notebook.prototype._insert_element_at_index = function(element, index){
776 778 if (element === undefined){
777 779 return false;
778 780 }
779 781
780 782 var ncells = this.ncells();
781 783
782 784 if (ncells === 0) {
783 785 // special case append if empty
784 786 this.element.find('div.end_space').before(element);
785 787 } else if ( ncells === index ) {
786 788 // special case append it the end, but not empty
787 789 this.get_cell_element(index-1).after(element);
788 790 } else if (this.is_valid_cell_index(index)) {
789 791 // otherwise always somewhere to append to
790 792 this.get_cell_element(index).before(element);
791 793 } else {
792 794 return false;
793 795 }
794 796
795 797 if (this.undelete_index !== null && index <= this.undelete_index) {
796 798 this.undelete_index = this.undelete_index + 1;
797 799 this.set_dirty(true);
798 800 }
799 801 return true;
800 802 };
801 803
802 804 /**
803 805 * Insert a cell of given type above given index, or at top
804 806 * of notebook if index smaller than 0.
805 807 *
806 808 * default index value is the one of currently selected cell
807 809 *
808 810 * @method insert_cell_above
809 811 * @param type {string} cell type
810 812 * @param [index] {integer}
811 813 *
812 814 * @return handle to created cell or null
813 815 **/
814 816 Notebook.prototype.insert_cell_above = function (type, index) {
815 817 index = this.index_or_selected(index);
816 818 return this.insert_cell_at_index(type, index);
817 819 };
818 820
819 821 /**
820 822 * Insert a cell of given type below given index, or at bottom
821 823 * of notebook if index greater thatn number of cell
822 824 *
823 825 * default index value is the one of currently selected cell
824 826 *
825 827 * @method insert_cell_below
826 828 * @param type {string} cell type
827 829 * @param [index] {integer}
828 830 *
829 831 * @return handle to created cell or null
830 832 *
831 833 **/
832 834 Notebook.prototype.insert_cell_below = function (type, index) {
833 835 index = this.index_or_selected(index);
834 836 return this.insert_cell_at_index(type, index+1);
835 837 };
836 838
837 839
838 840 /**
839 841 * Insert cell at end of notebook
840 842 *
841 843 * @method insert_cell_at_bottom
842 844 * @param {String} type cell type
843 845 *
844 846 * @return the added cell; or null
845 847 **/
846 848 Notebook.prototype.insert_cell_at_bottom = function (type){
847 849 var len = this.ncells();
848 850 return this.insert_cell_below(type,len-1);
849 851 };
850 852
851 853 /**
852 854 * Turn a cell into a code cell.
853 855 *
854 856 * @method to_code
855 857 * @param {Number} [index] A cell's index
856 858 */
857 859 Notebook.prototype.to_code = function (index) {
858 860 var i = this.index_or_selected(index);
859 861 if (this.is_valid_cell_index(i)) {
860 862 var source_element = this.get_cell_element(i);
861 863 var source_cell = source_element.data("cell");
862 864 if (!(source_cell instanceof IPython.CodeCell)) {
863 865 var target_cell = this.insert_cell_below('code',i);
864 866 var text = source_cell.get_text();
865 867 if (text === source_cell.placeholder) {
866 868 text = '';
867 869 }
868 870 target_cell.set_text(text);
869 871 // make this value the starting point, so that we can only undo
870 872 // to this state, instead of a blank cell
871 873 target_cell.code_mirror.clearHistory();
872 874 source_element.remove();
873 875 this.set_dirty(true);
874 876 };
875 877 };
876 878 };
877 879
878 880 /**
879 881 * Turn a cell into a Markdown cell.
880 882 *
881 883 * @method to_markdown
882 884 * @param {Number} [index] A cell's index
883 885 */
884 886 Notebook.prototype.to_markdown = function (index) {
885 887 var i = this.index_or_selected(index);
886 888 if (this.is_valid_cell_index(i)) {
887 889 var source_element = this.get_cell_element(i);
888 890 var source_cell = source_element.data("cell");
889 891 if (!(source_cell instanceof IPython.MarkdownCell)) {
890 892 var target_cell = this.insert_cell_below('markdown',i);
891 893 var text = source_cell.get_text();
892 894 if (text === source_cell.placeholder) {
893 895 text = '';
894 896 };
895 897 // The edit must come before the set_text.
896 898 target_cell.edit();
897 899 target_cell.set_text(text);
898 900 // make this value the starting point, so that we can only undo
899 901 // to this state, instead of a blank cell
900 902 target_cell.code_mirror.clearHistory();
901 903 source_element.remove();
902 904 this.set_dirty(true);
903 905 };
904 906 };
905 907 };
906 908
907 909 /**
908 910 * Turn a cell into a raw text cell.
909 911 *
910 912 * @method to_raw
911 913 * @param {Number} [index] A cell's index
912 914 */
913 915 Notebook.prototype.to_raw = function (index) {
914 916 var i = this.index_or_selected(index);
915 917 if (this.is_valid_cell_index(i)) {
916 918 var source_element = this.get_cell_element(i);
917 919 var source_cell = source_element.data("cell");
918 920 var target_cell = null;
919 921 if (!(source_cell instanceof IPython.RawCell)) {
920 922 target_cell = this.insert_cell_below('raw',i);
921 923 var text = source_cell.get_text();
922 924 if (text === source_cell.placeholder) {
923 925 text = '';
924 926 };
925 927 // The edit must come before the set_text.
926 928 target_cell.edit();
927 929 target_cell.set_text(text);
928 930 // make this value the starting point, so that we can only undo
929 931 // to this state, instead of a blank cell
930 932 target_cell.code_mirror.clearHistory();
931 933 source_element.remove();
932 934 this.set_dirty(true);
933 935 };
934 936 };
935 937 };
936 938
937 939 /**
938 940 * Turn a cell into a heading cell.
939 941 *
940 942 * @method to_heading
941 943 * @param {Number} [index] A cell's index
942 944 * @param {Number} [level] A heading level (e.g., 1 becomes &lt;h1&gt;)
943 945 */
944 946 Notebook.prototype.to_heading = function (index, level) {
945 947 level = level || 1;
946 948 var i = this.index_or_selected(index);
947 949 if (this.is_valid_cell_index(i)) {
948 950 var source_element = this.get_cell_element(i);
949 951 var source_cell = source_element.data("cell");
950 952 var target_cell = null;
951 953 if (source_cell instanceof IPython.HeadingCell) {
952 954 source_cell.set_level(level);
953 955 } else {
954 956 target_cell = this.insert_cell_below('heading',i);
955 957 var text = source_cell.get_text();
956 958 if (text === source_cell.placeholder) {
957 959 text = '';
958 960 };
959 961 // The edit must come before the set_text.
960 962 target_cell.set_level(level);
961 963 target_cell.edit();
962 964 target_cell.set_text(text);
963 965 // make this value the starting point, so that we can only undo
964 966 // to this state, instead of a blank cell
965 967 target_cell.code_mirror.clearHistory();
966 968 source_element.remove();
967 969 this.set_dirty(true);
968 970 };
969 971 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
970 972 {'cell_type':'heading',level:level}
971 973 );
972 974 };
973 975 };
974 976
975 977
976 978 // Cut/Copy/Paste
977 979
978 980 /**
979 981 * Enable UI elements for pasting cells.
980 982 *
981 983 * @method enable_paste
982 984 */
983 985 Notebook.prototype.enable_paste = function () {
984 986 var that = this;
985 987 if (!this.paste_enabled) {
986 988 $('#paste_cell_replace').removeClass('ui-state-disabled')
987 989 .on('click', function () {that.paste_cell_replace();});
988 990 $('#paste_cell_above').removeClass('ui-state-disabled')
989 991 .on('click', function () {that.paste_cell_above();});
990 992 $('#paste_cell_below').removeClass('ui-state-disabled')
991 993 .on('click', function () {that.paste_cell_below();});
992 994 this.paste_enabled = true;
993 995 };
994 996 };
995 997
996 998 /**
997 999 * Disable UI elements for pasting cells.
998 1000 *
999 1001 * @method disable_paste
1000 1002 */
1001 1003 Notebook.prototype.disable_paste = function () {
1002 1004 if (this.paste_enabled) {
1003 1005 $('#paste_cell_replace').addClass('ui-state-disabled').off('click');
1004 1006 $('#paste_cell_above').addClass('ui-state-disabled').off('click');
1005 1007 $('#paste_cell_below').addClass('ui-state-disabled').off('click');
1006 1008 this.paste_enabled = false;
1007 1009 };
1008 1010 };
1009 1011
1010 1012 /**
1011 1013 * Cut a cell.
1012 1014 *
1013 1015 * @method cut_cell
1014 1016 */
1015 1017 Notebook.prototype.cut_cell = function () {
1016 1018 this.copy_cell();
1017 1019 this.delete_cell();
1018 1020 }
1019 1021
1020 1022 /**
1021 1023 * Copy a cell.
1022 1024 *
1023 1025 * @method copy_cell
1024 1026 */
1025 1027 Notebook.prototype.copy_cell = function () {
1026 1028 var cell = this.get_selected_cell();
1027 1029 this.clipboard = cell.toJSON();
1028 1030 this.enable_paste();
1029 1031 };
1030 1032
1031 1033 /**
1032 1034 * Replace the selected cell with a cell in the clipboard.
1033 1035 *
1034 1036 * @method paste_cell_replace
1035 1037 */
1036 1038 Notebook.prototype.paste_cell_replace = function () {
1037 1039 if (this.clipboard !== null && this.paste_enabled) {
1038 1040 var cell_data = this.clipboard;
1039 1041 var new_cell = this.insert_cell_above(cell_data.cell_type);
1040 1042 new_cell.fromJSON(cell_data);
1041 1043 var old_cell = this.get_next_cell(new_cell);
1042 1044 this.delete_cell(this.find_cell_index(old_cell));
1043 1045 this.select(this.find_cell_index(new_cell));
1044 1046 };
1045 1047 };
1046 1048
1047 1049 /**
1048 1050 * Paste a cell from the clipboard above the selected cell.
1049 1051 *
1050 1052 * @method paste_cell_above
1051 1053 */
1052 1054 Notebook.prototype.paste_cell_above = function () {
1053 1055 if (this.clipboard !== null && this.paste_enabled) {
1054 1056 var cell_data = this.clipboard;
1055 1057 var new_cell = this.insert_cell_above(cell_data.cell_type);
1056 1058 new_cell.fromJSON(cell_data);
1057 1059 };
1058 1060 };
1059 1061
1060 1062 /**
1061 1063 * Paste a cell from the clipboard below the selected cell.
1062 1064 *
1063 1065 * @method paste_cell_below
1064 1066 */
1065 1067 Notebook.prototype.paste_cell_below = function () {
1066 1068 if (this.clipboard !== null && this.paste_enabled) {
1067 1069 var cell_data = this.clipboard;
1068 1070 var new_cell = this.insert_cell_below(cell_data.cell_type);
1069 1071 new_cell.fromJSON(cell_data);
1070 1072 };
1071 1073 };
1072 1074
1073 1075 // Cell undelete
1074 1076
1075 1077 /**
1076 1078 * Restore the most recently deleted cell.
1077 1079 *
1078 1080 * @method undelete
1079 1081 */
1080 1082 Notebook.prototype.undelete = function() {
1081 1083 if (this.undelete_backup !== null && this.undelete_index !== null) {
1082 1084 var current_index = this.get_selected_index();
1083 1085 if (this.undelete_index < current_index) {
1084 1086 current_index = current_index + 1;
1085 1087 }
1086 1088 if (this.undelete_index >= this.ncells()) {
1087 1089 this.select(this.ncells() - 1);
1088 1090 }
1089 1091 else {
1090 1092 this.select(this.undelete_index);
1091 1093 }
1092 1094 var cell_data = this.undelete_backup;
1093 1095 var new_cell = null;
1094 1096 if (this.undelete_below) {
1095 1097 new_cell = this.insert_cell_below(cell_data.cell_type);
1096 1098 } else {
1097 1099 new_cell = this.insert_cell_above(cell_data.cell_type);
1098 1100 }
1099 1101 new_cell.fromJSON(cell_data);
1100 1102 this.select(current_index);
1101 1103 this.undelete_backup = null;
1102 1104 this.undelete_index = null;
1103 1105 }
1104 1106 $('#undelete_cell').addClass('ui-state-disabled');
1105 1107 }
1106 1108
1107 1109 // Split/merge
1108 1110
1109 1111 /**
1110 1112 * Split the selected cell into two, at the cursor.
1111 1113 *
1112 1114 * @method split_cell
1113 1115 */
1114 1116 Notebook.prototype.split_cell = function () {
1115 1117 // Todo: implement spliting for other cell types.
1116 1118 var cell = this.get_selected_cell();
1117 1119 if (cell.is_splittable()) {
1118 1120 var texta = cell.get_pre_cursor();
1119 1121 var textb = cell.get_post_cursor();
1120 1122 if (cell instanceof IPython.CodeCell) {
1121 1123 cell.set_text(texta);
1122 1124 var new_cell = this.insert_cell_below('code');
1123 1125 new_cell.set_text(textb);
1124 1126 } else if (cell instanceof IPython.MarkdownCell) {
1125 1127 cell.set_text(texta);
1126 1128 cell.render();
1127 1129 var new_cell = this.insert_cell_below('markdown');
1128 1130 new_cell.edit(); // editor must be visible to call set_text
1129 1131 new_cell.set_text(textb);
1130 1132 new_cell.render();
1131 1133 }
1132 1134 };
1133 1135 };
1134 1136
1135 1137 /**
1136 1138 * Combine the selected cell into the cell above it.
1137 1139 *
1138 1140 * @method merge_cell_above
1139 1141 */
1140 1142 Notebook.prototype.merge_cell_above = function () {
1141 1143 var index = this.get_selected_index();
1142 1144 var cell = this.get_cell(index);
1143 1145 if (index > 0) {
1144 1146 var upper_cell = this.get_cell(index-1);
1145 1147 var upper_text = upper_cell.get_text();
1146 1148 var text = cell.get_text();
1147 1149 if (cell instanceof IPython.CodeCell) {
1148 1150 cell.set_text(upper_text+'\n'+text);
1149 1151 } else if (cell instanceof IPython.MarkdownCell) {
1150 1152 cell.edit();
1151 1153 cell.set_text(upper_text+'\n'+text);
1152 1154 cell.render();
1153 1155 };
1154 1156 this.delete_cell(index-1);
1155 1157 this.select(this.find_cell_index(cell));
1156 1158 };
1157 1159 };
1158 1160
1159 1161 /**
1160 1162 * Combine the selected cell into the cell below it.
1161 1163 *
1162 1164 * @method merge_cell_below
1163 1165 */
1164 1166 Notebook.prototype.merge_cell_below = function () {
1165 1167 var index = this.get_selected_index();
1166 1168 var cell = this.get_cell(index);
1167 1169 if (index < this.ncells()-1) {
1168 1170 var lower_cell = this.get_cell(index+1);
1169 1171 var lower_text = lower_cell.get_text();
1170 1172 var text = cell.get_text();
1171 1173 if (cell instanceof IPython.CodeCell) {
1172 1174 cell.set_text(text+'\n'+lower_text);
1173 1175 } else if (cell instanceof IPython.MarkdownCell) {
1174 1176 cell.edit();
1175 1177 cell.set_text(text+'\n'+lower_text);
1176 1178 cell.render();
1177 1179 };
1178 1180 this.delete_cell(index+1);
1179 1181 this.select(this.find_cell_index(cell));
1180 1182 };
1181 1183 };
1182 1184
1183 1185
1184 1186 // Cell collapsing and output clearing
1185 1187
1186 1188 /**
1187 1189 * Hide a cell's output.
1188 1190 *
1189 1191 * @method collapse
1190 1192 * @param {Number} index A cell's numeric index
1191 1193 */
1192 1194 Notebook.prototype.collapse = function (index) {
1193 1195 var i = this.index_or_selected(index);
1194 1196 this.get_cell(i).collapse();
1195 1197 this.set_dirty(true);
1196 1198 };
1197 1199
1198 1200 /**
1199 1201 * Show a cell's output.
1200 1202 *
1201 1203 * @method expand
1202 1204 * @param {Number} index A cell's numeric index
1203 1205 */
1204 1206 Notebook.prototype.expand = function (index) {
1205 1207 var i = this.index_or_selected(index);
1206 1208 this.get_cell(i).expand();
1207 1209 this.set_dirty(true);
1208 1210 };
1209 1211
1210 1212 /** Toggle whether a cell's output is collapsed or expanded.
1211 1213 *
1212 1214 * @method toggle_output
1213 1215 * @param {Number} index A cell's numeric index
1214 1216 */
1215 1217 Notebook.prototype.toggle_output = function (index) {
1216 1218 var i = this.index_or_selected(index);
1217 1219 this.get_cell(i).toggle_output();
1218 1220 this.set_dirty(true);
1219 1221 };
1220 1222
1221 1223 /**
1222 1224 * Toggle a scrollbar for long cell outputs.
1223 1225 *
1224 1226 * @method toggle_output_scroll
1225 1227 * @param {Number} index A cell's numeric index
1226 1228 */
1227 1229 Notebook.prototype.toggle_output_scroll = function (index) {
1228 1230 var i = this.index_or_selected(index);
1229 1231 this.get_cell(i).toggle_output_scroll();
1230 1232 };
1231 1233
1232 1234 /**
1233 1235 * Hide each code cell's output area.
1234 1236 *
1235 1237 * @method collapse_all_output
1236 1238 */
1237 1239 Notebook.prototype.collapse_all_output = function () {
1238 1240 var ncells = this.ncells();
1239 1241 var cells = this.get_cells();
1240 1242 for (var i=0; i<ncells; i++) {
1241 1243 if (cells[i] instanceof IPython.CodeCell) {
1242 1244 cells[i].output_area.collapse();
1243 1245 }
1244 1246 };
1245 1247 // this should not be set if the `collapse` key is removed from nbformat
1246 1248 this.set_dirty(true);
1247 1249 };
1248 1250
1249 1251 /**
1250 1252 * Expand each code cell's output area, and add a scrollbar for long output.
1251 1253 *
1252 1254 * @method scroll_all_output
1253 1255 */
1254 1256 Notebook.prototype.scroll_all_output = function () {
1255 1257 var ncells = this.ncells();
1256 1258 var cells = this.get_cells();
1257 1259 for (var i=0; i<ncells; i++) {
1258 1260 if (cells[i] instanceof IPython.CodeCell) {
1259 1261 cells[i].output_area.expand();
1260 1262 cells[i].output_area.scroll_if_long();
1261 1263 }
1262 1264 };
1263 1265 // this should not be set if the `collapse` key is removed from nbformat
1264 1266 this.set_dirty(true);
1265 1267 };
1266 1268
1267 1269 /**
1268 1270 * Expand each code cell's output area, and remove scrollbars.
1269 1271 *
1270 1272 * @method expand_all_output
1271 1273 */
1272 1274 Notebook.prototype.expand_all_output = function () {
1273 1275 var ncells = this.ncells();
1274 1276 var cells = this.get_cells();
1275 1277 for (var i=0; i<ncells; i++) {
1276 1278 if (cells[i] instanceof IPython.CodeCell) {
1277 1279 cells[i].output_area.expand();
1278 1280 cells[i].output_area.unscroll_area();
1279 1281 }
1280 1282 };
1281 1283 // this should not be set if the `collapse` key is removed from nbformat
1282 1284 this.set_dirty(true);
1283 1285 };
1284 1286
1285 1287 /**
1286 1288 * Clear each code cell's output area.
1287 1289 *
1288 1290 * @method clear_all_output
1289 1291 */
1290 1292 Notebook.prototype.clear_all_output = function () {
1291 1293 var ncells = this.ncells();
1292 1294 var cells = this.get_cells();
1293 1295 for (var i=0; i<ncells; i++) {
1294 1296 if (cells[i] instanceof IPython.CodeCell) {
1295 1297 cells[i].clear_output(true,true,true);
1296 1298 // Make all In[] prompts blank, as well
1297 1299 // TODO: make this configurable (via checkbox?)
1298 1300 cells[i].set_input_prompt();
1299 1301 }
1300 1302 };
1301 1303 this.set_dirty(true);
1302 1304 };
1303 1305
1304 1306
1305 1307 // Other cell functions: line numbers, ...
1306 1308
1307 1309 /**
1308 1310 * Toggle line numbers in the selected cell's input area.
1309 1311 *
1310 1312 * @method cell_toggle_line_numbers
1311 1313 */
1312 1314 Notebook.prototype.cell_toggle_line_numbers = function() {
1313 1315 this.get_selected_cell().toggle_line_numbers();
1314 1316 };
1315 1317
1316 1318 // Kernel related things
1317 1319
1318 1320 /**
1319 1321 * Start a new kernel and set it on each code cell.
1320 1322 *
1321 1323 * @method start_kernel
1322 1324 */
1323 1325 Notebook.prototype.start_kernel = function () {
1324 1326 var base_url = $('body').data('baseKernelUrl') + "kernels";
1325 1327 this.kernel = new IPython.Kernel(base_url);
1326 1328 this.kernel.start(this.notebook_id);
1327 1329 // Now that the kernel has been created, tell the CodeCells about it.
1328 1330 var ncells = this.ncells();
1329 1331 for (var i=0; i<ncells; i++) {
1330 1332 var cell = this.get_cell(i);
1331 1333 if (cell instanceof IPython.CodeCell) {
1332 1334 cell.set_kernel(this.kernel)
1333 1335 };
1334 1336 };
1335 1337 };
1336 1338
1337 1339 /**
1338 1340 * Prompt the user to restart the IPython kernel.
1339 1341 *
1340 1342 * @method restart_kernel
1341 1343 */
1342 1344 Notebook.prototype.restart_kernel = function () {
1343 1345 var that = this;
1344 var dialog = $('<div/>');
1345 dialog.html('Do you want to restart the current kernel? You will lose all variables defined in it.');
1346 $(document).append(dialog);
1347 dialog.dialog({
1348 resizable: false,
1349 modal: true,
1350 title: "Restart kernel or continue running?",
1351 closeText: '',
1346 IPython.dialog.modal({
1347 title : "Restart kernel or continue running?",
1348 body : $("<p/>").html(
1349 'Do you want to restart the current kernel? You will lose all variables defined in it.'
1350 ),
1352 1351 buttons : {
1353 "Restart": function () {
1354 that.kernel.restart();
1355 $(this).dialog('close');
1356 },
1357 "Continue running": function () {
1358 $(this).dialog('close');
1352 "Continue running" : {},
1353 "Restart" : {
1354 "class" : "btn-danger",
1355 "click" : function() {
1356 that.kernel.restart();
1357 }
1359 1358 }
1360 1359 }
1361 1360 });
1362 1361 };
1363 1362
1364 1363 /**
1365 1364 * Run the selected cell.
1366 1365 *
1367 1366 * Execute or render cell outputs.
1368 1367 *
1369 1368 * @method execute_selected_cell
1370 1369 * @param {Object} options Customize post-execution behavior
1371 1370 */
1372 1371 Notebook.prototype.execute_selected_cell = function (options) {
1373 1372 // add_new: should a new cell be added if we are at the end of the nb
1374 1373 // terminal: execute in terminal mode, which stays in the current cell
1375 1374 var default_options = {terminal: false, add_new: true};
1376 1375 $.extend(default_options, options);
1377 1376 var that = this;
1378 1377 var cell = that.get_selected_cell();
1379 1378 var cell_index = that.find_cell_index(cell);
1380 1379 if (cell instanceof IPython.CodeCell) {
1381 1380 cell.execute();
1382 1381 }
1383 1382 if (default_options.terminal) {
1384 1383 cell.select_all();
1385 1384 } else {
1386 1385 if ((cell_index === (that.ncells()-1)) && default_options.add_new) {
1387 1386 that.insert_cell_below('code');
1388 1387 // If we are adding a new cell at the end, scroll down to show it.
1389 1388 that.scroll_to_bottom();
1390 1389 } else {
1391 1390 that.select(cell_index+1);
1392 1391 };
1393 1392 };
1394 1393 this.set_dirty(true);
1395 1394 };
1396 1395
1397 1396 /**
1398 1397 * Execute all cells below the selected cell.
1399 1398 *
1400 1399 * @method execute_cells_below
1401 1400 */
1402 1401 Notebook.prototype.execute_cells_below = function () {
1403 1402 this.execute_cell_range(this.get_selected_index(), this.ncells());
1404 1403 this.scroll_to_bottom();
1405 1404 };
1406 1405
1407 1406 /**
1408 1407 * Execute all cells above the selected cell.
1409 1408 *
1410 1409 * @method execute_cells_above
1411 1410 */
1412 1411 Notebook.prototype.execute_cells_above = function () {
1413 1412 this.execute_cell_range(0, this.get_selected_index());
1414 1413 };
1415 1414
1416 1415 /**
1417 1416 * Execute all cells.
1418 1417 *
1419 1418 * @method execute_all_cells
1420 1419 */
1421 1420 Notebook.prototype.execute_all_cells = function () {
1422 1421 this.execute_cell_range(0, this.ncells());
1423 1422 this.scroll_to_bottom();
1424 1423 };
1425 1424
1426 1425 /**
1427 1426 * Execute a contiguous range of cells.
1428 1427 *
1429 1428 * @method execute_cell_range
1430 1429 * @param {Number} start Index of the first cell to execute (inclusive)
1431 1430 * @param {Number} end Index of the last cell to execute (exclusive)
1432 1431 */
1433 1432 Notebook.prototype.execute_cell_range = function (start, end) {
1434 1433 for (var i=start; i<end; i++) {
1435 1434 this.select(i);
1436 1435 this.execute_selected_cell({add_new:false});
1437 1436 };
1438 1437 };
1439 1438
1440 1439 // Persistance and loading
1441 1440
1442 1441 /**
1443 1442 * Getter method for this notebook's ID.
1444 1443 *
1445 1444 * @method get_notebook_id
1446 1445 * @return {String} This notebook's ID
1447 1446 */
1448 1447 Notebook.prototype.get_notebook_id = function () {
1449 1448 return this.notebook_id;
1450 1449 };
1451 1450
1452 1451 /**
1453 1452 * Getter method for this notebook's name.
1454 1453 *
1455 1454 * @method get_notebook_name
1456 1455 * @return {String} This notebook's name
1457 1456 */
1458 1457 Notebook.prototype.get_notebook_name = function () {
1459 1458 return this.notebook_name;
1460 1459 };
1461 1460
1462 1461 /**
1463 1462 * Setter method for this notebook's name.
1464 1463 *
1465 1464 * @method set_notebook_name
1466 1465 * @param {String} name A new name for this notebook
1467 1466 */
1468 1467 Notebook.prototype.set_notebook_name = function (name) {
1469 1468 this.notebook_name = name;
1470 1469 };
1471 1470
1472 1471 /**
1473 1472 * Check that a notebook's name is valid.
1474 1473 *
1475 1474 * @method test_notebook_name
1476 1475 * @param {String} nbname A name for this notebook
1477 1476 * @return {Boolean} True if the name is valid, false if invalid
1478 1477 */
1479 1478 Notebook.prototype.test_notebook_name = function (nbname) {
1480 1479 nbname = nbname || '';
1481 1480 if (this.notebook_name_blacklist_re.test(nbname) == false && nbname.length>0) {
1482 1481 return true;
1483 1482 } else {
1484 1483 return false;
1485 1484 };
1486 1485 };
1487 1486
1488 1487 /**
1489 1488 * Load a notebook from JSON (.ipynb).
1490 1489 *
1491 1490 * This currently handles one worksheet: others are deleted.
1492 1491 *
1493 1492 * @method fromJSON
1494 1493 * @param {Object} data JSON representation of a notebook
1495 1494 */
1496 1495 Notebook.prototype.fromJSON = function (data) {
1497 1496 var ncells = this.ncells();
1498 1497 var i;
1499 1498 for (i=0; i<ncells; i++) {
1500 1499 // Always delete cell 0 as they get renumbered as they are deleted.
1501 1500 this.delete_cell(0);
1502 1501 };
1503 1502 // Save the metadata and name.
1504 1503 this.metadata = data.metadata;
1505 1504 this.notebook_name = data.metadata.name;
1506 1505 // Only handle 1 worksheet for now.
1507 1506 var worksheet = data.worksheets[0];
1508 1507 if (worksheet !== undefined) {
1509 1508 if (worksheet.metadata) {
1510 1509 this.worksheet_metadata = worksheet.metadata;
1511 1510 }
1512 1511 var new_cells = worksheet.cells;
1513 1512 ncells = new_cells.length;
1514 1513 var cell_data = null;
1515 1514 var new_cell = null;
1516 1515 for (i=0; i<ncells; i++) {
1517 1516 cell_data = new_cells[i];
1518 1517 // VERSIONHACK: plaintext -> raw
1519 1518 // handle never-released plaintext name for raw cells
1520 1519 if (cell_data.cell_type === 'plaintext'){
1521 1520 cell_data.cell_type = 'raw';
1522 1521 }
1523 1522
1524 1523 new_cell = this.insert_cell_below(cell_data.cell_type);
1525 1524 new_cell.fromJSON(cell_data);
1526 1525 };
1527 1526 };
1528 1527 if (data.worksheets.length > 1) {
1529 var dialog = $('<div/>');
1530 dialog.html("This notebook has " + data.worksheets.length + " worksheets, " +
1531 "but this version of IPython can only handle the first. " +
1532 "If you save this notebook, worksheets after the first will be lost."
1533 );
1534 this.element.append(dialog);
1535 dialog.dialog({
1536 resizable: false,
1537 modal: true,
1538 title: "Multiple worksheets",
1539 closeText: "",
1540 close: function(event, ui) {$(this).dialog('destroy').remove();},
1528 IPython.dialog.modal({
1529 title : "Multiple worksheets",
1530 body : "This notebook has " + data.worksheets.length + " worksheets, " +
1531 "but this version of IPython can only handle the first. " +
1532 "If you save this notebook, worksheets after the first will be lost.",
1541 1533 buttons : {
1542 "OK": function () {
1543 $(this).dialog('close');
1534 OK : {
1535 class : "btn-danger"
1544 1536 }
1545 },
1546 width: 400
1537 }
1547 1538 });
1548 1539 }
1549 1540 };
1550 1541
1551 1542 /**
1552 1543 * Dump this notebook into a JSON-friendly object.
1553 1544 *
1554 1545 * @method toJSON
1555 1546 * @return {Object} A JSON-friendly representation of this notebook.
1556 1547 */
1557 1548 Notebook.prototype.toJSON = function () {
1558 1549 var cells = this.get_cells();
1559 1550 var ncells = cells.length;
1560 1551 var cell_array = new Array(ncells);
1561 1552 for (var i=0; i<ncells; i++) {
1562 1553 cell_array[i] = cells[i].toJSON();
1563 1554 };
1564 1555 var data = {
1565 1556 // Only handle 1 worksheet for now.
1566 1557 worksheets : [{
1567 1558 cells: cell_array,
1568 1559 metadata: this.worksheet_metadata
1569 1560 }],
1570 1561 metadata : this.metadata
1571 1562 };
1572 1563 return data;
1573 1564 };
1574 1565
1575 1566 /**
1576 1567 * Start an autosave timer, for periodically saving the notebook.
1577 1568 *
1578 1569 * @method set_autosave_interval
1579 1570 * @param {Integer} interval the autosave interval in milliseconds
1580 1571 */
1581 1572 Notebook.prototype.set_autosave_interval = function (interval) {
1582 1573 var that = this;
1583 1574 // clear previous interval, so we don't get simultaneous timers
1584 1575 if (this.autosave_timer) {
1585 1576 clearInterval(this.autosave_timer);
1586 1577 }
1587 1578
1588 1579 this.autosave_interval = this.minimum_autosave_interval = interval;
1589 1580 if (interval) {
1590 1581 this.autosave_timer = setInterval(function() {
1591 1582 if (that.dirty) {
1592 1583 that.save_notebook();
1593 1584 }
1594 1585 }, interval);
1595 1586 $([IPython.events]).trigger("autosave_enabled.Notebook", interval);
1596 1587 } else {
1597 1588 this.autosave_timer = null;
1598 1589 $([IPython.events]).trigger("autosave_disabled.Notebook");
1599 1590 };
1600 1591 };
1601 1592
1602 1593 /**
1603 1594 * Save this notebook on the server.
1604 1595 *
1605 1596 * @method save_notebook
1606 1597 */
1607 1598 Notebook.prototype.save_notebook = function () {
1608 1599 // We may want to move the name/id/nbformat logic inside toJSON?
1609 1600 var data = this.toJSON();
1610 1601 data.metadata.name = this.notebook_name;
1611 1602 data.nbformat = this.nbformat;
1612 1603 data.nbformat_minor = this.nbformat_minor;
1613 1604
1614 1605 // time the ajax call for autosave tuning purposes.
1615 1606 var start = new Date().getTime();
1616 1607
1617 1608 // We do the call with settings so we can set cache to false.
1618 1609 var settings = {
1619 1610 processData : false,
1620 1611 cache : false,
1621 1612 type : "PUT",
1622 1613 data : JSON.stringify(data),
1623 1614 headers : {'Content-Type': 'application/json'},
1624 1615 success : $.proxy(this.save_notebook_success, this, start),
1625 1616 error : $.proxy(this.save_notebook_error, this)
1626 1617 };
1627 1618 $([IPython.events]).trigger('notebook_saving.Notebook');
1628 1619 var url = this.baseProjectUrl() + 'notebooks/' + this.notebook_id;
1629 1620 $.ajax(url, settings);
1630 1621 };
1631 1622
1632 1623 /**
1633 1624 * Success callback for saving a notebook.
1634 1625 *
1635 1626 * @method save_notebook_success
1636 1627 * @param {Integer} start the time when the save request started
1637 1628 * @param {Object} data JSON representation of a notebook
1638 1629 * @param {String} status Description of response status
1639 1630 * @param {jqXHR} xhr jQuery Ajax object
1640 1631 */
1641 1632 Notebook.prototype.save_notebook_success = function (start, data, status, xhr) {
1642 1633 this.set_dirty(false);
1643 1634 $([IPython.events]).trigger('notebook_saved.Notebook');
1644 1635 this._update_autosave_interval(start);
1645 1636 if (this._checkpoint_after_save) {
1646 1637 this.create_checkpoint();
1647 1638 this._checkpoint_after_save = false;
1648 1639 };
1649 1640 };
1650 1641
1651 1642 /**
1652 1643 * update the autosave interval based on how long the last save took
1653 1644 *
1654 1645 * @method _update_autosave_interval
1655 1646 * @param {Integer} timestamp when the save request started
1656 1647 */
1657 1648 Notebook.prototype._update_autosave_interval = function (start) {
1658 1649 var duration = (new Date().getTime() - start);
1659 1650 if (this.autosave_interval) {
1660 1651 // new save interval: higher of 10x save duration or parameter (default 30 seconds)
1661 1652 var interval = Math.max(10 * duration, this.minimum_autosave_interval);
1662 1653 // round to 10 seconds, otherwise we will be setting a new interval too often
1663 1654 interval = 10000 * Math.round(interval / 10000);
1664 1655 // set new interval, if it's changed
1665 1656 if (interval != this.autosave_interval) {
1666 1657 this.set_autosave_interval(interval);
1667 1658 }
1668 1659 }
1669 1660 };
1670 1661
1671 1662 /**
1672 1663 * Failure callback for saving a notebook.
1673 1664 *
1674 1665 * @method save_notebook_error
1675 1666 * @param {jqXHR} xhr jQuery Ajax object
1676 1667 * @param {String} status Description of response status
1677 1668 * @param {String} error_msg HTTP error message
1678 1669 */
1679 1670 Notebook.prototype.save_notebook_error = function (xhr, status, error_msg) {
1680 1671 $([IPython.events]).trigger('notebook_save_failed.Notebook');
1681 1672 };
1682 1673
1683 1674 /**
1684 1675 * Request a notebook's data from the server.
1685 1676 *
1686 1677 * @method load_notebook
1687 1678 * @param {String} notebook_id A notebook to load
1688 1679 */
1689 1680 Notebook.prototype.load_notebook = function (notebook_id) {
1690 1681 var that = this;
1691 1682 this.notebook_id = notebook_id;
1692 1683 // We do the call with settings so we can set cache to false.
1693 1684 var settings = {
1694 1685 processData : false,
1695 1686 cache : false,
1696 1687 type : "GET",
1697 1688 dataType : "json",
1698 1689 success : $.proxy(this.load_notebook_success,this),
1699 1690 error : $.proxy(this.load_notebook_error,this),
1700 1691 };
1701 1692 $([IPython.events]).trigger('notebook_loading.Notebook');
1702 1693 var url = this.baseProjectUrl() + 'notebooks/' + this.notebook_id;
1703 1694 $.ajax(url, settings);
1704 1695 };
1705 1696
1706 1697 /**
1707 1698 * Success callback for loading a notebook from the server.
1708 1699 *
1709 1700 * Load notebook data from the JSON response.
1710 1701 *
1711 1702 * @method load_notebook_success
1712 1703 * @param {Object} data JSON representation of a notebook
1713 1704 * @param {String} status Description of response status
1714 1705 * @param {jqXHR} xhr jQuery Ajax object
1715 1706 */
1716 1707 Notebook.prototype.load_notebook_success = function (data, status, xhr) {
1717 1708 this.fromJSON(data);
1718 1709 if (this.ncells() === 0) {
1719 1710 this.insert_cell_below('code');
1720 1711 };
1721 1712 this.set_dirty(false);
1722 1713 this.select(0);
1723 1714 this.scroll_to_top();
1724 1715 if (data.orig_nbformat !== undefined && data.nbformat !== data.orig_nbformat) {
1725 msg = "This notebook has been converted from an older " +
1716 var msg = "This notebook has been converted from an older " +
1726 1717 "notebook format (v"+data.orig_nbformat+") to the current notebook " +
1727 1718 "format (v"+data.nbformat+"). The next time you save this notebook, the " +
1728 "newer notebook format will be used and older verions of IPython " +
1719 "newer notebook format will be used and older versions of IPython " +
1729 1720 "may not be able to read it. To keep the older version, close the " +
1730 1721 "notebook without saving it.";
1731 var dialog = $('<div/>');
1732 dialog.html(msg);
1733 this.element.append(dialog);
1734 dialog.dialog({
1735 resizable: false,
1736 modal: true,
1737 title: "Notebook converted",
1738 closeText: "",
1739 close: function(event, ui) {$(this).dialog('destroy').remove();},
1722 IPython.dialog.modal({
1723 title : "Notebook converted",
1724 body : msg,
1740 1725 buttons : {
1741 "OK": function () {
1742 $(this).dialog('close');
1726 OK : {
1727 class : "btn-primary"
1743 1728 }
1744 },
1745 width: 400
1729 }
1746 1730 });
1747 1731 } else if (data.orig_nbformat_minor !== undefined && data.nbformat_minor !== data.orig_nbformat_minor) {
1748 1732 var that = this;
1749 1733 var orig_vs = 'v' + data.nbformat + '.' + data.orig_nbformat_minor;
1750 1734 var this_vs = 'v' + data.nbformat + '.' + this.nbformat_minor;
1751 1735 var msg = "This notebook is version " + orig_vs + ", but we only fully support up to " +
1752 1736 this_vs + ". You can still work with this notebook, but some features " +
1753 1737 "introduced in later notebook versions may not be available."
1754 1738
1755 var dialog = $('<div/>');
1756 dialog.html(msg);
1757 this.element.append(dialog);
1758 dialog.dialog({
1759 resizable: false,
1760 modal: true,
1761 title: "Newer Notebook",
1762 closeText: "",
1763 close: function(event, ui) {$(this).dialog('destroy').remove();},
1739 IPython.dialog.modal({
1740 title : "Newer Notebook",
1741 body : msg,
1764 1742 buttons : {
1765 "OK": function () {
1766 $(this).dialog('close');
1743 OK : {
1744 class : "btn-danger"
1767 1745 }
1768 },
1769 width: 400
1746 }
1770 1747 });
1771 1748
1772 1749 }
1773 1750
1774 1751 // Create the kernel after the notebook is completely loaded to prevent
1775 1752 // code execution upon loading, which is a security risk.
1776 1753 if (! this.read_only) {
1777 1754 this.start_kernel();
1778 1755 // load our checkpoint list
1779 1756 IPython.notebook.list_checkpoints();
1780 1757 }
1781 1758 $([IPython.events]).trigger('notebook_loaded.Notebook');
1782 1759 };
1783 1760
1784 1761 /**
1785 1762 * Failure callback for loading a notebook from the server.
1786 1763 *
1787 1764 * @method load_notebook_error
1788 1765 * @param {jqXHR} xhr jQuery Ajax object
1789 1766 * @param {String} textStatus Description of response status
1790 1767 * @param {String} errorThrow HTTP error message
1791 1768 */
1792 1769 Notebook.prototype.load_notebook_error = function (xhr, textStatus, errorThrow) {
1793 1770 if (xhr.status === 500) {
1794 1771 var msg = "An error occurred while loading this notebook. Most likely " +
1795 1772 "this notebook is in a newer format than is supported by this " +
1796 1773 "version of IPython. This version can load notebook formats " +
1797 1774 "v"+this.nbformat+" or earlier.";
1798 var dialog = $('<div/>');
1799 dialog.html(msg);
1800 this.element.append(dialog);
1801 dialog.dialog({
1802 resizable: false,
1803 modal: true,
1775
1776 IPython.dialog.modal({
1804 1777 title: "Error loading notebook",
1805 closeText: "",
1806 close: function(event, ui) {$(this).dialog('destroy').remove();},
1778 body : msg,
1807 1779 buttons : {
1808 "OK": function () {
1809 $(this).dialog('close');
1810 }
1811 },
1812 width: 400
1780 "OK": {}
1781 }
1813 1782 });
1814 1783 }
1815 1784 }
1816 1785
1817 1786 /********************* checkpoint-related *********************/
1818 1787
1819 1788 /**
1820 1789 * Save the notebook then immediately create a checkpoint.
1821 1790 *
1822 1791 * @method save_checkpoint
1823 1792 */
1824 1793 Notebook.prototype.save_checkpoint = function () {
1825 1794 this._checkpoint_after_save = true;
1826 1795 this.save_notebook();
1827 1796 };
1828 1797
1829 1798 /**
1830 1799 * List checkpoints for this notebook.
1831 1800 *
1832 1801 * @method list_checkpoint
1833 1802 */
1834 1803 Notebook.prototype.list_checkpoints = function () {
1835 1804 var url = this.baseProjectUrl() + 'notebooks/' + this.notebook_id + '/checkpoints';
1836 1805 $.get(url).done(
1837 1806 $.proxy(this.list_checkpoints_success, this)
1838 1807 ).fail(
1839 1808 $.proxy(this.list_checkpoints_error, this)
1840 1809 );
1841 1810 };
1842 1811
1843 1812 /**
1844 1813 * Success callback for listing checkpoints.
1845 1814 *
1846 1815 * @method list_checkpoint_success
1847 1816 * @param {Object} data JSON representation of a checkpoint
1848 1817 * @param {String} status Description of response status
1849 1818 * @param {jqXHR} xhr jQuery Ajax object
1850 1819 */
1851 1820 Notebook.prototype.list_checkpoints_success = function (data, status, xhr) {
1852 1821 var data = $.parseJSON(data);
1853 1822 if (data.length) {
1854 1823 this.last_checkpoint = data[0];
1855 1824 } else {
1856 1825 this.last_checkpoint = null;
1857 1826 }
1858 1827 $([IPython.events]).trigger('checkpoints_listed.Notebook', [data]);
1859 1828 };
1860 1829
1861 1830 /**
1862 1831 * Failure callback for listing a checkpoint.
1863 1832 *
1864 1833 * @method list_checkpoint_error
1865 1834 * @param {jqXHR} xhr jQuery Ajax object
1866 1835 * @param {String} status Description of response status
1867 1836 * @param {String} error_msg HTTP error message
1868 1837 */
1869 1838 Notebook.prototype.list_checkpoints_error = function (xhr, status, error_msg) {
1870 1839 $([IPython.events]).trigger('list_checkpoints_failed.Notebook');
1871 1840 };
1872 1841
1873 1842 /**
1874 1843 * Create a checkpoint of this notebook on the server from the most recent save.
1875 1844 *
1876 1845 * @method create_checkpoint
1877 1846 */
1878 1847 Notebook.prototype.create_checkpoint = function () {
1879 1848 var url = this.baseProjectUrl() + 'notebooks/' + this.notebook_id + '/checkpoints';
1880 1849 $.post(url).done(
1881 1850 $.proxy(this.create_checkpoint_success, this)
1882 1851 ).fail(
1883 1852 $.proxy(this.create_checkpoint_error, this)
1884 1853 );
1885 1854 };
1886 1855
1887 1856 /**
1888 1857 * Success callback for creating a checkpoint.
1889 1858 *
1890 1859 * @method create_checkpoint_success
1891 1860 * @param {Object} data JSON representation of a checkpoint
1892 1861 * @param {String} status Description of response status
1893 1862 * @param {jqXHR} xhr jQuery Ajax object
1894 1863 */
1895 1864 Notebook.prototype.create_checkpoint_success = function (data, status, xhr) {
1896 1865 var data = $.parseJSON(data);
1897 1866 this.last_checkpoint = data;
1898 1867 $([IPython.events]).trigger('checkpoint_created.Notebook', data);
1899 1868 };
1900 1869
1901 1870 /**
1902 1871 * Failure callback for creating a checkpoint.
1903 1872 *
1904 1873 * @method create_checkpoint_error
1905 1874 * @param {jqXHR} xhr jQuery Ajax object
1906 1875 * @param {String} status Description of response status
1907 1876 * @param {String} error_msg HTTP error message
1908 1877 */
1909 1878 Notebook.prototype.create_checkpoint_error = function (xhr, status, error_msg) {
1910 1879 $([IPython.events]).trigger('checkpoint_failed.Notebook');
1911 1880 };
1912 1881
1913 1882 Notebook.prototype.restore_checkpoint_dialog = function (checkpoint) {
1914 1883 var that = this;
1915 1884 var checkpoint = checkpoint || this.last_checkpoint;
1916 1885 if ( ! checkpoint ) {
1917 1886 console.log("restore dialog, but no checkpoint to restore to!");
1918 1887 return;
1919 1888 }
1920 var dialog = $('<div/>').append(
1889 var body = $('<div/>').append(
1921 1890 $('<p/>').addClass("p-space").text(
1922 1891 "Are you sure you want to revert the notebook to " +
1923 1892 "the latest checkpoint?"
1924 1893 ).append(
1925 1894 $("<strong/>").text(
1926 1895 " This cannot be undone."
1927 1896 )
1928 1897 )
1929 1898 ).append(
1930 1899 $('<p/>').addClass("p-space").text("The checkpoint was last updated at:")
1931 1900 ).append(
1932 1901 $('<p/>').addClass("p-space").text(
1933 1902 Date(checkpoint.last_modified)
1934 1903 ).css("text-align", "center")
1935 1904 );
1936 1905
1937 $(document).append(dialog);
1938
1939 dialog.dialog({
1940 resizable: false,
1941 modal: true,
1942 title: "Revert notebook to checkpoint",
1943 closeText: '',
1906 IPython.dialog.modal({
1907 title : "Revert notebook to checkpoint",
1908 body : body,
1944 1909 buttons : {
1945 "Revert": function () {
1946 that.restore_checkpoint(checkpoint.checkpoint_id);
1947 $(this).dialog('close');
1910 Revert : {
1911 class : "btn-danger",
1912 click : function () {
1913 that.restore_checkpoint(checkpoint.checkpoint_id);
1914 }
1948 1915 },
1949 "Cancel": function () {
1950 $(this).dialog('close');
1916 Cancel : {}
1951 1917 }
1952 },
1953 width: 400
1954 1918 });
1955 1919 }
1956 1920
1957 1921 /**
1958 1922 * Restore the notebook to a checkpoint state.
1959 1923 *
1960 1924 * @method restore_checkpoint
1961 1925 * @param {String} checkpoint ID
1962 1926 */
1963 1927 Notebook.prototype.restore_checkpoint = function (checkpoint) {
1964 1928 $([IPython.events]).trigger('notebook_restoring.Notebook', checkpoint);
1965 1929 var url = this.baseProjectUrl() + 'notebooks/' + this.notebook_id + '/checkpoints/' + checkpoint;
1966 1930 $.post(url).done(
1967 1931 $.proxy(this.restore_checkpoint_success, this)
1968 1932 ).fail(
1969 1933 $.proxy(this.restore_checkpoint_error, this)
1970 1934 );
1971 1935 };
1972 1936
1973 1937 /**
1974 1938 * Success callback for restoring a notebook to a checkpoint.
1975 1939 *
1976 1940 * @method restore_checkpoint_success
1977 1941 * @param {Object} data (ignored, should be empty)
1978 1942 * @param {String} status Description of response status
1979 1943 * @param {jqXHR} xhr jQuery Ajax object
1980 1944 */
1981 1945 Notebook.prototype.restore_checkpoint_success = function (data, status, xhr) {
1982 1946 $([IPython.events]).trigger('checkpoint_restored.Notebook');
1983 1947 this.load_notebook(this.notebook_id);
1984 1948 };
1985 1949
1986 1950 /**
1987 1951 * Failure callback for restoring a notebook to a checkpoint.
1988 1952 *
1989 1953 * @method restore_checkpoint_error
1990 1954 * @param {jqXHR} xhr jQuery Ajax object
1991 1955 * @param {String} status Description of response status
1992 1956 * @param {String} error_msg HTTP error message
1993 1957 */
1994 1958 Notebook.prototype.restore_checkpoint_error = function (xhr, status, error_msg) {
1995 1959 $([IPython.events]).trigger('checkpoint_restore_failed.Notebook');
1996 1960 };
1997 1961
1998 1962 /**
1999 1963 * Delete a notebook checkpoint.
2000 1964 *
2001 1965 * @method delete_checkpoint
2002 1966 * @param {String} checkpoint ID
2003 1967 */
2004 1968 Notebook.prototype.delete_checkpoint = function (checkpoint) {
2005 1969 $([IPython.events]).trigger('notebook_restoring.Notebook', checkpoint);
2006 1970 var url = this.baseProjectUrl() + 'notebooks/' + this.notebook_id + '/checkpoints/' + checkpoint;
2007 1971 $.ajax(url, {
2008 1972 type: 'DELETE',
2009 1973 success: $.proxy(this.delete_checkpoint_success, this),
2010 1974 error: $.proxy(this.delete_notebook_error,this)
2011 1975 });
2012 1976 };
2013 1977
2014 1978 /**
2015 1979 * Success callback for deleting a notebook checkpoint
2016 1980 *
2017 1981 * @method delete_checkpoint_success
2018 1982 * @param {Object} data (ignored, should be empty)
2019 1983 * @param {String} status Description of response status
2020 1984 * @param {jqXHR} xhr jQuery Ajax object
2021 1985 */
2022 1986 Notebook.prototype.delete_checkpoint_success = function (data, status, xhr) {
2023 1987 $([IPython.events]).trigger('checkpoint_deleted.Notebook', data);
2024 1988 this.load_notebook(this.notebook_id);
2025 1989 };
2026 1990
2027 1991 /**
2028 1992 * Failure callback for deleting a notebook checkpoint.
2029 1993 *
2030 1994 * @method delete_checkpoint_error
2031 1995 * @param {jqXHR} xhr jQuery Ajax object
2032 1996 * @param {String} status Description of response status
2033 1997 * @param {String} error_msg HTTP error message
2034 1998 */
2035 1999 Notebook.prototype.delete_checkpoint_error = function (xhr, status, error_msg) {
2036 2000 $([IPython.events]).trigger('checkpoint_delete_failed.Notebook');
2037 2001 };
2038 2002
2039 2003
2040 2004 IPython.Notebook = Notebook;
2041 2005
2042 2006
2043 2007 return IPython;
2044 2008
2045 2009 }(IPython));
2046 2010
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed, binary diff hidden
1 NO CONTENT: file was removed, binary diff hidden
1 NO CONTENT: file was removed, binary diff hidden
1 NO CONTENT: file was removed, binary diff hidden
1 NO CONTENT: file was removed, binary diff hidden
1 NO CONTENT: file was removed, binary diff hidden
1 NO CONTENT: file was removed, binary diff hidden
1 NO CONTENT: file was removed, binary diff hidden
1 NO CONTENT: file was removed, binary diff hidden
1 NO CONTENT: file was removed, binary diff hidden
1 NO CONTENT: file was removed, binary diff hidden
1 NO CONTENT: file was removed, binary diff hidden
1 NO CONTENT: file was removed, binary diff hidden
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now