##// END OF EJS Templates
minor heading-link tweaks...
MinRK -
Show More
@@ -1,560 +1,561
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 A module that allow to create different type of Text Cell
14 14 @module IPython
15 15 @namespace IPython
16 16 */
17 17 var IPython = (function (IPython) {
18 18
19 19 // TextCell base class
20 20 var key = IPython.utils.keycodes;
21 21
22 22 /**
23 23 * Construct a new TextCell, codemirror mode is by default 'htmlmixed', and cell type is 'text'
24 24 * cell start as not redered.
25 25 *
26 26 * @class TextCell
27 27 * @constructor TextCell
28 28 * @extend Ipython.Cell
29 29 * @param {object|undefined} [options]
30 30 * @param [options.cm_config] {object} config to pass to CodeMirror, will extend/overwrite default config
31 31 */
32 32 var TextCell = function (options) {
33 33 this.code_mirror_mode = this.code_mirror_mode || 'htmlmixed';
34 34 var options = options || {};
35 35
36 36 var cm_overwrite_options = {
37 37 extraKeys: {"Tab": "indentMore","Shift-Tab" : "indentLess"},
38 38 onKeyEvent: $.proxy(this.handle_codemirror_keyevent,this)
39 39 };
40 40
41 41 var arg_cm_options = options.cm_options || {};
42 42 var cm_config = $.extend({},TextCell.cm_default, arg_cm_options, cm_overwrite_options);
43 43
44 44 var options = {};
45 45 options.cm_config = cm_config;
46 46
47 47
48 48 IPython.Cell.apply(this, [options]);
49 49 this.rendered = false;
50 50 this.cell_type = this.cell_type || 'text';
51 51 };
52 52
53 53 TextCell.cm_default = {
54 54 mode: this.code_mirror_mode,
55 55 theme: 'default',
56 56 value: this.placeholder,
57 57 lineWrapping : true,
58 58 }
59 59
60 60
61 61 TextCell.prototype = new IPython.Cell();
62 62
63 63 /**
64 64 * Create the DOM element of the TextCell
65 65 * @method create_element
66 66 * @private
67 67 */
68 68 TextCell.prototype.create_element = function () {
69 69 IPython.Cell.prototype.create_element.apply(this, arguments);
70 70 var cell = $("<div>").addClass('cell text_cell border-box-sizing vbox');
71 71 cell.attr('tabindex','2');
72 72
73 73 this.celltoolbar = new IPython.CellToolbar(this);
74 74 cell.append(this.celltoolbar.element);
75 75
76 76 var input_area = $('<div/>').addClass('text_cell_input border-box-sizing');
77 77 this.code_mirror = CodeMirror(input_area.get(0), this.cm_config);
78 78 // The tabindex=-1 makes this div focusable.
79 79 var render_area = $('<div/>').addClass('text_cell_render border-box-sizing').
80 80 addClass('rendered_html').attr('tabindex','-1');
81 81 cell.append(input_area).append(render_area);
82 82 this.element = cell;
83 83 };
84 84
85 85
86 86 /**
87 87 * Bind the DOM evet to cell actions
88 88 * Need to be called after TextCell.create_element
89 89 * @private
90 90 * @method bind_event
91 91 */
92 92 TextCell.prototype.bind_events = function () {
93 93 IPython.Cell.prototype.bind_events.apply(this);
94 94 var that = this;
95 95 this.element.keydown(function (event) {
96 96 if (event.which === 13 && !event.shiftKey) {
97 97 if (that.rendered) {
98 98 that.edit();
99 99 return false;
100 100 };
101 101 };
102 102 });
103 103 this.element.dblclick(function () {
104 104 that.edit();
105 105 });
106 106 };
107 107
108 108 /**
109 109 * This method gets called in CodeMirror's onKeyDown/onKeyPress
110 110 * handlers and is used to provide custom key handling.
111 111 *
112 112 * Subclass should override this method to have custom handeling
113 113 *
114 114 * @method handle_codemirror_keyevent
115 115 * @param {CodeMirror} editor - The codemirror instance bound to the cell
116 116 * @param {event} event -
117 117 * @return {Boolean} `true` if CodeMirror should ignore the event, `false` Otherwise
118 118 */
119 119 TextCell.prototype.handle_codemirror_keyevent = function (editor, event) {
120 120
121 121 if (event.keyCode === 13 && (event.shiftKey || event.ctrlKey)) {
122 122 // Always ignore shift-enter in CodeMirror as we handle it.
123 123 return true;
124 124 }
125 125 return false;
126 126 };
127 127
128 128 /**
129 129 * Select the current cell and trigger 'focus'
130 130 * @method select
131 131 */
132 132 TextCell.prototype.select = function () {
133 133 IPython.Cell.prototype.select.apply(this);
134 134 var output = this.element.find("div.text_cell_render");
135 135 output.trigger('focus');
136 136 };
137 137
138 138 /**
139 139 * unselect the current cell and `render` it
140 140 * @method unselect
141 141 */
142 142 TextCell.prototype.unselect = function() {
143 143 // render on selection of another cell
144 144 this.render();
145 145 IPython.Cell.prototype.unselect.apply(this);
146 146 };
147 147
148 148 /**
149 149 *
150 150 * put the current cell in edition mode
151 151 * @method edit
152 152 */
153 153 TextCell.prototype.edit = function () {
154 154 if ( this.read_only ) return;
155 155 if (this.rendered === true) {
156 156 var text_cell = this.element;
157 157 var output = text_cell.find("div.text_cell_render");
158 158 output.hide();
159 159 text_cell.find('div.text_cell_input').show();
160 160 this.code_mirror.refresh();
161 161 this.code_mirror.focus();
162 162 // We used to need an additional refresh() after the focus, but
163 163 // it appears that this has been fixed in CM. This bug would show
164 164 // up on FF when a newly loaded markdown cell was edited.
165 165 this.rendered = false;
166 166 if (this.get_text() === this.placeholder) {
167 167 this.set_text('');
168 168 this.refresh();
169 169 }
170 170 }
171 171 };
172 172
173 173
174 174 /**
175 175 * Empty, Subclasses must define render.
176 176 * @method render
177 177 */
178 178 TextCell.prototype.render = function () {};
179 179
180 180
181 181 /**
182 182 * setter: {{#crossLink "TextCell/set_text"}}{{/crossLink}}
183 183 * @method get_text
184 184 * @retrun {string} CodeMirror current text value
185 185 */
186 186 TextCell.prototype.get_text = function() {
187 187 return this.code_mirror.getValue();
188 188 };
189 189
190 190 /**
191 191 * @param {string} text - Codemiror text value
192 192 * @see TextCell#get_text
193 193 * @method set_text
194 194 * */
195 195 TextCell.prototype.set_text = function(text) {
196 196 this.code_mirror.setValue(text);
197 197 this.code_mirror.refresh();
198 198 };
199 199
200 200 /**
201 201 * setter :{{#crossLink "TextCell/set_rendered"}}{{/crossLink}}
202 202 * @method get_rendered
203 203 * @return {html} html of rendered element
204 204 * */
205 205 TextCell.prototype.get_rendered = function() {
206 206 return this.element.find('div.text_cell_render').html();
207 207 };
208 208
209 209 /**
210 210 * @method set_rendered
211 211 */
212 212 TextCell.prototype.set_rendered = function(text) {
213 213 this.element.find('div.text_cell_render').html(text);
214 214 };
215 215
216 216 /**
217 217 * not deprecated, but implementation wrong
218 218 * @method at_top
219 219 * @deprecated
220 220 * @return {Boolean} true is cell rendered, false otherwise
221 221 * I doubt this is what it is supposed to do
222 222 * this implementation is completly false
223 223 */
224 224 TextCell.prototype.at_top = function () {
225 225 if (this.rendered) {
226 226 return true;
227 227 } else {
228 228 return false;
229 229 }
230 230 };
231 231
232 232
233 233 /**
234 234 * not deprecated, but implementation wrong
235 235 * @method at_bottom
236 236 * @deprecated
237 237 * @return {Boolean} true is cell rendered, false otherwise
238 238 * I doubt this is what it is supposed to do
239 239 * this implementation is completly false
240 240 * */
241 241 TextCell.prototype.at_bottom = function () {
242 242 if (this.rendered) {
243 243 return true;
244 244 } else {
245 245 return false;
246 246 }
247 247 };
248 248
249 249 /**
250 250 * Create Text cell from JSON
251 251 * @param {json} data - JSON serialized text-cell
252 252 * @method fromJSON
253 253 */
254 254 TextCell.prototype.fromJSON = function (data) {
255 255 IPython.Cell.prototype.fromJSON.apply(this, arguments);
256 256 if (data.cell_type === this.cell_type) {
257 257 if (data.source !== undefined) {
258 258 this.set_text(data.source);
259 259 // make this value the starting point, so that we can only undo
260 260 // to this state, instead of a blank cell
261 261 this.code_mirror.clearHistory();
262 262 this.set_rendered(data.rendered || '');
263 263 this.rendered = false;
264 264 this.render();
265 265 }
266 266 }
267 267 };
268 268
269 269 /** Generate JSON from cell
270 270 * @return {object} cell data serialised to json
271 271 */
272 272 TextCell.prototype.toJSON = function () {
273 273 var data = IPython.Cell.prototype.toJSON.apply(this);
274 274 data.cell_type = this.cell_type;
275 275 data.source = this.get_text();
276 276 return data;
277 277 };
278 278
279 279
280 280 /**
281 281 * @constructor HtmlCell
282 282 * @class HtmlCell
283 283 * @extends Ipython.TextCell
284 284 */
285 285 var HTMLCell = function () {
286 286 this.placeholder = "Type <strong>HTML</strong> and LaTeX: $\\alpha^2$";
287 287 IPython.TextCell.apply(this, arguments);
288 288 this.cell_type = 'html';
289 289 };
290 290
291 291
292 292 HTMLCell.prototype = new TextCell();
293 293
294 294 /**
295 295 * @method render
296 296 */
297 297 HTMLCell.prototype.render = function () {
298 298 if (this.rendered === false) {
299 299 var text = this.get_text();
300 300 if (text === "") { text = this.placeholder; }
301 301 this.set_rendered(text);
302 302 this.typeset();
303 303 this.element.find('div.text_cell_input').hide();
304 304 this.element.find("div.text_cell_render").show();
305 305 this.rendered = true;
306 306 }
307 307 };
308 308
309 309
310 310 /**
311 311 * @class MarkdownCell
312 312 * @constructor MarkdownCell
313 313 * @extends Ipython.HtmlCell
314 314 */
315 315 var MarkdownCell = function () {
316 316 this.placeholder = "Type *Markdown* and LaTeX: $\\alpha^2$";
317 317 IPython.TextCell.apply(this, arguments);
318 318 this.cell_type = 'markdown';
319 319 };
320 320
321 321
322 322 MarkdownCell.prototype = new TextCell();
323 323
324 324 /**
325 325 * @method render
326 326 */
327 327 MarkdownCell.prototype.render = function () {
328 328 if (this.rendered === false) {
329 329 var text = this.get_text();
330 330 if (text === "") { text = this.placeholder; }
331 331 text = IPython.mathjaxutils.remove_math(text)
332 332 var html = IPython.markdown_converter.makeHtml(text);
333 333 html = IPython.mathjaxutils.replace_math(html)
334 334 try {
335 335 this.set_rendered(html);
336 336 } catch (e) {
337 337 console.log("Error running Javascript in Markdown:");
338 338 console.log(e);
339 339 this.set_rendered($("<div/>").addClass("js-error").html(
340 340 "Error rendering Markdown!<br/>" + e.toString())
341 341 );
342 342 }
343 343 this.element.find('div.text_cell_input').hide();
344 344 this.element.find("div.text_cell_render").show();
345 345 var code_snippets = this.element.find("pre > code");
346 346 code_snippets.replaceWith(function () {
347 347 var code = $(this).html();
348 348 /* Substitute br for newlines and &nbsp; for spaces
349 349 before highlighting, since prettify doesn't
350 350 preserve those on all browsers */
351 351 code = code.replace(/(\r\n|\n|\r)/gm, "<br/>");
352 352 code = code.replace(/ /gm, '&nbsp;');
353 353 code = prettyPrintOne(code);
354 354
355 355 return '<code class="prettyprint">' + code + '</code>';
356 356 });
357 357 this.typeset()
358 358 this.rendered = true;
359 359 }
360 360 };
361 361
362 362
363 363 // RawCell
364 364
365 365 /**
366 366 * @class RawCell
367 367 * @constructor RawCell
368 368 * @extends Ipython.TextCell
369 369 */
370 370 var RawCell = function () {
371 371 this.placeholder = "Type plain text and LaTeX: $\\alpha^2$";
372 372 this.code_mirror_mode = 'rst';
373 373 IPython.TextCell.apply(this, arguments);
374 374 this.cell_type = 'raw';
375 375 var that = this
376 376
377 377 this.element.focusout(
378 378 function() { that.auto_highlight(); }
379 379 );
380 380 };
381 381
382 382
383 383 RawCell.prototype = new TextCell();
384 384
385 385 /**
386 386 * Trigger autodetection of highlight scheme for current cell
387 387 * @method auto_highlight
388 388 */
389 389 RawCell.prototype.auto_highlight = function () {
390 390 this._auto_highlight(IPython.config.raw_cell_highlight);
391 391 };
392 392
393 393 /** @method render **/
394 394 RawCell.prototype.render = function () {
395 395 this.rendered = true;
396 396 this.edit();
397 397 };
398 398
399 399
400 400 /** @method handle_codemirror_keyevent **/
401 401 RawCell.prototype.handle_codemirror_keyevent = function (editor, event) {
402 402
403 403 var that = this;
404 404 if (event.which === key.UPARROW && event.type === 'keydown') {
405 405 // If we are not at the top, let CM handle the up arrow and
406 406 // prevent the global keydown handler from handling it.
407 407 if (!that.at_top()) {
408 408 event.stop();
409 409 return false;
410 410 } else {
411 411 return true;
412 412 };
413 413 } else if (event.which === key.DOWNARROW && event.type === 'keydown') {
414 414 // If we are not at the bottom, let CM handle the down arrow and
415 415 // prevent the global keydown handler from handling it.
416 416 if (!that.at_bottom()) {
417 417 event.stop();
418 418 return false;
419 419 } else {
420 420 return true;
421 421 };
422 422 };
423 423 return false;
424 424 };
425 425
426 426 /** @method select **/
427 427 RawCell.prototype.select = function () {
428 428 IPython.Cell.prototype.select.apply(this);
429 429 this.code_mirror.refresh();
430 430 this.code_mirror.focus();
431 431 };
432 432
433 433 /** @method at_top **/
434 434 RawCell.prototype.at_top = function () {
435 435 var cursor = this.code_mirror.getCursor();
436 436 if (cursor.line === 0 && cursor.ch === 0) {
437 437 return true;
438 438 } else {
439 439 return false;
440 440 }
441 441 };
442 442
443 443
444 444 /** @method at_bottom **/
445 445 RawCell.prototype.at_bottom = function () {
446 446 var cursor = this.code_mirror.getCursor();
447 447 if (cursor.line === (this.code_mirror.lineCount()-1) && cursor.ch === this.code_mirror.getLine(cursor.line).length) {
448 448 return true;
449 449 } else {
450 450 return false;
451 451 }
452 452 };
453 453
454 454
455 455 /**
456 456 * @class HeadingCell
457 457 * @extends Ipython.TextCell
458 458 */
459 459
460 460 /**
461 461 * @constructor HeadingCell
462 462 * @extends Ipython.TextCell
463 463 */
464 464 var HeadingCell = function () {
465 465 this.placeholder = "Type Heading Here";
466 466 IPython.TextCell.apply(this, arguments);
467 467 /**
468 468 * heading level of the cell, use getter and setter to access
469 469 * @property level
470 470 */
471 471 this.level = 1;
472 472 this.cell_type = 'heading';
473 473 };
474 474
475 475
476 476 HeadingCell.prototype = new TextCell();
477 477
478 478 /** @method fromJSON */
479 479 HeadingCell.prototype.fromJSON = function (data) {
480 480 if (data.level != undefined){
481 481 this.level = data.level;
482 482 }
483 483 IPython.TextCell.prototype.fromJSON.apply(this, arguments);
484 484 };
485 485
486 486
487 487 /** @method toJSON */
488 488 HeadingCell.prototype.toJSON = function () {
489 489 var data = IPython.TextCell.prototype.toJSON.apply(this);
490 490 data.level = this.get_level();
491 491 return data;
492 492 };
493 493
494 494
495 495 /**
496 496 * Change heading level of cell, and re-render
497 497 * @method set_level
498 498 */
499 499 HeadingCell.prototype.set_level = function (level) {
500 500 this.level = level;
501 501 if (this.rendered) {
502 502 this.rendered = false;
503 503 this.render();
504 504 };
505 505 };
506 506
507 507 /** The depth of header cell, based on html (h1 to h6)
508 508 * @method get_level
509 509 * @return {integer} level - for 1 to 6
510 510 */
511 511 HeadingCell.prototype.get_level = function () {
512 512 return this.level;
513 513 };
514 514
515 515
516 516 HeadingCell.prototype.set_rendered = function (text) {
517 517 var r = this.element.find("div.text_cell_render");
518 518 r.empty();
519 var link = text.replace(/ /g, '_');
519 520 r.append(
520 521 $('<h'+this.level+'/>')
521 522 .append(
522 523 $('<a/>')
523 524 .addClass('heading-anchor')
524 .attr('href', '#' + text)
525 .attr('name', text)
525 .attr('href', '#' + link)
526 .attr('id', link)
526 527 .html(text)
527 528 )
528 529 );
529 530 };
530 531
531 532
532 533 HeadingCell.prototype.get_rendered = function () {
533 534 var r = this.element.find("div.text_cell_render");
534 535 return r.children().first().html();
535 536 };
536 537
537 538
538 539 HeadingCell.prototype.render = function () {
539 540 if (this.rendered === false) {
540 541 var text = this.get_text();
541 542 if (text === "") { text = this.placeholder; }
542 543 this.set_rendered(text);
543 544 this.typeset();
544 545 this.element.find('div.text_cell_input').hide();
545 546 this.element.find("div.text_cell_render").show();
546 547 this.rendered = true;
547 548 };
548 549 };
549 550
550 551 IPython.TextCell = TextCell;
551 552 IPython.HTMLCell = HTMLCell;
552 553 IPython.MarkdownCell = MarkdownCell;
553 554 IPython.RawCell = RawCell;
554 555 IPython.HeadingCell = HeadingCell;
555 556
556 557
557 558 return IPython;
558 559
559 560 }(IPython));
560 561
General Comments 0
You need to be logged in to leave comments. Login now