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