##// END OF EJS Templates
statelessify matjaxutils...
Matthias BUSSONNIER -
Show More
@@ -1,245 +1,261 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2008-2012 The IPython Development Team
2 // Copyright (C) 2008-2012 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // MathJax utility functions
9 // MathJax utility functions
10 //============================================================================
10 //============================================================================
11
11
12 "using strict";
13
12 IPython.namespace('IPython.mathjaxutils');
14 IPython.namespace('IPython.mathjaxutils');
13
15
14 IPython.mathjaxutils = (function (IPython) {
16 IPython.mathjaxutils = (function (IPython) {
15
17
16 var init = function () {
18 var init = function () {
17 if (window.MathJax) {
19 if (window.MathJax) {
18 // MathJax loaded
20 // MathJax loaded
19 MathJax.Hub.Config({
21 MathJax.Hub.Config({
20 tex2jax: {
22 tex2jax: {
21 inlineMath: [ ['$','$'], ["\\(","\\)"] ],
23 inlineMath: [ ['$','$'], ["\\(","\\)"] ],
22 displayMath: [ ['$$','$$'], ["\\[","\\]"] ],
24 displayMath: [ ['$$','$$'], ["\\[","\\]"] ],
23 processEscapes: true,
25 processEscapes: true,
24 processEnvironments: true
26 processEnvironments: true
25 },
27 },
26 displayAlign: 'left', // Change this to 'center' to center equations.
28 displayAlign: 'left', // Change this to 'center' to center equations.
27 "HTML-CSS": {
29 "HTML-CSS": {
28 styles: {'.MathJax_Display': {"margin": 0}}
30 styles: {'.MathJax_Display': {"margin": 0}}
29 }
31 }
30 });
32 });
31 MathJax.Hub.Configured();
33 MathJax.Hub.Configured();
32 } else if (window.mathjax_url != "") {
34 } else if (window.mathjax_url != "") {
33 // Don't have MathJax, but should. Show dialog.
35 // Don't have MathJax, but should. Show dialog.
34 var message = $('<div/>')
36 var message = $('<div/>')
35 .append(
37 .append(
36 $("<p/></p>").addClass('dialog').html(
38 $("<p/></p>").addClass('dialog').html(
37 "Math/LaTeX rendering will be disabled."
39 "Math/LaTeX rendering will be disabled."
38 )
40 )
39 ).append(
41 ).append(
40 $("<p></p>").addClass('dialog').html(
42 $("<p></p>").addClass('dialog').html(
41 "If you have administrative access to the notebook server and" +
43 "If you have administrative access to the notebook server and" +
42 " a working internet connection, you can install a local copy" +
44 " a working internet connection, you can install a local copy" +
43 " of MathJax for offline use with the following command on the server" +
45 " of MathJax for offline use with the following command on the server" +
44 " at a Python or IPython prompt:"
46 " at a Python or IPython prompt:"
45 )
47 )
46 ).append(
48 ).append(
47 $("<pre></pre>").addClass('dialog').html(
49 $("<pre></pre>").addClass('dialog').html(
48 ">>> from IPython.external import mathjax; mathjax.install_mathjax()"
50 ">>> from IPython.external import mathjax; mathjax.install_mathjax()"
49 )
51 )
50 ).append(
52 ).append(
51 $("<p></p>").addClass('dialog').html(
53 $("<p></p>").addClass('dialog').html(
52 "This will try to install MathJax into the IPython source directory."
54 "This will try to install MathJax into the IPython source directory."
53 )
55 )
54 ).append(
56 ).append(
55 $("<p></p>").addClass('dialog').html(
57 $("<p></p>").addClass('dialog').html(
56 "If IPython is installed to a location that requires" +
58 "If IPython is installed to a location that requires" +
57 " administrative privileges to write, you will need to make this call as" +
59 " administrative privileges to write, you will need to make this call as" +
58 " an administrator, via 'sudo'."
60 " an administrator, via 'sudo'."
59 )
61 )
60 ).append(
62 ).append(
61 $("<p></p>").addClass('dialog').html(
63 $("<p></p>").addClass('dialog').html(
62 "When you start the notebook server, you can instruct it to disable MathJax support altogether:"
64 "When you start the notebook server, you can instruct it to disable MathJax support altogether:"
63 )
65 )
64 ).append(
66 ).append(
65 $("<pre></pre>").addClass('dialog').html(
67 $("<pre></pre>").addClass('dialog').html(
66 "$ ipython notebook --no-mathjax"
68 "$ ipython notebook --no-mathjax"
67 )
69 )
68 ).append(
70 ).append(
69 $("<p></p>").addClass('dialog').html(
71 $("<p></p>").addClass('dialog').html(
70 "which will prevent this dialog from appearing."
72 "which will prevent this dialog from appearing."
71 )
73 )
72 )
74 )
73 IPython.dialog.modal({
75 IPython.dialog.modal({
74 title : "Failed to retrieve MathJax from '" + window.mathjax_url + "'",
76 title : "Failed to retrieve MathJax from '" + window.mathjax_url + "'",
75 body : message,
77 body : message,
76 buttons : {
78 buttons : {
77 OK : {class: "btn-danger"}
79 OK : {class: "btn-danger"}
78 }
80 }
79 });
81 });
80 } else {
82 } else {
81 // No MathJax, but none expected. No dialog.
83 // No MathJax, but none expected. No dialog.
82 };
84 };
83 };
85 };
84
86
85 // Some magic for deferring mathematical expressions to MathJax
87 // Some magic for deferring mathematical expressions to MathJax
86 // by hiding them from the Markdown parser.
88 // by hiding them from the Markdown parser.
87 // Some of the code here is adapted with permission from Davide Cervone
89 // Some of the code here is adapted with permission from Davide Cervone
88 // under the terms of the Apache2 license governing the MathJax project.
90 // under the terms of the Apache2 license governing the MathJax project.
89 // Other minor modifications are also due to StackExchange and are used with
91 // Other minor modifications are also due to StackExchange and are used with
90 // permission.
92 // permission.
91
93
92 var inline = "$"; // the inline math delimiter
94 var inline = "$"; // the inline math delimiter
93 var blocks, start, end, last, braces; // used in searching for math
94 var math; // stores math until pagedown (Markdown parser) is done
95
95
96 // MATHSPLIT contains the pattern for math delimiters and special symbols
96 // MATHSPLIT contains the pattern for math delimiters and special symbols
97 // needed for searching for math in the text input.
97 // needed for searching for math in the text input.
98 var MATHSPLIT = /(\$\$?|\\(?:begin|end)\{[a-z]*\*?\}|\\[\\{}$]|[{}]|(?:\n\s*)+|@@\d+@@)/i;
98 var MATHSPLIT = /(\$\$?|\\(?:begin|end)\{[a-z]*\*?\}|\\[\\{}$]|[{}]|(?:\n\s*)+|@@\d+@@)/i;
99
99
100 // The math is in blocks i through j, so
100 // The math is in blocks i through j, so
101 // collect it into one block and clear the others.
101 // collect it into one block and clear the others.
102 // Replace &, <, and > by named entities.
102 // Replace &, <, and > by named entities.
103 // For IE, put <br> at the ends of comments since IE removes \n.
103 // For IE, put <br> at the ends of comments since IE removes \n.
104 // Clear the current math positions and store the index of the
104 // Clear the current math positions and store the index of the
105 // math, then push the math string onto the storage array.
105 // math, then push the math string onto the storage array.
106 // The preProcess function is called on all blocks if it has been passed in
106 // The preProcess function is called on all blocks if it has been passed in
107 var process_math = function (i, j, pre_process) {
107 var process_math = function (i, j, pre_process, math, blocks, start, end, last) {
108 var hub = MathJax.Hub;
108 var hub = MathJax.Hub;
109 var block = blocks.slice(i, j + 1).join("").replace(/&/g, "&amp;") // use HTML entity for &
109 var block = blocks.slice(i, j + 1).join("").replace(/&/g, "&amp;") // use HTML entity for &
110 .replace(/</g, "&lt;") // use HTML entity for <
110 .replace(/</g, "&lt;") // use HTML entity for <
111 .replace(/>/g, "&gt;") // use HTML entity for >
111 .replace(/>/g, "&gt;") // use HTML entity for >
112 ;
112 ;
113 if (hub.Browser.isMSIE) {
113 if (hub.Browser.isMSIE) {
114 block = block.replace(/(%[^\n]*)\n/g, "$1<br/>\n")
114 block = block.replace(/(%[^\n]*)\n/g, "$1<br/>\n")
115 }
115 }
116 while (j > i) {
116 while (j > i) {
117 blocks[j] = "";
117 blocks[j] = "";
118 j--;
118 j--;
119 }
119 }
120 blocks[i] = "@@" + math.length + "@@"; // replace the current block text with a unique tag to find later
120 blocks[i] = "@@" + math.length + "@@"; // replace the current block text with a unique tag to find later
121 if (pre_process)
121 if (pre_process)
122 block = pre_process(block);
122 block = pre_process(block);
123 math.push(block);
123 math.push(block);
124 start = end = last = null;
124 start = null;
125 end = null;
126 last = null;
127 return [blocks, start, end, last]
125 }
128 }
126
129
127 // Break up the text into its component parts and search
130 // Break up the text into its component parts and search
128 // through them for math delimiters, braces, linebreaks, etc.
131 // through them for math delimiters, braces, linebreaks, etc.
129 // Math delimiters must match and braces must balance.
132 // Math delimiters must match and braces must balance.
130 // Don't allow math to pass through a double linebreak
133 // Don't allow math to pass through a double linebreak
131 // (which will be a paragraph).
134 // (which will be a paragraph).
132 //
135 //
133 var remove_math = function (text) {
136 var remove_math = function (text) {
134 if (!window.MathJax) {
137 if (!window.MathJax) {
135 return text;
138 return text;
136 }
139 }
137
140
138 start = end = last = null; // for tracking math delimiters
141 var math = []; // stores math strings for later
139 math = []; // stores math strings for later
142 var start;
143 var end;
144 var last;
140
145
141 // Except for extreme edge cases, this should catch precisely those pieces of the markdown
146 // Except for extreme edge cases, this should catch precisely those pieces of the markdown
142 // source that will later be turned into code spans. While MathJax will not TeXify code spans,
147 // source that will later be turned into code spans. While MathJax will not TeXify code spans,
143 // we still have to consider them at this point; the following issue has happened several times:
148 // we still have to consider them at this point; the following issue has happened several times:
144 //
149 //
145 // `$foo` and `$bar` are varibales. --> <code>$foo ` and `$bar</code> are variables.
150 // `$foo` and `$bar` are varibales. --> <code>$foo ` and `$bar</code> are variables.
146
151
147 var hasCodeSpans = /`/.test(text),
152 var hasCodeSpans = /`/.test(text),
148 de_tilde;
153 de_tilde;
149 if (hasCodeSpans) {
154 if (hasCodeSpans) {
150 text = text.replace(/~/g, "~T").replace(/(^|[^\\])(`+)([^\n]*?[^`\n])\2(?!`)/gm, function (wholematch) {
155 text = text.replace(/~/g, "~T").replace(/(^|[^\\])(`+)([^\n]*?[^`\n])\2(?!`)/gm, function (wholematch) {
151 return wholematch.replace(/\$/g, "~D");
156 return wholematch.replace(/\$/g, "~D");
152 });
157 });
153 de_tilde = function (text) { return text.replace(/~([TD])/g, function (wholematch, character) { return { T: "~", D: "$" }[character]; }) };
158 de_tilde = function (text) { return text.replace(/~([TD])/g, function (wholematch, character) { return { T: "~", D: "$" }[character]; }) };
154 } else {
159 } else {
155 de_tilde = function (text) { return text; };
160 de_tilde = function (text) { return text; };
156 }
161 }
157
162
158 blocks = IPython.utils.regex_split(text.replace(/\r\n?/g, "\n"),MATHSPLIT);
163 var blocks = IPython.utils.regex_split(text.replace(/\r\n?/g, "\n"),MATHSPLIT);
159
164
160 for (var i = 1, m = blocks.length; i < m; i += 2) {
165 for (var i = 1, m = blocks.length; i < m; i += 2) {
161 var block = blocks[i];
166 var block = blocks[i];
162 if (block.charAt(0) === "@") {
167 if (block.charAt(0) === "@") {
163 //
168 //
164 // Things that look like our math markers will get
169 // Things that look like our math markers will get
165 // stored and then retrieved along with the math.
170 // stored and then retrieved along with the math.
166 //
171 //
167 blocks[i] = "@@" + math.length + "@@";
172 blocks[i] = "@@" + math.length + "@@";
168 math.push(block);
173 math.push(block);
169 }
174 }
170 else if (start) {
175 else if (start) {
171 //
176 //
172 // If we are in math, look for the end delimiter,
177 // If we are in math, look for the end delimiter,
173 // but don't go past double line breaks, and
178 // but don't go past double line breaks, and
174 // and balance braces within the math.
179 // and balance braces within the math.
175 //
180 //
176 if (block === end) {
181 if (block === end) {
177 if (braces) {
182 if (braces) {
178 last = i
183 last = i
179 }
184 }
180 else {
185 else {
181 process_math(start, i, de_tilde)
186 var res = process_math(start, i, de_tilde, math, blocks, start, end, last);
187 blocks = res[0];
188 start = res[1];
189 end = res[2];
190 last = res[3];
182 }
191 }
183 }
192 }
184 else if (block.match(/\n.*\n/)) {
193 else if (block.match(/\n.*\n/)) {
185 if (last) {
194 if (last) {
186 i = last;
195 i = last;
187 process_math(start, i, de_tilde)
196 var res = process_math(start, i, de_tilde, math, blocks, start, end, last);
197 blocks = res[0];
198 start = res[1];
199 end = res[2];
200 last = res[3];
188 }
201 }
189 start = end = last = null;
202 start = null;
203 end = null;
204 last = null;
190 braces = 0;
205 braces = 0;
191 }
206 }
192 else if (block === "{") {
207 else if (block === "{") {
193 braces++
208 braces++
194 }
209 }
195 else if (block === "}" && braces) {
210 else if (block === "}" && braces) {
196 braces--
211 braces--
197 }
212 }
198 }
213 }
199 else {
214 else {
200 //
215 //
201 // Look for math start delimiters and when
216 // Look for math start delimiters and when
202 // found, set up the end delimiter.
217 // found, set up the end delimiter.
203 //
218 //
204 if (block === inline || block === "$$") {
219 if (block === inline || block === "$$") {
205 start = i;
220 start = i;
206 end = block;
221 end = block;
207 braces = 0;
222 braces = 0;
208 }
223 }
209 else if (block.substr(1, 5) === "begin") {
224 else if (block.substr(1, 5) === "begin") {
210 start = i;
225 start = i;
211 end = "\\end" + block.substr(6);
226 end = "\\end" + block.substr(6);
212 braces = 0;
227 braces = 0;
213 }
228 }
214 }
229 }
215 }
230 }
216 if (last) {
231 if (last) {
217 process_math(start, last, de_tilde)
232 var res = process_math(start, last, de_tilde, math, blocks, start, end, last);
233 blocks = res[0];
234 start = res[1]
235 end = res[2];
236 last = res[3];
218 }
237 }
219 return de_tilde(blocks.join(""));
238 return [de_tilde(blocks.join("")), math];
220 }
239 }
221
240
222 //
241 //
223 // Put back the math strings that were saved,
242 // Put back the math strings that were saved,
224 // and clear the math array (no need to keep it around).
243 // and clear the math array (no need to keep it around).
225 //
244 //
226 var replace_math = function (text) {
245 var replace_math = function (text, math) {
227 if (!window.MathJax) {
246 if (!window.MathJax) {
228 return text;
247 return text;
229 }
248 }
230
231 text = text.replace(/@@(\d+)@@/g, function (match, n) {
249 text = text.replace(/@@(\d+)@@/g, function (match, n) {
232 return math[n]
250 return math[n]
233 });
251 });
234 math = null;
235 return text;
252 return text;
236 }
253 }
237
254
238 return {
255 return {
239 init : init,
256 init : init,
240 process_math : process_math,
241 remove_math : remove_math,
257 remove_math : remove_math,
242 replace_math : replace_math
258 replace_math : replace_math
243 };
259 };
244
260
245 }(IPython));
261 }(IPython));
@@ -1,552 +1,554 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2008-2012 The IPython Development Team
2 // Copyright (C) 2008-2012 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // TextCell
9 // TextCell
10 //============================================================================
10 //============================================================================
11
11
12
12
13
13
14 /**
14 /**
15 A module that allow to create different type of Text Cell
15 A module that allow to create different type of Text Cell
16 @module IPython
16 @module IPython
17 @namespace IPython
17 @namespace IPython
18 */
18 */
19 var IPython = (function (IPython) {
19 var IPython = (function (IPython) {
20
20
21 // TextCell base class
21 // TextCell base class
22 var key = IPython.utils.keycodes;
22 var key = IPython.utils.keycodes;
23
23
24 /**
24 /**
25 * Construct a new TextCell, codemirror mode is by default 'htmlmixed', and cell type is 'text'
25 * Construct a new TextCell, codemirror mode is by default 'htmlmixed', and cell type is 'text'
26 * cell start as not redered.
26 * cell start as not redered.
27 *
27 *
28 * @class TextCell
28 * @class TextCell
29 * @constructor TextCell
29 * @constructor TextCell
30 * @extend IPython.Cell
30 * @extend IPython.Cell
31 * @param {object|undefined} [options]
31 * @param {object|undefined} [options]
32 * @param [options.cm_config] {object} config to pass to CodeMirror, will extend/overwrite default config
32 * @param [options.cm_config] {object} config to pass to CodeMirror, will extend/overwrite default config
33 * @param [options.placeholder] {string} default string to use when souce in empty for rendering (only use in some TextCell subclass)
33 * @param [options.placeholder] {string} default string to use when souce in empty for rendering (only use in some TextCell subclass)
34 */
34 */
35 var TextCell = function (options) {
35 var TextCell = function (options) {
36 // in all TextCell/Cell subclasses
36 // in all TextCell/Cell subclasses
37 // do not assign most of members here, just pass it down
37 // do not assign most of members here, just pass it down
38 // in the options dict potentially overwriting what you wish.
38 // in the options dict potentially overwriting what you wish.
39 // they will be assigned in the base class.
39 // they will be assigned in the base class.
40
40
41 // we cannot put this as a class key as it has handle to "this".
41 // we cannot put this as a class key as it has handle to "this".
42 var cm_overwrite_options = {
42 var cm_overwrite_options = {
43 onKeyEvent: $.proxy(this.handle_codemirror_keyevent,this)
43 onKeyEvent: $.proxy(this.handle_codemirror_keyevent,this)
44 };
44 };
45
45
46 options = this.mergeopt(TextCell,options,{cm_config:cm_overwrite_options});
46 options = this.mergeopt(TextCell,options,{cm_config:cm_overwrite_options});
47
47
48 IPython.Cell.apply(this, [options]);
48 IPython.Cell.apply(this, [options]);
49
49
50
50
51 this.rendered = false;
51 this.rendered = false;
52 this.cell_type = this.cell_type || 'text';
52 this.cell_type = this.cell_type || 'text';
53 };
53 };
54
54
55 TextCell.prototype = new IPython.Cell();
55 TextCell.prototype = new IPython.Cell();
56
56
57 TextCell.options_default = {
57 TextCell.options_default = {
58 cm_config : {
58 cm_config : {
59 extraKeys: {"Tab": "indentMore","Shift-Tab" : "indentLess"},
59 extraKeys: {"Tab": "indentMore","Shift-Tab" : "indentLess"},
60 mode: 'htmlmixed',
60 mode: 'htmlmixed',
61 lineWrapping : true,
61 lineWrapping : true,
62 }
62 }
63 };
63 };
64
64
65
65
66
66
67 /**
67 /**
68 * Create the DOM element of the TextCell
68 * Create the DOM element of the TextCell
69 * @method create_element
69 * @method create_element
70 * @private
70 * @private
71 */
71 */
72 TextCell.prototype.create_element = function () {
72 TextCell.prototype.create_element = function () {
73 IPython.Cell.prototype.create_element.apply(this, arguments);
73 IPython.Cell.prototype.create_element.apply(this, arguments);
74 var cell = $("<div>").addClass('cell text_cell border-box-sizing');
74 var cell = $("<div>").addClass('cell text_cell border-box-sizing');
75 cell.attr('tabindex','2');
75 cell.attr('tabindex','2');
76
76
77 this.celltoolbar = new IPython.CellToolbar(this);
77 this.celltoolbar = new IPython.CellToolbar(this);
78 cell.append(this.celltoolbar.element);
78 cell.append(this.celltoolbar.element);
79
79
80 var input_area = $('<div/>').addClass('text_cell_input border-box-sizing');
80 var input_area = $('<div/>').addClass('text_cell_input border-box-sizing');
81 this.code_mirror = CodeMirror(input_area.get(0), this.cm_config);
81 this.code_mirror = CodeMirror(input_area.get(0), this.cm_config);
82
82
83 // The tabindex=-1 makes this div focusable.
83 // The tabindex=-1 makes this div focusable.
84 var render_area = $('<div/>').addClass('text_cell_render border-box-sizing').
84 var render_area = $('<div/>').addClass('text_cell_render border-box-sizing').
85 addClass('rendered_html').attr('tabindex','-1');
85 addClass('rendered_html').attr('tabindex','-1');
86 cell.append(input_area).append(render_area);
86 cell.append(input_area).append(render_area);
87 this.element = cell;
87 this.element = cell;
88 };
88 };
89
89
90
90
91 /**
91 /**
92 * Bind the DOM evet to cell actions
92 * Bind the DOM evet to cell actions
93 * Need to be called after TextCell.create_element
93 * Need to be called after TextCell.create_element
94 * @private
94 * @private
95 * @method bind_event
95 * @method bind_event
96 */
96 */
97 TextCell.prototype.bind_events = function () {
97 TextCell.prototype.bind_events = function () {
98 IPython.Cell.prototype.bind_events.apply(this);
98 IPython.Cell.prototype.bind_events.apply(this);
99 var that = this;
99 var that = this;
100 this.element.keydown(function (event) {
100 this.element.keydown(function (event) {
101 if (event.which === 13 && !event.shiftKey) {
101 if (event.which === 13 && !event.shiftKey) {
102 if (that.rendered) {
102 if (that.rendered) {
103 that.edit();
103 that.edit();
104 return false;
104 return false;
105 };
105 };
106 };
106 };
107 });
107 });
108 this.element.dblclick(function () {
108 this.element.dblclick(function () {
109 that.edit();
109 that.edit();
110 });
110 });
111 };
111 };
112
112
113 /**
113 /**
114 * This method gets called in CodeMirror's onKeyDown/onKeyPress
114 * This method gets called in CodeMirror's onKeyDown/onKeyPress
115 * handlers and is used to provide custom key handling.
115 * handlers and is used to provide custom key handling.
116 *
116 *
117 * Subclass should override this method to have custom handeling
117 * Subclass should override this method to have custom handeling
118 *
118 *
119 * @method handle_codemirror_keyevent
119 * @method handle_codemirror_keyevent
120 * @param {CodeMirror} editor - The codemirror instance bound to the cell
120 * @param {CodeMirror} editor - The codemirror instance bound to the cell
121 * @param {event} event -
121 * @param {event} event -
122 * @return {Boolean} `true` if CodeMirror should ignore the event, `false` Otherwise
122 * @return {Boolean} `true` if CodeMirror should ignore the event, `false` Otherwise
123 */
123 */
124 TextCell.prototype.handle_codemirror_keyevent = function (editor, event) {
124 TextCell.prototype.handle_codemirror_keyevent = function (editor, event) {
125
125
126 if (event.keyCode === 13 && (event.shiftKey || event.ctrlKey)) {
126 if (event.keyCode === 13 && (event.shiftKey || event.ctrlKey)) {
127 // Always ignore shift-enter in CodeMirror as we handle it.
127 // Always ignore shift-enter in CodeMirror as we handle it.
128 return true;
128 return true;
129 }
129 }
130 return false;
130 return false;
131 };
131 };
132
132
133 /**
133 /**
134 * Select the current cell and trigger 'focus'
134 * Select the current cell and trigger 'focus'
135 * @method select
135 * @method select
136 */
136 */
137 TextCell.prototype.select = function () {
137 TextCell.prototype.select = function () {
138 IPython.Cell.prototype.select.apply(this);
138 IPython.Cell.prototype.select.apply(this);
139 var output = this.element.find("div.text_cell_render");
139 var output = this.element.find("div.text_cell_render");
140 output.trigger('focus');
140 output.trigger('focus');
141 };
141 };
142
142
143 /**
143 /**
144 * unselect the current cell and `render` it
144 * unselect the current cell and `render` it
145 * @method unselect
145 * @method unselect
146 */
146 */
147 TextCell.prototype.unselect = function() {
147 TextCell.prototype.unselect = function() {
148 // render on selection of another cell
148 // render on selection of another cell
149 this.render();
149 this.render();
150 IPython.Cell.prototype.unselect.apply(this);
150 IPython.Cell.prototype.unselect.apply(this);
151 };
151 };
152
152
153 /**
153 /**
154 *
154 *
155 * put the current cell in edition mode
155 * put the current cell in edition mode
156 * @method edit
156 * @method edit
157 */
157 */
158 TextCell.prototype.edit = function () {
158 TextCell.prototype.edit = function () {
159 if ( this.read_only ) return;
159 if ( this.read_only ) return;
160 if (this.rendered === true) {
160 if (this.rendered === true) {
161 var text_cell = this.element;
161 var text_cell = this.element;
162 var output = text_cell.find("div.text_cell_render");
162 var output = text_cell.find("div.text_cell_render");
163 output.hide();
163 output.hide();
164 text_cell.find('div.text_cell_input').show();
164 text_cell.find('div.text_cell_input').show();
165 this.code_mirror.refresh();
165 this.code_mirror.refresh();
166 this.code_mirror.focus();
166 this.code_mirror.focus();
167 // We used to need an additional refresh() after the focus, but
167 // We used to need an additional refresh() after the focus, but
168 // it appears that this has been fixed in CM. This bug would show
168 // it appears that this has been fixed in CM. This bug would show
169 // up on FF when a newly loaded markdown cell was edited.
169 // up on FF when a newly loaded markdown cell was edited.
170 this.rendered = false;
170 this.rendered = false;
171 if (this.get_text() === this.placeholder) {
171 if (this.get_text() === this.placeholder) {
172 this.set_text('');
172 this.set_text('');
173 this.refresh();
173 this.refresh();
174 }
174 }
175 }
175 }
176 };
176 };
177
177
178
178
179 /**
179 /**
180 * Empty, Subclasses must define render.
180 * Empty, Subclasses must define render.
181 * @method render
181 * @method render
182 */
182 */
183 TextCell.prototype.render = function () {};
183 TextCell.prototype.render = function () {};
184
184
185
185
186 /**
186 /**
187 * setter: {{#crossLink "TextCell/set_text"}}{{/crossLink}}
187 * setter: {{#crossLink "TextCell/set_text"}}{{/crossLink}}
188 * @method get_text
188 * @method get_text
189 * @retrun {string} CodeMirror current text value
189 * @retrun {string} CodeMirror current text value
190 */
190 */
191 TextCell.prototype.get_text = function() {
191 TextCell.prototype.get_text = function() {
192 return this.code_mirror.getValue();
192 return this.code_mirror.getValue();
193 };
193 };
194
194
195 /**
195 /**
196 * @param {string} text - Codemiror text value
196 * @param {string} text - Codemiror text value
197 * @see TextCell#get_text
197 * @see TextCell#get_text
198 * @method set_text
198 * @method set_text
199 * */
199 * */
200 TextCell.prototype.set_text = function(text) {
200 TextCell.prototype.set_text = function(text) {
201 this.code_mirror.setValue(text);
201 this.code_mirror.setValue(text);
202 this.code_mirror.refresh();
202 this.code_mirror.refresh();
203 };
203 };
204
204
205 /**
205 /**
206 * setter :{{#crossLink "TextCell/set_rendered"}}{{/crossLink}}
206 * setter :{{#crossLink "TextCell/set_rendered"}}{{/crossLink}}
207 * @method get_rendered
207 * @method get_rendered
208 * @return {html} html of rendered element
208 * @return {html} html of rendered element
209 * */
209 * */
210 TextCell.prototype.get_rendered = function() {
210 TextCell.prototype.get_rendered = function() {
211 return this.element.find('div.text_cell_render').html();
211 return this.element.find('div.text_cell_render').html();
212 };
212 };
213
213
214 /**
214 /**
215 * @method set_rendered
215 * @method set_rendered
216 */
216 */
217 TextCell.prototype.set_rendered = function(text) {
217 TextCell.prototype.set_rendered = function(text) {
218 this.element.find('div.text_cell_render').html(text);
218 this.element.find('div.text_cell_render').html(text);
219 };
219 };
220
220
221 /**
221 /**
222 * not deprecated, but implementation wrong
222 * not deprecated, but implementation wrong
223 * @method at_top
223 * @method at_top
224 * @deprecated
224 * @deprecated
225 * @return {Boolean} true is cell rendered, false otherwise
225 * @return {Boolean} true is cell rendered, false otherwise
226 * I doubt this is what it is supposed to do
226 * I doubt this is what it is supposed to do
227 * this implementation is completly false
227 * this implementation is completly false
228 */
228 */
229 TextCell.prototype.at_top = function () {
229 TextCell.prototype.at_top = function () {
230 if (this.rendered) {
230 if (this.rendered) {
231 return true;
231 return true;
232 } else {
232 } else {
233 return false;
233 return false;
234 }
234 }
235 };
235 };
236
236
237
237
238 /**
238 /**
239 * not deprecated, but implementation wrong
239 * not deprecated, but implementation wrong
240 * @method at_bottom
240 * @method at_bottom
241 * @deprecated
241 * @deprecated
242 * @return {Boolean} true is cell rendered, false otherwise
242 * @return {Boolean} true is cell rendered, false otherwise
243 * I doubt this is what it is supposed to do
243 * I doubt this is what it is supposed to do
244 * this implementation is completly false
244 * this implementation is completly false
245 * */
245 * */
246 TextCell.prototype.at_bottom = function () {
246 TextCell.prototype.at_bottom = function () {
247 if (this.rendered) {
247 if (this.rendered) {
248 return true;
248 return true;
249 } else {
249 } else {
250 return false;
250 return false;
251 }
251 }
252 };
252 };
253
253
254 /**
254 /**
255 * Create Text cell from JSON
255 * Create Text cell from JSON
256 * @param {json} data - JSON serialized text-cell
256 * @param {json} data - JSON serialized text-cell
257 * @method fromJSON
257 * @method fromJSON
258 */
258 */
259 TextCell.prototype.fromJSON = function (data) {
259 TextCell.prototype.fromJSON = function (data) {
260 IPython.Cell.prototype.fromJSON.apply(this, arguments);
260 IPython.Cell.prototype.fromJSON.apply(this, arguments);
261 if (data.cell_type === this.cell_type) {
261 if (data.cell_type === this.cell_type) {
262 if (data.source !== undefined) {
262 if (data.source !== undefined) {
263 this.set_text(data.source);
263 this.set_text(data.source);
264 // make this value the starting point, so that we can only undo
264 // make this value the starting point, so that we can only undo
265 // to this state, instead of a blank cell
265 // to this state, instead of a blank cell
266 this.code_mirror.clearHistory();
266 this.code_mirror.clearHistory();
267 this.set_rendered(data.rendered || '');
267 this.set_rendered(data.rendered || '');
268 this.rendered = false;
268 this.rendered = false;
269 this.render();
269 this.render();
270 }
270 }
271 }
271 }
272 };
272 };
273
273
274 /** Generate JSON from cell
274 /** Generate JSON from cell
275 * @return {object} cell data serialised to json
275 * @return {object} cell data serialised to json
276 */
276 */
277 TextCell.prototype.toJSON = function () {
277 TextCell.prototype.toJSON = function () {
278 var data = IPython.Cell.prototype.toJSON.apply(this);
278 var data = IPython.Cell.prototype.toJSON.apply(this);
279 data.cell_type = this.cell_type;
279 data.cell_type = this.cell_type;
280 data.source = this.get_text();
280 data.source = this.get_text();
281 return data;
281 return data;
282 };
282 };
283
283
284
284
285 /**
285 /**
286 * @class MarkdownCell
286 * @class MarkdownCell
287 * @constructor MarkdownCell
287 * @constructor MarkdownCell
288 * @extends IPython.HTMLCell
288 * @extends IPython.HTMLCell
289 */
289 */
290 var MarkdownCell = function (options) {
290 var MarkdownCell = function (options) {
291 var options = options || {};
291 var options = options || {};
292
292
293 options = this.mergeopt(MarkdownCell,options);
293 options = this.mergeopt(MarkdownCell,options);
294 TextCell.apply(this, [options]);
294 TextCell.apply(this, [options]);
295
295
296 this.cell_type = 'markdown';
296 this.cell_type = 'markdown';
297 };
297 };
298
298
299 MarkdownCell.options_default = {
299 MarkdownCell.options_default = {
300 cm_config: {
300 cm_config: {
301 mode: 'gfm'
301 mode: 'gfm'
302 },
302 },
303 placeholder: "Type *Markdown* and LaTeX: $\\alpha^2$"
303 placeholder: "Type *Markdown* and LaTeX: $\\alpha^2$"
304 }
304 }
305
305
306
306
307
307
308
308
309 MarkdownCell.prototype = new TextCell();
309 MarkdownCell.prototype = new TextCell();
310
310
311 /**
311 /**
312 * @method render
312 * @method render
313 */
313 */
314 MarkdownCell.prototype.render = function () {
314 MarkdownCell.prototype.render = function () {
315 if (this.rendered === false) {
315 if (this.rendered === false) {
316 var text = this.get_text();
316 var text = this.get_text();
317 if (text === "") { text = this.placeholder; }
317 if (text === "") { text = this.placeholder; }
318 text = IPython.mathjaxutils.remove_math(text);
318 text_math = IPython.mathjaxutils.remove_math(text);
319 text = text_math[0]
320 math = text_math[1]
319 var html = marked.parser(marked.lexer(text));
321 var html = marked.parser(marked.lexer(text));
320 html = $(IPython.mathjaxutils.replace_math(html));
322 html = $(IPython.mathjaxutils.replace_math(html, math));
321 // links in markdown cells should open in new tabs
323 // links in markdown cells should open in new tabs
322 html.find("a[href]").attr("target", "_blank");
324 html.find("a[href]").attr("target", "_blank");
323 try {
325 try {
324 this.set_rendered(html);
326 this.set_rendered(html);
325 } catch (e) {
327 } catch (e) {
326 console.log("Error running Javascript in Markdown:");
328 console.log("Error running Javascript in Markdown:");
327 console.log(e);
329 console.log(e);
328 this.set_rendered($("<div/>").addClass("js-error").html(
330 this.set_rendered($("<div/>").addClass("js-error").html(
329 "Error rendering Markdown!<br/>" + e.toString())
331 "Error rendering Markdown!<br/>" + e.toString())
330 );
332 );
331 }
333 }
332 this.element.find('div.text_cell_input').hide();
334 this.element.find('div.text_cell_input').hide();
333 this.element.find("div.text_cell_render").show();
335 this.element.find("div.text_cell_render").show();
334 this.typeset()
336 this.typeset()
335 this.rendered = true;
337 this.rendered = true;
336 }
338 }
337 };
339 };
338
340
339
341
340 // RawCell
342 // RawCell
341
343
342 /**
344 /**
343 * @class RawCell
345 * @class RawCell
344 * @constructor RawCell
346 * @constructor RawCell
345 * @extends IPython.TextCell
347 * @extends IPython.TextCell
346 */
348 */
347 var RawCell = function (options) {
349 var RawCell = function (options) {
348
350
349 options = this.mergeopt(RawCell,options)
351 options = this.mergeopt(RawCell,options)
350 TextCell.apply(this, [options]);
352 TextCell.apply(this, [options]);
351
353
352 this.cell_type = 'raw';
354 this.cell_type = 'raw';
353
355
354 var that = this
356 var that = this
355 this.element.focusout(
357 this.element.focusout(
356 function() { that.auto_highlight(); }
358 function() { that.auto_highlight(); }
357 );
359 );
358 };
360 };
359
361
360 RawCell.options_default = {
362 RawCell.options_default = {
361 placeholder : "Type plain text and LaTeX: $\\alpha^2$"
363 placeholder : "Type plain text and LaTeX: $\\alpha^2$"
362 };
364 };
363
365
364
366
365
367
366 RawCell.prototype = new TextCell();
368 RawCell.prototype = new TextCell();
367
369
368 /**
370 /**
369 * Trigger autodetection of highlight scheme for current cell
371 * Trigger autodetection of highlight scheme for current cell
370 * @method auto_highlight
372 * @method auto_highlight
371 */
373 */
372 RawCell.prototype.auto_highlight = function () {
374 RawCell.prototype.auto_highlight = function () {
373 this._auto_highlight(IPython.config.raw_cell_highlight);
375 this._auto_highlight(IPython.config.raw_cell_highlight);
374 };
376 };
375
377
376 /** @method render **/
378 /** @method render **/
377 RawCell.prototype.render = function () {
379 RawCell.prototype.render = function () {
378 this.rendered = true;
380 this.rendered = true;
379 this.edit();
381 this.edit();
380 };
382 };
381
383
382
384
383 /** @method handle_codemirror_keyevent **/
385 /** @method handle_codemirror_keyevent **/
384 RawCell.prototype.handle_codemirror_keyevent = function (editor, event) {
386 RawCell.prototype.handle_codemirror_keyevent = function (editor, event) {
385
387
386 var that = this;
388 var that = this;
387 if (event.which === key.UPARROW && event.type === 'keydown') {
389 if (event.which === key.UPARROW && event.type === 'keydown') {
388 // If we are not at the top, let CM handle the up arrow and
390 // If we are not at the top, let CM handle the up arrow and
389 // prevent the global keydown handler from handling it.
391 // prevent the global keydown handler from handling it.
390 if (!that.at_top()) {
392 if (!that.at_top()) {
391 event.stop();
393 event.stop();
392 return false;
394 return false;
393 } else {
395 } else {
394 return true;
396 return true;
395 };
397 };
396 } else if (event.which === key.DOWNARROW && event.type === 'keydown') {
398 } else if (event.which === key.DOWNARROW && event.type === 'keydown') {
397 // If we are not at the bottom, let CM handle the down arrow and
399 // If we are not at the bottom, let CM handle the down arrow and
398 // prevent the global keydown handler from handling it.
400 // prevent the global keydown handler from handling it.
399 if (!that.at_bottom()) {
401 if (!that.at_bottom()) {
400 event.stop();
402 event.stop();
401 return false;
403 return false;
402 } else {
404 } else {
403 return true;
405 return true;
404 };
406 };
405 };
407 };
406 return false;
408 return false;
407 };
409 };
408
410
409 /** @method select **/
411 /** @method select **/
410 RawCell.prototype.select = function () {
412 RawCell.prototype.select = function () {
411 IPython.Cell.prototype.select.apply(this);
413 IPython.Cell.prototype.select.apply(this);
412 this.code_mirror.refresh();
414 this.code_mirror.refresh();
413 this.code_mirror.focus();
415 this.code_mirror.focus();
414 };
416 };
415
417
416 /** @method at_top **/
418 /** @method at_top **/
417 RawCell.prototype.at_top = function () {
419 RawCell.prototype.at_top = function () {
418 var cursor = this.code_mirror.getCursor();
420 var cursor = this.code_mirror.getCursor();
419 if (cursor.line === 0 && cursor.ch === 0) {
421 if (cursor.line === 0 && cursor.ch === 0) {
420 return true;
422 return true;
421 } else {
423 } else {
422 return false;
424 return false;
423 }
425 }
424 };
426 };
425
427
426
428
427 /** @method at_bottom **/
429 /** @method at_bottom **/
428 RawCell.prototype.at_bottom = function () {
430 RawCell.prototype.at_bottom = function () {
429 var cursor = this.code_mirror.getCursor();
431 var cursor = this.code_mirror.getCursor();
430 if (cursor.line === (this.code_mirror.lineCount()-1) && cursor.ch === this.code_mirror.getLine(cursor.line).length) {
432 if (cursor.line === (this.code_mirror.lineCount()-1) && cursor.ch === this.code_mirror.getLine(cursor.line).length) {
431 return true;
433 return true;
432 } else {
434 } else {
433 return false;
435 return false;
434 }
436 }
435 };
437 };
436
438
437
439
438 /**
440 /**
439 * @class HeadingCell
441 * @class HeadingCell
440 * @extends IPython.TextCell
442 * @extends IPython.TextCell
441 */
443 */
442
444
443 /**
445 /**
444 * @constructor HeadingCell
446 * @constructor HeadingCell
445 * @extends IPython.TextCell
447 * @extends IPython.TextCell
446 */
448 */
447 var HeadingCell = function (options) {
449 var HeadingCell = function (options) {
448
450
449 options = this.mergeopt(HeadingCell,options)
451 options = this.mergeopt(HeadingCell,options)
450 TextCell.apply(this, [options]);
452 TextCell.apply(this, [options]);
451
453
452 /**
454 /**
453 * heading level of the cell, use getter and setter to access
455 * heading level of the cell, use getter and setter to access
454 * @property level
456 * @property level
455 */
457 */
456 this.level = 1;
458 this.level = 1;
457 this.cell_type = 'heading';
459 this.cell_type = 'heading';
458 };
460 };
459
461
460 HeadingCell.options_default = {
462 HeadingCell.options_default = {
461 placeholder: "Type Heading Here"
463 placeholder: "Type Heading Here"
462 };
464 };
463
465
464 HeadingCell.prototype = new TextCell();
466 HeadingCell.prototype = new TextCell();
465
467
466 /** @method fromJSON */
468 /** @method fromJSON */
467 HeadingCell.prototype.fromJSON = function (data) {
469 HeadingCell.prototype.fromJSON = function (data) {
468 if (data.level != undefined){
470 if (data.level != undefined){
469 this.level = data.level;
471 this.level = data.level;
470 }
472 }
471 TextCell.prototype.fromJSON.apply(this, arguments);
473 TextCell.prototype.fromJSON.apply(this, arguments);
472 };
474 };
473
475
474
476
475 /** @method toJSON */
477 /** @method toJSON */
476 HeadingCell.prototype.toJSON = function () {
478 HeadingCell.prototype.toJSON = function () {
477 var data = TextCell.prototype.toJSON.apply(this);
479 var data = TextCell.prototype.toJSON.apply(this);
478 data.level = this.get_level();
480 data.level = this.get_level();
479 return data;
481 return data;
480 };
482 };
481
483
482
484
483 /**
485 /**
484 * Change heading level of cell, and re-render
486 * Change heading level of cell, and re-render
485 * @method set_level
487 * @method set_level
486 */
488 */
487 HeadingCell.prototype.set_level = function (level) {
489 HeadingCell.prototype.set_level = function (level) {
488 this.level = level;
490 this.level = level;
489 if (this.rendered) {
491 if (this.rendered) {
490 this.rendered = false;
492 this.rendered = false;
491 this.render();
493 this.render();
492 };
494 };
493 };
495 };
494
496
495 /** The depth of header cell, based on html (h1 to h6)
497 /** The depth of header cell, based on html (h1 to h6)
496 * @method get_level
498 * @method get_level
497 * @return {integer} level - for 1 to 6
499 * @return {integer} level - for 1 to 6
498 */
500 */
499 HeadingCell.prototype.get_level = function () {
501 HeadingCell.prototype.get_level = function () {
500 return this.level;
502 return this.level;
501 };
503 };
502
504
503
505
504 HeadingCell.prototype.set_rendered = function (html) {
506 HeadingCell.prototype.set_rendered = function (html) {
505 this.element.find("div.text_cell_render").html(html);
507 this.element.find("div.text_cell_render").html(html);
506 };
508 };
507
509
508
510
509 HeadingCell.prototype.get_rendered = function () {
511 HeadingCell.prototype.get_rendered = function () {
510 var r = this.element.find("div.text_cell_render");
512 var r = this.element.find("div.text_cell_render");
511 return r.children().first().html();
513 return r.children().first().html();
512 };
514 };
513
515
514
516
515 HeadingCell.prototype.render = function () {
517 HeadingCell.prototype.render = function () {
516 if (this.rendered === false) {
518 if (this.rendered === false) {
517 var text = this.get_text();
519 var text = this.get_text();
518 // Markdown headings must be a single line
520 // Markdown headings must be a single line
519 text = text.replace(/\n/g, ' ');
521 text = text.replace(/\n/g, ' ');
520 if (text === "") { text = this.placeholder; }
522 if (text === "") { text = this.placeholder; }
521 text = Array(this.level + 1).join("#") + " " + text;
523 text = Array(this.level + 1).join("#") + " " + text;
522 text = IPython.mathjaxutils.remove_math(text);
524 text = IPython.mathjaxutils.remove_math(text);
523 var html = marked.parser(marked.lexer(text));
525 var html = marked.parser(marked.lexer(text));
524 var h = $(IPython.mathjaxutils.replace_math(html));
526 var h = $(IPython.mathjaxutils.replace_math(html));
525 // add id and linkback anchor
527 // add id and linkback anchor
526 var hash = h.text().replace(/ /g, '-');
528 var hash = h.text().replace(/ /g, '-');
527 h.attr('id', hash);
529 h.attr('id', hash);
528 h.append(
530 h.append(
529 $('<a/>')
531 $('<a/>')
530 .addClass('anchor-link')
532 .addClass('anchor-link')
531 .attr('href', '#' + hash)
533 .attr('href', '#' + hash)
532 .text('¶')
534 .text('¶')
533 );
535 );
534
536
535 this.set_rendered(h);
537 this.set_rendered(h);
536 this.typeset();
538 this.typeset();
537 this.element.find('div.text_cell_input').hide();
539 this.element.find('div.text_cell_input').hide();
538 this.element.find("div.text_cell_render").show();
540 this.element.find("div.text_cell_render").show();
539 this.rendered = true;
541 this.rendered = true;
540 };
542 };
541 };
543 };
542
544
543 IPython.TextCell = TextCell;
545 IPython.TextCell = TextCell;
544 IPython.MarkdownCell = MarkdownCell;
546 IPython.MarkdownCell = MarkdownCell;
545 IPython.RawCell = RawCell;
547 IPython.RawCell = RawCell;
546 IPython.HeadingCell = HeadingCell;
548 IPython.HeadingCell = HeadingCell;
547
549
548
550
549 return IPython;
551 return IPython;
550
552
551 }(IPython));
553 }(IPython));
552
554
General Comments 0
You need to be logged in to leave comments. Login now