##// END OF EJS Templates
better regex replacement for MarkdownCell.set_heading_level...
MinRK -
Show More
@@ -1,352 +1,345 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 define([
4 define([
5 'base/js/namespace',
5 'base/js/namespace',
6 'base/js/utils',
6 'base/js/utils',
7 'jquery',
7 'jquery',
8 'notebook/js/cell',
8 'notebook/js/cell',
9 'base/js/security',
9 'base/js/security',
10 'notebook/js/mathjaxutils',
10 'notebook/js/mathjaxutils',
11 'notebook/js/celltoolbar',
11 'notebook/js/celltoolbar',
12 'components/marked/lib/marked',
12 'components/marked/lib/marked',
13 'codemirror/lib/codemirror',
13 'codemirror/lib/codemirror',
14 'codemirror/mode/gfm/gfm',
14 'codemirror/mode/gfm/gfm',
15 'notebook/js/codemirror-ipythongfm'
15 'notebook/js/codemirror-ipythongfm'
16 ], function(IPython,utils , $, cell, security, mathjaxutils, celltoolbar, marked, CodeMirror, gfm, ipgfm) {
16 ], function(IPython,utils , $, cell, security, mathjaxutils, celltoolbar, marked, CodeMirror, gfm, ipgfm) {
17 "use strict";
17 "use strict";
18 var Cell = cell.Cell;
18 var Cell = cell.Cell;
19
19
20 var TextCell = function (options) {
20 var TextCell = function (options) {
21 // Constructor
21 // Constructor
22 //
22 //
23 // Construct a new TextCell, codemirror mode is by default 'htmlmixed',
23 // Construct a new TextCell, codemirror mode is by default 'htmlmixed',
24 // and cell type is 'text' cell start as not redered.
24 // and cell type is 'text' cell start as not redered.
25 //
25 //
26 // Parameters:
26 // Parameters:
27 // options: dictionary
27 // options: dictionary
28 // Dictionary of keyword arguments.
28 // Dictionary of keyword arguments.
29 // events: $(Events) instance
29 // events: $(Events) instance
30 // config: dictionary
30 // config: dictionary
31 // keyboard_manager: KeyboardManager instance
31 // keyboard_manager: KeyboardManager instance
32 // notebook: Notebook instance
32 // notebook: Notebook instance
33 options = options || {};
33 options = options || {};
34
34
35 // in all TextCell/Cell subclasses
35 // in all TextCell/Cell subclasses
36 // do not assign most of members here, just pass it down
36 // do not assign most of members here, just pass it down
37 // in the options dict potentially overwriting what you wish.
37 // in the options dict potentially overwriting what you wish.
38 // they will be assigned in the base class.
38 // they will be assigned in the base class.
39 this.notebook = options.notebook;
39 this.notebook = options.notebook;
40 this.events = options.events;
40 this.events = options.events;
41 this.config = options.config;
41 this.config = options.config;
42
42
43 // we cannot put this as a class key as it has handle to "this".
43 // we cannot put this as a class key as it has handle to "this".
44 var cm_overwrite_options = {
44 var cm_overwrite_options = {
45 onKeyEvent: $.proxy(this.handle_keyevent,this)
45 onKeyEvent: $.proxy(this.handle_keyevent,this)
46 };
46 };
47 var config = utils.mergeopt(TextCell, this.config, {cm_config:cm_overwrite_options});
47 var config = utils.mergeopt(TextCell, this.config, {cm_config:cm_overwrite_options});
48 Cell.apply(this, [{
48 Cell.apply(this, [{
49 config: config,
49 config: config,
50 keyboard_manager: options.keyboard_manager,
50 keyboard_manager: options.keyboard_manager,
51 events: this.events}]);
51 events: this.events}]);
52
52
53 this.cell_type = this.cell_type || 'text';
53 this.cell_type = this.cell_type || 'text';
54 mathjaxutils = mathjaxutils;
54 mathjaxutils = mathjaxutils;
55 this.rendered = false;
55 this.rendered = false;
56 };
56 };
57
57
58 TextCell.prototype = Object.create(Cell.prototype);
58 TextCell.prototype = Object.create(Cell.prototype);
59
59
60 TextCell.options_default = {
60 TextCell.options_default = {
61 cm_config : {
61 cm_config : {
62 extraKeys: {"Tab": "indentMore","Shift-Tab" : "indentLess"},
62 extraKeys: {"Tab": "indentMore","Shift-Tab" : "indentLess"},
63 mode: 'htmlmixed',
63 mode: 'htmlmixed',
64 lineWrapping : true,
64 lineWrapping : true,
65 }
65 }
66 };
66 };
67
67
68
68
69 /**
69 /**
70 * Create the DOM element of the TextCell
70 * Create the DOM element of the TextCell
71 * @method create_element
71 * @method create_element
72 * @private
72 * @private
73 */
73 */
74 TextCell.prototype.create_element = function () {
74 TextCell.prototype.create_element = function () {
75 Cell.prototype.create_element.apply(this, arguments);
75 Cell.prototype.create_element.apply(this, arguments);
76
76
77 var cell = $("<div>").addClass('cell text_cell');
77 var cell = $("<div>").addClass('cell text_cell');
78 cell.attr('tabindex','2');
78 cell.attr('tabindex','2');
79
79
80 var prompt = $('<div/>').addClass('prompt input_prompt');
80 var prompt = $('<div/>').addClass('prompt input_prompt');
81 cell.append(prompt);
81 cell.append(prompt);
82 var inner_cell = $('<div/>').addClass('inner_cell');
82 var inner_cell = $('<div/>').addClass('inner_cell');
83 this.celltoolbar = new celltoolbar.CellToolbar({
83 this.celltoolbar = new celltoolbar.CellToolbar({
84 cell: this,
84 cell: this,
85 notebook: this.notebook});
85 notebook: this.notebook});
86 inner_cell.append(this.celltoolbar.element);
86 inner_cell.append(this.celltoolbar.element);
87 var input_area = $('<div/>').addClass('input_area');
87 var input_area = $('<div/>').addClass('input_area');
88 this.code_mirror = new CodeMirror(input_area.get(0), this.cm_config);
88 this.code_mirror = new CodeMirror(input_area.get(0), this.cm_config);
89 // The tabindex=-1 makes this div focusable.
89 // The tabindex=-1 makes this div focusable.
90 var render_area = $('<div/>').addClass('text_cell_render rendered_html')
90 var render_area = $('<div/>').addClass('text_cell_render rendered_html')
91 .attr('tabindex','-1');
91 .attr('tabindex','-1');
92 inner_cell.append(input_area).append(render_area);
92 inner_cell.append(input_area).append(render_area);
93 cell.append(inner_cell);
93 cell.append(inner_cell);
94 this.element = cell;
94 this.element = cell;
95 };
95 };
96
96
97
97
98 // Cell level actions
98 // Cell level actions
99
99
100 TextCell.prototype.select = function () {
100 TextCell.prototype.select = function () {
101 var cont = Cell.prototype.select.apply(this);
101 var cont = Cell.prototype.select.apply(this);
102 if (cont) {
102 if (cont) {
103 if (this.mode === 'edit') {
103 if (this.mode === 'edit') {
104 this.code_mirror.refresh();
104 this.code_mirror.refresh();
105 }
105 }
106 }
106 }
107 return cont;
107 return cont;
108 };
108 };
109
109
110 TextCell.prototype.unrender = function () {
110 TextCell.prototype.unrender = function () {
111 if (this.read_only) return;
111 if (this.read_only) return;
112 var cont = Cell.prototype.unrender.apply(this);
112 var cont = Cell.prototype.unrender.apply(this);
113 if (cont) {
113 if (cont) {
114 var text_cell = this.element;
114 var text_cell = this.element;
115 var output = text_cell.find("div.text_cell_render");
115 var output = text_cell.find("div.text_cell_render");
116 if (this.get_text() === this.placeholder) {
116 if (this.get_text() === this.placeholder) {
117 this.set_text('');
117 this.set_text('');
118 }
118 }
119 this.refresh();
119 this.refresh();
120 }
120 }
121 return cont;
121 return cont;
122 };
122 };
123
123
124 TextCell.prototype.execute = function () {
124 TextCell.prototype.execute = function () {
125 this.render();
125 this.render();
126 };
126 };
127
127
128 /**
128 /**
129 * setter: {{#crossLink "TextCell/set_text"}}{{/crossLink}}
129 * setter: {{#crossLink "TextCell/set_text"}}{{/crossLink}}
130 * @method get_text
130 * @method get_text
131 * @retrun {string} CodeMirror current text value
131 * @retrun {string} CodeMirror current text value
132 */
132 */
133 TextCell.prototype.get_text = function() {
133 TextCell.prototype.get_text = function() {
134 return this.code_mirror.getValue();
134 return this.code_mirror.getValue();
135 };
135 };
136
136
137 /**
137 /**
138 * @param {string} text - Codemiror text value
138 * @param {string} text - Codemiror text value
139 * @see TextCell#get_text
139 * @see TextCell#get_text
140 * @method set_text
140 * @method set_text
141 * */
141 * */
142 TextCell.prototype.set_text = function(text) {
142 TextCell.prototype.set_text = function(text) {
143 this.code_mirror.setValue(text);
143 this.code_mirror.setValue(text);
144 this.unrender();
144 this.unrender();
145 this.code_mirror.refresh();
145 this.code_mirror.refresh();
146 };
146 };
147
147
148 /**
148 /**
149 * setter :{{#crossLink "TextCell/set_rendered"}}{{/crossLink}}
149 * setter :{{#crossLink "TextCell/set_rendered"}}{{/crossLink}}
150 * @method get_rendered
150 * @method get_rendered
151 * */
151 * */
152 TextCell.prototype.get_rendered = function() {
152 TextCell.prototype.get_rendered = function() {
153 return this.element.find('div.text_cell_render').html();
153 return this.element.find('div.text_cell_render').html();
154 };
154 };
155
155
156 /**
156 /**
157 * @method set_rendered
157 * @method set_rendered
158 */
158 */
159 TextCell.prototype.set_rendered = function(text) {
159 TextCell.prototype.set_rendered = function(text) {
160 this.element.find('div.text_cell_render').html(text);
160 this.element.find('div.text_cell_render').html(text);
161 };
161 };
162
162
163
163
164 /**
164 /**
165 * Create Text cell from JSON
165 * Create Text cell from JSON
166 * @param {json} data - JSON serialized text-cell
166 * @param {json} data - JSON serialized text-cell
167 * @method fromJSON
167 * @method fromJSON
168 */
168 */
169 TextCell.prototype.fromJSON = function (data) {
169 TextCell.prototype.fromJSON = function (data) {
170 Cell.prototype.fromJSON.apply(this, arguments);
170 Cell.prototype.fromJSON.apply(this, arguments);
171 if (data.cell_type === this.cell_type) {
171 if (data.cell_type === this.cell_type) {
172 if (data.source !== undefined) {
172 if (data.source !== undefined) {
173 this.set_text(data.source);
173 this.set_text(data.source);
174 // make this value the starting point, so that we can only undo
174 // make this value the starting point, so that we can only undo
175 // to this state, instead of a blank cell
175 // to this state, instead of a blank cell
176 this.code_mirror.clearHistory();
176 this.code_mirror.clearHistory();
177 // TODO: This HTML needs to be treated as potentially dangerous
177 // TODO: This HTML needs to be treated as potentially dangerous
178 // user input and should be handled before set_rendered.
178 // user input and should be handled before set_rendered.
179 this.set_rendered(data.rendered || '');
179 this.set_rendered(data.rendered || '');
180 this.rendered = false;
180 this.rendered = false;
181 this.render();
181 this.render();
182 }
182 }
183 }
183 }
184 };
184 };
185
185
186 /** Generate JSON from cell
186 /** Generate JSON from cell
187 * @return {object} cell data serialised to json
187 * @return {object} cell data serialised to json
188 */
188 */
189 TextCell.prototype.toJSON = function () {
189 TextCell.prototype.toJSON = function () {
190 var data = Cell.prototype.toJSON.apply(this);
190 var data = Cell.prototype.toJSON.apply(this);
191 data.source = this.get_text();
191 data.source = this.get_text();
192 if (data.source == this.placeholder) {
192 if (data.source == this.placeholder) {
193 data.source = "";
193 data.source = "";
194 }
194 }
195 return data;
195 return data;
196 };
196 };
197
197
198
198
199 var MarkdownCell = function (options) {
199 var MarkdownCell = function (options) {
200 // Constructor
200 // Constructor
201 //
201 //
202 // Parameters:
202 // Parameters:
203 // options: dictionary
203 // options: dictionary
204 // Dictionary of keyword arguments.
204 // Dictionary of keyword arguments.
205 // events: $(Events) instance
205 // events: $(Events) instance
206 // config: dictionary
206 // config: dictionary
207 // keyboard_manager: KeyboardManager instance
207 // keyboard_manager: KeyboardManager instance
208 // notebook: Notebook instance
208 // notebook: Notebook instance
209 options = options || {};
209 options = options || {};
210 var config = utils.mergeopt(MarkdownCell, options.config);
210 var config = utils.mergeopt(MarkdownCell, options.config);
211 TextCell.apply(this, [$.extend({}, options, {config: config})]);
211 TextCell.apply(this, [$.extend({}, options, {config: config})]);
212
212
213 this.cell_type = 'markdown';
213 this.cell_type = 'markdown';
214 };
214 };
215
215
216 MarkdownCell.options_default = {
216 MarkdownCell.options_default = {
217 cm_config: {
217 cm_config: {
218 mode: 'ipythongfm'
218 mode: 'ipythongfm'
219 },
219 },
220 placeholder: "Type *Markdown* and LaTeX: $\\alpha^2$"
220 placeholder: "Type *Markdown* and LaTeX: $\\alpha^2$"
221 };
221 };
222
222
223 MarkdownCell.prototype = Object.create(TextCell.prototype);
223 MarkdownCell.prototype = Object.create(TextCell.prototype);
224
224
225 MarkdownCell.prototype.set_heading_level = function (level) {
225 MarkdownCell.prototype.set_heading_level = function (level) {
226 // make a markdown cell a heading
226 // make a markdown cell a heading
227 level = level || 1;
227 level = level || 1;
228 var source = this.get_text();
228 var source = this.get_text();
229 // \s\S appears to be the js version of multi-line dot-all
229 source = source.replace(/^(#*)\s?/,
230 var match = source.match(/(#*)\s*([\s\S]*)/);
230 new Array(level + 1).join('#') + ' ');
231 // strip the leading `#` if it's already there
231 this.set_text(source);
232 if (match) {
233 source = match[2];
234 }
235 // add `#` markdown heading prefix
236 var new_text = new Array(level + 1).join('#') + ' ' + source;
237
238 this.set_text(new_text);
239 this.refresh();
232 this.refresh();
240 if (this.rendered) {
233 if (this.rendered) {
241 this.render();
234 this.render();
242 }
235 }
243 };
236 };
244
237
245 /**
238 /**
246 * @method render
239 * @method render
247 */
240 */
248 MarkdownCell.prototype.render = function () {
241 MarkdownCell.prototype.render = function () {
249 var cont = TextCell.prototype.render.apply(this);
242 var cont = TextCell.prototype.render.apply(this);
250 if (cont) {
243 if (cont) {
251 var text = this.get_text();
244 var text = this.get_text();
252 var math = null;
245 var math = null;
253 if (text === "") { text = this.placeholder; }
246 if (text === "") { text = this.placeholder; }
254 var text_and_math = mathjaxutils.remove_math(text);
247 var text_and_math = mathjaxutils.remove_math(text);
255 text = text_and_math[0];
248 text = text_and_math[0];
256 math = text_and_math[1];
249 math = text_and_math[1];
257 var html = marked.parser(marked.lexer(text));
250 var html = marked.parser(marked.lexer(text));
258 html = mathjaxutils.replace_math(html, math);
251 html = mathjaxutils.replace_math(html, math);
259 html = security.sanitize_html(html);
252 html = security.sanitize_html(html);
260 html = $($.parseHTML(html));
253 html = $($.parseHTML(html));
261 // add anchors to headings
254 // add anchors to headings
262 // console.log(html);
255 // console.log(html);
263 html.find(":header").addBack(":header").each(function (i, h) {
256 html.find(":header").addBack(":header").each(function (i, h) {
264 h = $(h);
257 h = $(h);
265 var hash = h.text().replace(/ /g, '-');
258 var hash = h.text().replace(/ /g, '-');
266 h.attr('id', hash);
259 h.attr('id', hash);
267 h.append(
260 h.append(
268 $('<a/>')
261 $('<a/>')
269 .addClass('anchor-link')
262 .addClass('anchor-link')
270 .attr('href', '#' + hash)
263 .attr('href', '#' + hash)
271 .text('ΒΆ')
264 .text('ΒΆ')
272 );
265 );
273 })
266 })
274 // links in markdown cells should open in new tabs
267 // links in markdown cells should open in new tabs
275 html.find("a[href]").not('[href^="#"]').attr("target", "_blank");
268 html.find("a[href]").not('[href^="#"]').attr("target", "_blank");
276 this.set_rendered(html);
269 this.set_rendered(html);
277 this.typeset();
270 this.typeset();
278 this.events.trigger("rendered.MarkdownCell", {cell: this})
271 this.events.trigger("rendered.MarkdownCell", {cell: this})
279 }
272 }
280 return cont;
273 return cont;
281 };
274 };
282
275
283
276
284 var RawCell = function (options) {
277 var RawCell = function (options) {
285 // Constructor
278 // Constructor
286 //
279 //
287 // Parameters:
280 // Parameters:
288 // options: dictionary
281 // options: dictionary
289 // Dictionary of keyword arguments.
282 // Dictionary of keyword arguments.
290 // events: $(Events) instance
283 // events: $(Events) instance
291 // config: dictionary
284 // config: dictionary
292 // keyboard_manager: KeyboardManager instance
285 // keyboard_manager: KeyboardManager instance
293 // notebook: Notebook instance
286 // notebook: Notebook instance
294 options = options || {};
287 options = options || {};
295 var config = utils.mergeopt(RawCell, options.config);
288 var config = utils.mergeopt(RawCell, options.config);
296 TextCell.apply(this, [$.extend({}, options, {config: config})]);
289 TextCell.apply(this, [$.extend({}, options, {config: config})]);
297
290
298 this.cell_type = 'raw';
291 this.cell_type = 'raw';
299 };
292 };
300
293
301 RawCell.options_default = {
294 RawCell.options_default = {
302 placeholder : "Write raw LaTeX or other formats here, for use with nbconvert. " +
295 placeholder : "Write raw LaTeX or other formats here, for use with nbconvert. " +
303 "It will not be rendered in the notebook. " +
296 "It will not be rendered in the notebook. " +
304 "When passing through nbconvert, a Raw Cell's content is added to the output unmodified."
297 "When passing through nbconvert, a Raw Cell's content is added to the output unmodified."
305 };
298 };
306
299
307 RawCell.prototype = Object.create(TextCell.prototype);
300 RawCell.prototype = Object.create(TextCell.prototype);
308
301
309 /** @method bind_events **/
302 /** @method bind_events **/
310 RawCell.prototype.bind_events = function () {
303 RawCell.prototype.bind_events = function () {
311 TextCell.prototype.bind_events.apply(this);
304 TextCell.prototype.bind_events.apply(this);
312 var that = this;
305 var that = this;
313 this.element.focusout(function() {
306 this.element.focusout(function() {
314 that.auto_highlight();
307 that.auto_highlight();
315 that.render();
308 that.render();
316 });
309 });
317
310
318 this.code_mirror.on('focus', function() { that.unrender(); });
311 this.code_mirror.on('focus', function() { that.unrender(); });
319 };
312 };
320
313
321 /**
314 /**
322 * Trigger autodetection of highlight scheme for current cell
315 * Trigger autodetection of highlight scheme for current cell
323 * @method auto_highlight
316 * @method auto_highlight
324 */
317 */
325 RawCell.prototype.auto_highlight = function () {
318 RawCell.prototype.auto_highlight = function () {
326 this._auto_highlight(this.config.raw_cell_highlight);
319 this._auto_highlight(this.config.raw_cell_highlight);
327 };
320 };
328
321
329 /** @method render **/
322 /** @method render **/
330 RawCell.prototype.render = function () {
323 RawCell.prototype.render = function () {
331 var cont = TextCell.prototype.render.apply(this);
324 var cont = TextCell.prototype.render.apply(this);
332 if (cont){
325 if (cont){
333 var text = this.get_text();
326 var text = this.get_text();
334 if (text === "") { text = this.placeholder; }
327 if (text === "") { text = this.placeholder; }
335 this.set_text(text);
328 this.set_text(text);
336 this.element.removeClass('rendered');
329 this.element.removeClass('rendered');
337 }
330 }
338 return cont;
331 return cont;
339 };
332 };
340
333
341 // Backwards compatability.
334 // Backwards compatability.
342 IPython.TextCell = TextCell;
335 IPython.TextCell = TextCell;
343 IPython.MarkdownCell = MarkdownCell;
336 IPython.MarkdownCell = MarkdownCell;
344 IPython.RawCell = RawCell;
337 IPython.RawCell = RawCell;
345
338
346 var textcell = {
339 var textcell = {
347 TextCell: TextCell,
340 TextCell: TextCell,
348 MarkdownCell: MarkdownCell,
341 MarkdownCell: MarkdownCell,
349 RawCell: RawCell,
342 RawCell: RawCell,
350 };
343 };
351 return textcell;
344 return textcell;
352 });
345 });
General Comments 0
You need to be logged in to leave comments. Login now