bhf 0.4.7 → 0.4.8

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,2317 @@
1
+ ;
2
+ (function () {
3
+
4
+ WMDEditor = function (options) {
5
+ this.options = WMDEditor.util.extend({}, WMDEditor.defaults, options || {});
6
+ wmdBase(this, this.options);
7
+
8
+ this.startEditor();
9
+ };
10
+ window.WMDEditor = WMDEditor;
11
+
12
+ WMDEditor.defaults = { // {{{
13
+ version: 2.1,
14
+ output_format: "markdown",
15
+ lineLength: 40,
16
+
17
+ button_bar: "wmd-button-bar",
18
+ preview: "wmd-preview",
19
+ output: "wmd-output",
20
+ input: "wmd-input",
21
+
22
+ // The text that appears on the upper part of the dialog box when
23
+ // entering links.
24
+ imageDialogText: "<p style='margin-top: 0px'><b>Enter the image URL.</b></p><p>You can also add a title, which will be displayed as a tool tip.</p><p>Example:<br />http://i.imgur.com/1cZl4.jpg</p>",
25
+ linkDialogText: "<p style='margin-top: 0px'><b>Enter the web address.</b></p><p>You can also add a title, which will be displayed as a tool tip.</p><p>Example:<br />http://www.google.com/</p>",
26
+
27
+ // The default text that appears in the dialog input box when entering
28
+ // links.
29
+ imageDefaultText: "http://",
30
+ linkDefaultText: "http://",
31
+ imageDirectory: "images/",
32
+
33
+ // The link and title for the help button
34
+ helpLink: "/wmd/markdownhelp.html",
35
+ helpHoverTitle: "Markdown Syntax",
36
+ helpTarget: "_blank",
37
+
38
+ // Some intervals in ms. These can be adjusted to reduce the control's load.
39
+ previewPollInterval: 500,
40
+ pastePollInterval: 100,
41
+
42
+ buttons: "bold italic link blockquote code image ol ul heading hr undo redo help",
43
+
44
+ autoFormatting: {
45
+ list: true,
46
+ quote: true,
47
+ code: true,
48
+ },
49
+
50
+ modifierKeys: { //replace this with null or false to disable key-combos
51
+ bold: "b",
52
+ italic: "i",
53
+ link: "l",
54
+ quote: "q",
55
+ code: "k",
56
+ image: "g",
57
+ orderedList: "o",
58
+ unorderedList: "u",
59
+ heading: "h",
60
+ horizontalRule: "r",
61
+ redo: "y",
62
+ undo: "z"
63
+ },
64
+
65
+
66
+ tagFilter: {
67
+ enabled: false,
68
+ allowedTags: /^(<\/?(b|blockquote|code|del|dd|dl|dt|em|h1|h2|h3|i|kbd|li|ol|p|pre|s|sup|sub|strong|strike|ul)>|<(br|hr)\s?\/?>)$/i,
69
+ patternLink: /^(<a\shref=("|')(\#\d+|(https?:\/\/|ftp:\/\/|mailto:)[-A-Za-z0-9+&@#\/%?=~_|!:,.;\(\)]+)\2(\stitle="[^"<>]+")?\s?>|<\/a>)$/i,
70
+ patternImage: /^(<img\ssrc="https?:(\/\/[-A-Za-z0-9+&@#\/%?=~_|!:,.;\(\)]+)"(\swidth="\d{1,3}")?(\sheight="\d{1,3}")?(\salt="[^"<>]*")?(\stitle="[^"<>]*")?\s?\/?>)$/i
71
+ }
72
+ }; // }}}
73
+ WMDEditor.prototype = {
74
+ getPanels: function () {
75
+ return {
76
+ buttonBar: (typeof this.options.button_bar == 'string') ? document.getElementById(this.options.button_bar) : this.options.button_bar,
77
+ preview: (typeof this.options.preview == 'string') ? document.getElementById(this.options.preview) : this.options.preview,
78
+ output: (typeof this.options.output == 'string') ? document.getElementById(this.options.output) : this.options.output,
79
+ input: (typeof this.options.input == 'string') ? document.getElementById(this.options.input) : this.options.input
80
+ };
81
+ },
82
+
83
+ startEditor: function () {
84
+ this.panels = this.getPanels();
85
+ this.previewMgr = new PreviewManager(this);
86
+ edit = new this.editor(this.previewMgr.refresh);
87
+ this.previewMgr.refresh(true);
88
+ }
89
+ };
90
+
91
+
92
+ var util = { // {{{
93
+ // Returns true if the DOM element is visible, false if it's hidden.
94
+ // Checks if display is anything other than none.
95
+ isVisible: function (elem) {
96
+ // shamelessly copied from jQuery
97
+ return elem.offsetWidth > 0 || elem.offsetHeight > 0;
98
+ },
99
+
100
+ // Adds a listener callback to a DOM element which is fired on a specified
101
+ // event.
102
+ addEvent: function (elem, event, listener) {
103
+ if (elem.attachEvent) {
104
+ // IE only. The "on" is mandatory.
105
+ elem.attachEvent("on" + event, listener);
106
+ }
107
+ else {
108
+ // Other browsers.
109
+ elem.addEventListener(event, listener, false);
110
+ }
111
+ },
112
+
113
+ // Removes a listener callback from a DOM element which is fired on a specified
114
+ // event.
115
+ removeEvent: function (elem, event, listener) {
116
+ if (elem.detachEvent) {
117
+ // IE only. The "on" is mandatory.
118
+ elem.detachEvent("on" + event, listener);
119
+ }
120
+ else {
121
+ // Other browsers.
122
+ elem.removeEventListener(event, listener, false);
123
+ }
124
+ },
125
+
126
+ // Converts \r\n and \r to \n.
127
+ fixEolChars: function (text) {
128
+ text = text.replace(/\r\n/g, "\n");
129
+ text = text.replace(/\r/g, "\n");
130
+ return text;
131
+ },
132
+
133
+ // Extends a regular expression. Returns a new RegExp
134
+ // using pre + regex + post as the expression.
135
+ // Used in a few functions where we have a base
136
+ // expression and we want to pre- or append some
137
+ // conditions to it (e.g. adding "$" to the end).
138
+ // The flags are unchanged.
139
+ //
140
+ // regex is a RegExp, pre and post are strings.
141
+ extendRegExp: function (regex, pre, post) {
142
+
143
+ if (pre === null || pre === undefined) {
144
+ pre = "";
145
+ }
146
+ if (post === null || post === undefined) {
147
+ post = "";
148
+ }
149
+
150
+ var pattern = regex.toString();
151
+ var flags = "";
152
+
153
+ // Replace the flags with empty space and store them.
154
+ // Technically, this can match incorrect flags like "gmm".
155
+ var result = pattern.match(/\/([gim]*)$/);
156
+ if (result === null) {
157
+ flags = result[0];
158
+ }
159
+ else {
160
+ flags = "";
161
+ }
162
+
163
+ // Remove the flags and slash delimiters from the regular expression.
164
+ pattern = pattern.replace(/(^\/|\/[gim]*$)/g, "");
165
+ pattern = pre + pattern + post;
166
+
167
+ return new RegExp(pattern, flags);
168
+ },
169
+
170
+ // Sets the image for a button passed to the WMD editor.
171
+ // Returns a new element with the image attached.
172
+ // Adds several style properties to the image.
173
+ //
174
+ // XXX-ANAND: Is this used anywhere?
175
+ createImage: function (img) {
176
+
177
+ var imgPath = imageDirectory + img;
178
+
179
+ var elem = document.createElement("img");
180
+ elem.className = "wmd-button";
181
+ elem.src = imgPath;
182
+
183
+ return elem;
184
+ },
185
+
186
+ // This simulates a modal dialog box and asks for the URL when you
187
+ // click the hyperlink or image buttons.
188
+ //
189
+ // text: The html for the input box.
190
+ // defaultInputText: The default value that appears in the input box.
191
+ // makeLinkMarkdown: The function which is executed when the prompt is dismissed, either via OK or Cancel
192
+ prompt: function (text, defaultInputText, makeLinkMarkdown, promptType) {
193
+
194
+ // These variables need to be declared at this level since they are used
195
+ // in multiple functions.
196
+ var dialog; // The dialog box.
197
+ var background; // The background beind the dialog box.
198
+ var input; // The text box where you enter the hyperlink.
199
+ var titleInput; // The text box for the image's title text
200
+ var newWinCheckbox; //The checkbox to choose if a link should be opened in a new window.
201
+ if (defaultInputText === undefined) {
202
+ defaultInputText = "";
203
+ }
204
+
205
+ // Used as a keydown event handler. Esc dismisses the prompt.
206
+ // Key code 27 is ESC.
207
+ var checkEscape = function (key) {
208
+ var code = (key.charCode || key.keyCode);
209
+ if (code === 27) {
210
+ close(true);
211
+ }
212
+ };
213
+
214
+ // Dismisses the hyperlink input box.
215
+ // isCancel is true if we don't care about the input text.
216
+ // isCancel is false if we are going to keep the text.
217
+ var close = function (isCancel) {
218
+ util.removeEvent(document.body, "keydown", checkEscape);
219
+ var text = input.value+ (titleInput.value?' "'+titleInput.value+'"':'');
220
+
221
+ if (isCancel) {
222
+ text = null;
223
+ }
224
+ else {
225
+ // Fixes common pasting errors.
226
+ text = text.replace('http://http://', 'http://');
227
+ text = text.replace('http://https://', 'https://');
228
+ text = text.replace('http://ftp://', 'ftp://');
229
+ if (promptType=='link' && newWinCheckbox.checked) text = '!'+text;
230
+ }
231
+
232
+ dialog.parentNode.removeChild(dialog);
233
+ background.parentNode.removeChild(background);
234
+ makeLinkMarkdown(text);
235
+ return false;
236
+ };
237
+
238
+ // Creates the background behind the hyperlink text entry box.
239
+ // Most of this has been moved to CSS but the div creation and
240
+ // browser-specific hacks remain here.
241
+ var createBackground = function () {
242
+ background = document.createElement("div");
243
+ background.className = "wmd-prompt-background";
244
+ style = background.style;
245
+ style.position = "absolute";
246
+ style.top = "0";
247
+
248
+ style.zIndex = "10000";
249
+
250
+ // Some versions of Konqueror don't support transparent colors
251
+ // so we make the whole window transparent.
252
+ //
253
+ // Is this necessary on modern konqueror browsers?
254
+ if (browser.isKonqueror) {
255
+ style.backgroundColor = "transparent";
256
+ }
257
+ else if (browser.isIE) {
258
+ style.filter = "alpha(opacity=50)";
259
+ }
260
+ else {
261
+ style.opacity = "0.5";
262
+ }
263
+
264
+ var pageSize = position.getPageSize();
265
+ style.height = pageSize[1] + "px";
266
+
267
+ if (browser.isIE) {
268
+ style.left = document.documentElement.scrollLeft;
269
+ style.width = document.documentElement.clientWidth;
270
+ }
271
+ else {
272
+ style.left = "0";
273
+ style.width = "100%";
274
+ }
275
+
276
+ document.body.appendChild(background);
277
+ };
278
+
279
+ // Create the text input box form/window.
280
+ var createDialog = function () {
281
+
282
+ // The main dialog box.
283
+ dialog = document.createElement("div");
284
+ dialog.className = "wmd-prompt-dialog";
285
+ dialog.style.padding = "10px;";
286
+ dialog.style.position = "fixed";
287
+ dialog.style.width = "400px";
288
+ dialog.style.zIndex = "10001";
289
+
290
+ // The dialog text.
291
+ var question = document.createElement("div");
292
+ question.innerHTML = text;
293
+ question.style.padding = "5px";
294
+ dialog.appendChild(question);
295
+
296
+ // The web form container for the text box and buttons.
297
+ var form = document.createElement("form");
298
+ form.onsubmit = function () {
299
+ return close(false);
300
+ };
301
+ var style = form.style;
302
+ style.padding = "0";
303
+ style.margin = "0";
304
+ style.cssFloat = "left";
305
+ style.width = "100%";
306
+ style.textAlign = "center";
307
+ style.position = "relative";
308
+ dialog.appendChild(form);
309
+
310
+ var label = document.createElement("label");
311
+ style = label.style;
312
+ style.display = "block";
313
+ style.width = "80%";
314
+ style.marginLeft = style.marginRight = "auto";
315
+ style.textAlign = "left";
316
+ form.appendChild(label);
317
+
318
+ label.appendChild(document.createTextNode(promptType+" URL:"));
319
+
320
+ // The input text box
321
+ input = document.createElement("input");
322
+ input.type = "text";
323
+ input.value = defaultInputText;
324
+ style = input.style;
325
+ style.display = "block";
326
+ style.width = "100%";
327
+ style.marginLeft = style.marginRight = "auto";
328
+ label.appendChild(input);
329
+
330
+ label = document.createElement("label");
331
+ style = label.style;
332
+ style.display = "block";
333
+ style.width = "80%";
334
+ style.marginLeft = style.marginRight = "auto";
335
+ style.textAlign = "left";
336
+ form.appendChild(label);
337
+
338
+ label.appendChild(document.createTextNode(promptType+" Title (Hover Text):"));
339
+
340
+ // The input text box
341
+ titleInput = document.createElement("input");
342
+ titleInput.type = "text";
343
+ style = titleInput.style;
344
+ style.display = "block";
345
+ style.width = "100%";
346
+ style.marginLeft = style.marginRight = "auto";
347
+ label.appendChild(titleInput);
348
+
349
+
350
+ if (promptType=='link') {
351
+ label = document.createElement("label");
352
+ style = label.style;
353
+ style.display = "block";
354
+ style.textAlign = "center";
355
+ form.appendChild(label);
356
+
357
+ newWinCheckbox = document.createElement("input");
358
+ newWinCheckbox.type = 'checkbox';
359
+ newWinCheckbox.value = '!';
360
+ label.appendChild(newWinCheckbox);
361
+
362
+ label.appendChild(document.createTextNode(" Have this link open in a new window"));
363
+ }
364
+
365
+ // The ok button
366
+ var okButton = document.createElement("input");
367
+ okButton.type = "button";
368
+ okButton.onclick = function () {
369
+ return close(false);
370
+ };
371
+ okButton.value = "OK";
372
+ style = okButton.style;
373
+ style.margin = "10px";
374
+ style.display = "inline";
375
+ style.width = "7em";
376
+
377
+
378
+ // The cancel button
379
+ var cancelButton = document.createElement("input");
380
+ cancelButton.type = "button";
381
+ cancelButton.onclick = function () {
382
+ return close(true);
383
+ };
384
+ cancelButton.value = "Cancel";
385
+ style = cancelButton.style;
386
+ style.margin = "10px";
387
+ style.display = "inline";
388
+ style.width = "7em";
389
+
390
+ // The order of these buttons is different on macs.
391
+ if (/mac/.test(nav.platform.toLowerCase())) {
392
+ form.appendChild(cancelButton);
393
+ form.appendChild(okButton);
394
+ }
395
+ else {
396
+ form.appendChild(okButton);
397
+ form.appendChild(cancelButton);
398
+ }
399
+
400
+ util.addEvent(document.body, "keydown", checkEscape);
401
+ dialog.style.top = "50%";
402
+ dialog.style.left = "50%";
403
+ dialog.style.display = "block";
404
+ if (browser.isIE_5or6) {
405
+ dialog.style.position = "absolute";
406
+ dialog.style.top = document.documentElement.scrollTop + 200 + "px";
407
+ dialog.style.left = "50%";
408
+ }
409
+ document.body.appendChild(dialog);
410
+
411
+ // This has to be done AFTER adding the dialog to the form if you
412
+ // want it to be centered.
413
+ dialog.style.marginTop = -(position.getHeight(dialog) / 2) + "px";
414
+ dialog.style.marginLeft = -(position.getWidth(dialog) / 2) + "px";
415
+ };
416
+
417
+ createBackground();
418
+
419
+ // Why is this in a zero-length timeout?
420
+ // Is it working around a browser bug?
421
+ window.setTimeout(function () {
422
+ createDialog();
423
+
424
+ var defTextLen = defaultInputText.length;
425
+ if (input.selectionStart !== undefined) {
426
+ input.selectionStart = 0;
427
+ input.selectionEnd = defTextLen;
428
+ }
429
+ else if (input.createTextRange) {
430
+ var range = input.createTextRange();
431
+ range.collapse(false);
432
+ range.moveStart("character", -defTextLen);
433
+ range.moveEnd("character", defTextLen);
434
+ range.select();
435
+ }
436
+ input.focus();
437
+ }, 0);
438
+ },
439
+
440
+ extend: function () {
441
+ function _update(a, b) {
442
+ for (var k in b) if (b.hasOwnProperty(k)){
443
+ if (typeof a[k] === 'object' && typeof b[k] === 'object') _update(a[k], b[k]); //if property is an object or array, merge the contents instead of overwriting
444
+ else a[k] = b[k];
445
+ }
446
+ return a;
447
+ }
448
+
449
+ var d = {};
450
+ for (var i = 0; i < arguments.length; i++) {
451
+ _update(d, arguments[i]);
452
+ }
453
+ return d;
454
+ }
455
+ }; // }}}
456
+ var position = { // {{{
457
+ // UNFINISHED
458
+ // The assignment in the while loop makes jslint cranky.
459
+ // I'll change it to a better loop later.
460
+ getTop: function (elem, isInner) {
461
+ var result = elem.offsetTop;
462
+ if (!isInner) {
463
+ while (elem = elem.offsetParent) {
464
+ result += elem.offsetTop;
465
+ }
466
+ }
467
+ return result;
468
+ },
469
+
470
+ getHeight: function (elem) {
471
+ return elem.offsetHeight || elem.scrollHeight;
472
+ },
473
+
474
+ getWidth: function (elem) {
475
+ return elem.offsetWidth || elem.scrollWidth;
476
+ },
477
+
478
+ getPageSize: function () {
479
+ var scrollWidth, scrollHeight;
480
+ var innerWidth, innerHeight;
481
+
482
+ // It's not very clear which blocks work with which browsers.
483
+ if (self.innerHeight && self.scrollMaxY) {
484
+ scrollWidth = document.body.scrollWidth;
485
+ scrollHeight = self.innerHeight + self.scrollMaxY;
486
+ }
487
+ else if (document.body.scrollHeight > document.body.offsetHeight) {
488
+ scrollWidth = document.body.scrollWidth;
489
+ scrollHeight = document.body.scrollHeight;
490
+ }
491
+ else {
492
+ scrollWidth = document.body.offsetWidth;
493
+ scrollHeight = document.body.offsetHeight;
494
+ }
495
+
496
+ if (self.innerHeight) {
497
+ // Non-IE browser
498
+ innerWidth = self.innerWidth;
499
+ innerHeight = self.innerHeight;
500
+ }
501
+ else if (document.documentElement && document.documentElement.clientHeight) {
502
+ // Some versions of IE (IE 6 w/ a DOCTYPE declaration)
503
+ innerWidth = document.documentElement.clientWidth;
504
+ innerHeight = document.documentElement.clientHeight;
505
+ }
506
+ else if (document.body) {
507
+ // Other versions of IE
508
+ innerWidth = document.body.clientWidth;
509
+ innerHeight = document.body.clientHeight;
510
+ }
511
+
512
+ var maxWidth = Math.max(scrollWidth, innerWidth);
513
+ var maxHeight = Math.max(scrollHeight, innerHeight);
514
+ return [maxWidth, maxHeight, innerWidth, innerHeight];
515
+ }
516
+ }; // }}}
517
+ // The input textarea state/contents.
518
+ // This is used to implement undo/redo by the undo manager.
519
+ var TextareaState = function (textarea, wmd) { // {{{
520
+ // Aliases
521
+ var stateObj = this;
522
+ var inputArea = textarea;
523
+
524
+ this.init = function () {
525
+
526
+ if (!util.isVisible(inputArea)) {
527
+ return;
528
+ }
529
+
530
+ this.setInputAreaSelectionStartEnd();
531
+ this.scrollTop = inputArea.scrollTop;
532
+ if (!this.text && inputArea.selectionStart || inputArea.selectionStart === 0) {
533
+ this.text = inputArea.value;
534
+ }
535
+
536
+ };
537
+
538
+ // Sets the selected text in the input box after we've performed an
539
+ // operation.
540
+ this.setInputAreaSelection = function () {
541
+
542
+ if (!util.isVisible(inputArea)) {
543
+ return;
544
+ }
545
+
546
+ if (inputArea.selectionStart !== undefined && !browser.isOpera) {
547
+
548
+ inputArea.focus();
549
+ inputArea.selectionStart = stateObj.start;
550
+ inputArea.selectionEnd = stateObj.end;
551
+ inputArea.scrollTop = stateObj.scrollTop;
552
+ }
553
+ else if (document.selection) {
554
+
555
+ if (typeof(document.activeElement)!="unknown" && document.activeElement && document.activeElement !== inputArea) {
556
+ return;
557
+ }
558
+
559
+ inputArea.focus();
560
+ var range = inputArea.createTextRange();
561
+ range.moveStart("character", -inputArea.value.length);
562
+ range.moveEnd("character", -inputArea.value.length);
563
+ range.moveEnd("character", stateObj.end);
564
+ range.moveStart("character", stateObj.start);
565
+ range.select();
566
+ }
567
+ };
568
+
569
+ this.setInputAreaSelectionStartEnd = function () {
570
+
571
+ if (inputArea.selectionStart || inputArea.selectionStart === 0) {
572
+
573
+ stateObj.start = inputArea.selectionStart;
574
+ stateObj.end = inputArea.selectionEnd;
575
+ }
576
+ else if (document.selection) {
577
+
578
+ stateObj.text = util.fixEolChars(inputArea.value);
579
+
580
+ // IE loses the selection in the textarea when buttons are
581
+ // clicked. On IE we cache the selection and set a flag
582
+ // which we check for here.
583
+ var range;
584
+ if (wmd.ieRetardedClick && wmd.ieCachedRange) {
585
+ range = wmd.ieCachedRange;
586
+ wmd.ieRetardedClick = false;
587
+ }
588
+ else {
589
+ range = document.selection.createRange();
590
+ }
591
+
592
+ var fixedRange = util.fixEolChars(range.text);
593
+ var marker = "\x07";
594
+ var markedRange = marker + fixedRange + marker;
595
+ range.text = markedRange;
596
+ var inputText = util.fixEolChars(inputArea.value);
597
+
598
+ range.moveStart("character", -markedRange.length);
599
+ range.text = fixedRange;
600
+
601
+ stateObj.start = inputText.indexOf(marker);
602
+ stateObj.end = inputText.lastIndexOf(marker) - marker.length;
603
+
604
+ var len = stateObj.text.length - util.fixEolChars(inputArea.value).length;
605
+
606
+ if (len) {
607
+ range.moveStart("character", -fixedRange.length);
608
+ while (len--) {
609
+ fixedRange += "\n";
610
+ stateObj.end += 1;
611
+ }
612
+ range.text = fixedRange;
613
+ }
614
+
615
+ this.setInputAreaSelection();
616
+ }
617
+ };
618
+
619
+ // Restore this state into the input area.
620
+ this.restore = function () {
621
+
622
+ if (stateObj.text != undefined && stateObj.text != inputArea.value) {
623
+ inputArea.value = stateObj.text;
624
+ }
625
+ this.setInputAreaSelection();
626
+ inputArea.scrollTop = stateObj.scrollTop;
627
+ };
628
+
629
+ // Gets a collection of HTML chunks from the inptut textarea.
630
+ this.getChunks = function () {
631
+
632
+ var chunk = new Chunks();
633
+
634
+ chunk.before = util.fixEolChars(stateObj.text.substring(0, stateObj.start));
635
+ chunk.startTag = "";
636
+ chunk.selection = util.fixEolChars(stateObj.text.substring(stateObj.start, stateObj.end));
637
+ chunk.endTag = "";
638
+ chunk.after = util.fixEolChars(stateObj.text.substring(stateObj.end));
639
+ chunk.scrollTop = stateObj.scrollTop;
640
+
641
+ return chunk;
642
+ };
643
+
644
+ // Sets the TextareaState properties given a chunk of markdown.
645
+ this.setChunks = function (chunk) {
646
+
647
+ chunk.before = chunk.before + chunk.startTag;
648
+ chunk.after = chunk.endTag + chunk.after;
649
+
650
+ if (browser.isOpera) {
651
+ chunk.before = chunk.before.replace(/\n/g, "\r\n");
652
+ chunk.selection = chunk.selection.replace(/\n/g, "\r\n");
653
+ chunk.after = chunk.after.replace(/\n/g, "\r\n");
654
+ }
655
+
656
+ this.start = chunk.before.length;
657
+ this.end = chunk.before.length + chunk.selection.length;
658
+ this.text = chunk.before + chunk.selection + chunk.after;
659
+ this.scrollTop = chunk.scrollTop;
660
+ };
661
+
662
+ this.init();
663
+ }; // }}}
664
+ // Chunks {{{
665
+ // before: contains all the text in the input box BEFORE the selection.
666
+ // after: contains all the text in the input box AFTER the selection.
667
+ var Chunks = function () {};
668
+
669
+ // startRegex: a regular expression to find the start tag
670
+ // endRegex: a regular expresssion to find the end tag
671
+ Chunks.prototype.findTags = function (startRegex, endRegex) {
672
+
673
+ var chunkObj = this;
674
+ var regex;
675
+
676
+ if (startRegex) {
677
+
678
+ regex = util.extendRegExp(startRegex, "", "$");
679
+
680
+ this.before = this.before.replace(regex, function (match) {
681
+ chunkObj.startTag = chunkObj.startTag + match;
682
+ return "";
683
+ });
684
+
685
+ regex = util.extendRegExp(startRegex, "^", "");
686
+
687
+ this.selection = this.selection.replace(regex, function (match) {
688
+ chunkObj.startTag = chunkObj.startTag + match;
689
+ return "";
690
+ });
691
+ }
692
+
693
+ if (endRegex) {
694
+
695
+ regex = util.extendRegExp(endRegex, "", "$");
696
+
697
+ this.selection = this.selection.replace(regex, function (match) {
698
+ chunkObj.endTag = match + chunkObj.endTag;
699
+ return "";
700
+ });
701
+
702
+ regex = util.extendRegExp(endRegex, "^", "");
703
+
704
+ this.after = this.after.replace(regex, function (match) {
705
+ chunkObj.endTag = match + chunkObj.endTag;
706
+ return "";
707
+ });
708
+ }
709
+ };
710
+
711
+ // If remove is false, the whitespace is transferred
712
+ // to the before/after regions.
713
+ //
714
+ // If remove is true, the whitespace disappears.
715
+ Chunks.prototype.trimWhitespace = function (remove) {
716
+
717
+ this.selection = this.selection.replace(/^(\s*)/, "");
718
+
719
+ if (!remove) {
720
+ this.before += re.$1;
721
+ }
722
+
723
+ this.selection = this.selection.replace(/(\s*)$/, "");
724
+
725
+ if (!remove) {
726
+ this.after = re.$1 + this.after;
727
+ }
728
+ };
729
+
730
+
731
+ Chunks.prototype.addBlankLines = function (nLinesBefore, nLinesAfter, findExtraNewlines) {
732
+
733
+ if (nLinesBefore === undefined) {
734
+ nLinesBefore = 1;
735
+ }
736
+
737
+ if (nLinesAfter === undefined) {
738
+ nLinesAfter = 1;
739
+ }
740
+
741
+ nLinesBefore++;
742
+ nLinesAfter++;
743
+
744
+ var regexText;
745
+ var replacementText;
746
+
747
+ // New bug discovered in Chrome, which appears to be related to use of RegExp.$1
748
+ // Hack it to hold the match results. Sucks because we're double matching...
749
+ var match = /(^\n*)/.exec(this.selection);
750
+
751
+ this.selection = this.selection.replace(/(^\n*)/, "");
752
+ this.startTag = this.startTag + (match ? match[1] : "");
753
+ match = /(\n*$)/.exec(this.selection);
754
+ this.selection = this.selection.replace(/(\n*$)/, "");
755
+ this.endTag = this.endTag + (match ? match[1] : "");
756
+ match = /(^\n*)/.exec(this.startTag);
757
+ this.startTag = this.startTag.replace(/(^\n*)/, "");
758
+ this.before = this.before + (match ? match[1] : "");
759
+ match = /(\n*$)/.exec(this.endTag);
760
+ this.endTag = this.endTag.replace(/(\n*$)/, "");
761
+ this.after = this.after + (match ? match[1] : "");
762
+
763
+ if (this.before) {
764
+
765
+ regexText = replacementText = "";
766
+
767
+ while (nLinesBefore--) {
768
+ regexText += "\\n?";
769
+ replacementText += "\n";
770
+ }
771
+
772
+ if (findExtraNewlines) {
773
+ regexText = "\\n*";
774
+ }
775
+ this.before = this.before.replace(new re(regexText + "$", ""), replacementText);
776
+ }
777
+
778
+ if (this.after) {
779
+
780
+ regexText = replacementText = "";
781
+
782
+ while (nLinesAfter--) {
783
+ regexText += "\\n?";
784
+ replacementText += "\n";
785
+ }
786
+ if (findExtraNewlines) {
787
+ regexText = "\\n*";
788
+ }
789
+
790
+ this.after = this.after.replace(new re(regexText, ""), replacementText);
791
+ }
792
+ };
793
+ // }}} - END CHUNKS
794
+ // Watches the input textarea, polling at an interval and runs
795
+ // a callback function if anything has changed.
796
+ var InputPoller = function (textarea, callback, interval) { // {{{
797
+ var pollerObj = this;
798
+ var inputArea = textarea;
799
+
800
+ // Stored start, end and text. Used to see if there are changes to the input.
801
+ var lastStart;
802
+ var lastEnd;
803
+ var markdown;
804
+
805
+ var killHandle; // Used to cancel monitoring on destruction.
806
+ // Checks to see if anything has changed in the textarea.
807
+ // If so, it runs the callback.
808
+ this.tick = function () {
809
+
810
+ if (!util.isVisible(inputArea)) {
811
+ return;
812
+ }
813
+
814
+ // Update the selection start and end, text.
815
+ if (inputArea.selectionStart || inputArea.selectionStart === 0) {
816
+ var start = inputArea.selectionStart;
817
+ var end = inputArea.selectionEnd;
818
+ if (start != lastStart || end != lastEnd) {
819
+ lastStart = start;
820
+ lastEnd = end;
821
+
822
+ if (markdown != inputArea.value) {
823
+ markdown = inputArea.value;
824
+ return true;
825
+ }
826
+ }
827
+ }
828
+ return false;
829
+ };
830
+
831
+
832
+ var doTickCallback = function () {
833
+
834
+ if (!util.isVisible(inputArea)) {
835
+ return;
836
+ }
837
+
838
+ // If anything has changed, call the function.
839
+ if (pollerObj.tick()) {
840
+ callback();
841
+ }
842
+ };
843
+
844
+ // Set how often we poll the textarea for changes.
845
+ var assignInterval = function () {
846
+ killHandle = window.setInterval(doTickCallback, interval);
847
+ };
848
+
849
+ this.destroy = function () {
850
+ window.clearInterval(killHandle);
851
+ };
852
+
853
+ assignInterval();
854
+ }; // }}}
855
+ var PreviewManager = function (wmd) { // {{{
856
+ var managerObj = this;
857
+ var converter;
858
+ var poller;
859
+ var timeout;
860
+ var elapsedTime;
861
+ var oldInputText;
862
+ var htmlOut;
863
+ var maxDelay = 3000;
864
+ var startType = "delayed"; // The other legal value is "manual"
865
+ // Adds event listeners to elements and creates the input poller.
866
+ var setupEvents = function (inputElem, listener) {
867
+
868
+ util.addEvent(inputElem, "input", listener);
869
+ inputElem.onpaste = listener;
870
+ inputElem.ondrop = listener;
871
+
872
+ util.addEvent(inputElem, "keypress", listener);
873
+ util.addEvent(inputElem, "keydown", listener);
874
+ // previewPollInterval is set at the top of this file.
875
+ poller = new InputPoller(wmd.panels.input, listener, wmd.options.previewPollInterval);
876
+ };
877
+
878
+ var getDocScrollTop = function () {
879
+
880
+ var result = 0;
881
+
882
+ if (window.innerHeight) {
883
+ result = window.pageYOffset;
884
+ }
885
+ else if (document.documentElement && document.documentElement.scrollTop) {
886
+ result = document.documentElement.scrollTop;
887
+ }
888
+ else if (document.body) {
889
+ result = document.body.scrollTop;
890
+ }
891
+
892
+ return result;
893
+ };
894
+
895
+ var makePreviewHtml = function () {
896
+
897
+ // If there are no registered preview and output panels
898
+ // there is nothing to do.
899
+ if (!wmd.panels.preview && !wmd.panels.output) {
900
+ return;
901
+ }
902
+
903
+ var text = wmd.panels.input.value;
904
+ if (text && text == oldInputText) {
905
+ return; // Input text hasn't changed.
906
+ }
907
+ else {
908
+ oldInputText = text;
909
+ }
910
+
911
+ var prevTime = new Date().getTime();
912
+
913
+ if (!converter && wmd.showdown) {
914
+ converter = new wmd.showdown.converter();
915
+ }
916
+
917
+ if (converter) {
918
+ text = converter.makeHtml(text);
919
+ }
920
+
921
+ // Calculate the processing time of the HTML creation.
922
+ // It's used as the delay time in the event listener.
923
+ var currTime = new Date().getTime();
924
+ elapsedTime = currTime - prevTime;
925
+
926
+ pushPreviewHtml(text);
927
+ htmlOut = text;
928
+ };
929
+
930
+ // setTimeout is already used. Used as an event listener.
931
+ var applyTimeout = function () {
932
+
933
+ if (timeout) {
934
+ window.clearTimeout(timeout);
935
+ timeout = undefined;
936
+ }
937
+
938
+ if (startType !== "manual") {
939
+
940
+ var delay = 0;
941
+
942
+ if (startType === "delayed") {
943
+ delay = elapsedTime;
944
+ }
945
+
946
+ if (delay > maxDelay) {
947
+ delay = maxDelay;
948
+ }
949
+ timeout = window.setTimeout(makePreviewHtml, delay);
950
+ }
951
+ };
952
+
953
+ var getScaleFactor = function (panel) {
954
+ if (panel.scrollHeight <= panel.clientHeight) {
955
+ return 1;
956
+ }
957
+ return panel.scrollTop / (panel.scrollHeight - panel.clientHeight);
958
+ };
959
+
960
+ var setPanelScrollTops = function () {
961
+
962
+ if (wmd.panels.preview) {
963
+ wmd.panels.preview.scrollTop = (wmd.panels.preview.scrollHeight - wmd.panels.preview.clientHeight) * getScaleFactor(wmd.panels.preview);;
964
+ }
965
+
966
+ if (wmd.panels.output) {
967
+ wmd.panels.output.scrollTop = (wmd.panels.output.scrollHeight - wmd.panels.output.clientHeight) * getScaleFactor(wmd.panels.output);;
968
+ }
969
+ };
970
+
971
+ this.refresh = function (requiresRefresh) {
972
+
973
+ if (requiresRefresh) {
974
+ oldInputText = "";
975
+ makePreviewHtml();
976
+ }
977
+ else {
978
+ applyTimeout();
979
+ }
980
+ };
981
+
982
+ this.processingTime = function () {
983
+ return elapsedTime;
984
+ };
985
+
986
+ // The output HTML
987
+ this.output = function () {
988
+ return htmlOut;
989
+ };
990
+
991
+ // The mode can be "manual" or "delayed"
992
+ this.setUpdateMode = function (mode) {
993
+ startType = mode;
994
+ managerObj.refresh();
995
+ };
996
+
997
+ var isFirstTimeFilled = true;
998
+
999
+ var pushPreviewHtml = function (text) {
1000
+
1001
+ var emptyTop = position.getTop(wmd.panels.input) - getDocScrollTop();
1002
+
1003
+ // Send the encoded HTML to the output textarea/div.
1004
+ if (wmd.panels.output) {
1005
+ // The value property is only defined if the output is a textarea.
1006
+ if (wmd.panels.output.value !== undefined) {
1007
+ wmd.panels.output.value = text;
1008
+ }
1009
+ // Otherwise we are just replacing the text in a div.
1010
+ // Send the HTML wrapped in <pre><code>
1011
+ else {
1012
+ var newText = text.replace(/&/g, "&amp;");
1013
+ newText = newText.replace(/</g, "&lt;");
1014
+ wmd.panels.output.innerHTML = "<pre><code>" + newText + "</code></pre>";
1015
+ }
1016
+ }
1017
+
1018
+ if (wmd.panels.preview) {
1019
+ // original WMD code allowed javascript injection, like this:
1020
+ // <img src="http://www.google.com/intl/en_ALL/images/srpr/logo1w.png" onload="alert('haha');"/>
1021
+ // now, we first ensure elements (and attributes of IMG and A elements) are in a whitelist
1022
+ // and if not in whitelist, replace with blanks in preview to prevent XSS attacks
1023
+ // when editing malicious markdown
1024
+ // code courtesy of https://github.com/polestarsoft/wmd/commit/e7a09c9170ea23e7e806425f46d7423af2a74641
1025
+ if (wmd.options.tagFilter.enabled) {
1026
+ text = text.replace(/<[^<>]*>?/gi, function (tag) {
1027
+ return (tag.match(wmd.options.tagFilter.allowedTags) || tag.match(wmd.options.tagFilter.patternLink) || tag.match(wmd.options.tagFilter.patternImage)) ? tag : "";
1028
+ });
1029
+ }
1030
+ wmd.panels.preview.innerHTML = text;
1031
+ }
1032
+
1033
+ setPanelScrollTops();
1034
+
1035
+ if (isFirstTimeFilled) {
1036
+ isFirstTimeFilled = false;
1037
+ return;
1038
+ }
1039
+
1040
+ var fullTop = position.getTop(wmd.panels.input) - getDocScrollTop();
1041
+
1042
+ if (browser.isIE) {
1043
+ window.setTimeout(function () {
1044
+ window.scrollBy(0, fullTop - emptyTop);
1045
+ }, 0);
1046
+ }
1047
+ else {
1048
+ window.scrollBy(0, fullTop - emptyTop);
1049
+ }
1050
+ };
1051
+
1052
+ var init = function () {
1053
+
1054
+ setupEvents(wmd.panels.input, applyTimeout);
1055
+ makePreviewHtml();
1056
+
1057
+ if (wmd.panels.preview) {
1058
+ wmd.panels.preview.scrollTop = 0;
1059
+ }
1060
+ if (wmd.panels.output) {
1061
+ wmd.panels.output.scrollTop = 0;
1062
+ }
1063
+ };
1064
+
1065
+ this.destroy = function () {
1066
+ if (poller) {
1067
+ poller.destroy();
1068
+ }
1069
+ };
1070
+
1071
+ init();
1072
+ }; // }}}
1073
+ // Handles pushing and popping TextareaStates for undo/redo commands.
1074
+ // I should rename the stack variables to list.
1075
+ var UndoManager = function (wmd, textarea, pastePollInterval, callback) { // {{{
1076
+ var undoObj = this;
1077
+ var undoStack = []; // A stack of undo states
1078
+ var stackPtr = 0; // The index of the current state
1079
+ var mode = "none";
1080
+ var lastState; // The last state
1081
+ var poller;
1082
+ var timer; // The setTimeout handle for cancelling the timer
1083
+ var inputStateObj;
1084
+
1085
+ // Set the mode for later logic steps.
1086
+ var setMode = function (newMode, noSave) {
1087
+
1088
+ if (mode != newMode) {
1089
+ mode = newMode;
1090
+ if (!noSave) {
1091
+ saveState();
1092
+ }
1093
+ }
1094
+
1095
+ if (!browser.isIE || mode != "moving") {
1096
+ timer = window.setTimeout(refreshState, 1);
1097
+ }
1098
+ else {
1099
+ inputStateObj = null;
1100
+ }
1101
+ };
1102
+
1103
+ var refreshState = function () {
1104
+ inputStateObj = new TextareaState(textarea, wmd);
1105
+ poller.tick();
1106
+ timer = undefined;
1107
+ };
1108
+
1109
+ this.setCommandMode = function () {
1110
+ mode = "command";
1111
+ saveState();
1112
+ timer = window.setTimeout(refreshState, 0);
1113
+ };
1114
+
1115
+ this.canUndo = function () {
1116
+ return stackPtr > 1;
1117
+ };
1118
+
1119
+ this.canRedo = function () {
1120
+ if (undoStack[stackPtr + 1]) {
1121
+ return true;
1122
+ }
1123
+ return false;
1124
+ };
1125
+
1126
+ // Removes the last state and restores it.
1127
+ this.undo = function () {
1128
+
1129
+ if (undoObj.canUndo()) {
1130
+ if (lastState) {
1131
+ // What about setting state -1 to null or checking for undefined?
1132
+ lastState.restore();
1133
+ lastState = null;
1134
+ }
1135
+ else {
1136
+ undoStack[stackPtr] = new TextareaState(textarea, wmd);
1137
+ undoStack[--stackPtr].restore();
1138
+
1139
+ if (callback) {
1140
+ callback();
1141
+ }
1142
+ }
1143
+ }
1144
+
1145
+ mode = "none";
1146
+ textarea.focus();
1147
+ refreshState();
1148
+ };
1149
+
1150
+ // Redo an action.
1151
+ this.redo = function () {
1152
+
1153
+ if (undoObj.canRedo()) {
1154
+
1155
+ undoStack[++stackPtr].restore();
1156
+
1157
+ if (callback) {
1158
+ callback();
1159
+ }
1160
+ }
1161
+
1162
+ mode = "none";
1163
+ textarea.focus();
1164
+ refreshState();
1165
+ };
1166
+
1167
+ // Push the input area state to the stack.
1168
+ var saveState = function () {
1169
+
1170
+ var currState = inputStateObj || new TextareaState(textarea, wmd);
1171
+
1172
+ if (!currState) {
1173
+ return false;
1174
+ }
1175
+ if (mode == "moving") {
1176
+ if (!lastState) {
1177
+ lastState = currState;
1178
+ }
1179
+ return;
1180
+ }
1181
+ if (lastState) {
1182
+ if (undoStack[stackPtr - 1].text != lastState.text) {
1183
+ undoStack[stackPtr++] = lastState;
1184
+ }
1185
+ lastState = null;
1186
+ }
1187
+ undoStack[stackPtr++] = currState;
1188
+ undoStack[stackPtr + 1] = null;
1189
+ if (callback) {
1190
+ callback();
1191
+ }
1192
+ };
1193
+
1194
+ var handleCtrlYZ = function (event) {
1195
+
1196
+ var handled = false;
1197
+
1198
+ if (event.ctrlKey || event.metaKey) {
1199
+
1200
+ // IE and Opera do not support charCode.
1201
+ var keyCode = event.charCode || event.keyCode;
1202
+ var keyCodeChar = String.fromCharCode(keyCode);
1203
+
1204
+ switch (keyCodeChar) {
1205
+
1206
+ case "y":
1207
+ undoObj.redo();
1208
+ handled = true;
1209
+ break;
1210
+
1211
+ case "z":
1212
+ if (!event.shiftKey) {
1213
+ undoObj.undo();
1214
+ }
1215
+ else {
1216
+ undoObj.redo();
1217
+ }
1218
+ handled = true;
1219
+ break;
1220
+ }
1221
+ }
1222
+
1223
+ if (handled) {
1224
+ if (event.preventDefault) {
1225
+ event.preventDefault();
1226
+ }
1227
+ if (window.event) {
1228
+ window.event.returnValue = false;
1229
+ }
1230
+ return;
1231
+ }
1232
+ };
1233
+
1234
+ // Set the mode depending on what is going on in the input area.
1235
+ var handleModeChange = function (event) {
1236
+
1237
+ if (!event.ctrlKey && !event.metaKey) {
1238
+
1239
+ var keyCode = event.keyCode;
1240
+
1241
+ if ((keyCode >= 33 && keyCode <= 40) || (keyCode >= 63232 && keyCode <= 63235)) {
1242
+ // 33 - 40: page up/dn and arrow keys
1243
+ // 63232 - 63235: page up/dn and arrow keys on safari
1244
+ setMode("moving");
1245
+ }
1246
+ else if (keyCode == 8 || keyCode == 46 || keyCode == 127) {
1247
+ // 8: backspace
1248
+ // 46: delete
1249
+ // 127: delete
1250
+ setMode("deleting");
1251
+ }
1252
+ else if (keyCode == 13) {
1253
+ // 13: Enter
1254
+ setMode("newlines");
1255
+ }
1256
+ else if (keyCode == 27) {
1257
+ // 27: escape
1258
+ setMode("escape");
1259
+ }
1260
+ else if ((keyCode < 16 || keyCode > 20) && keyCode != 91) {
1261
+ // 16-20 are shift, etc.
1262
+ // 91: left window key
1263
+ // I think this might be a little messed up since there are
1264
+ // a lot of nonprinting keys above 20.
1265
+ setMode("typing");
1266
+ }
1267
+ }
1268
+ };
1269
+
1270
+ var setEventHandlers = function () {
1271
+
1272
+ util.addEvent(textarea, "keypress", function (event) {
1273
+ // keyCode 89: y
1274
+ // keyCode 90: z
1275
+ if ((event.ctrlKey || event.metaKey) && (event.keyCode == 89 || event.keyCode == 90)) {
1276
+ event.preventDefault();
1277
+ }
1278
+ });
1279
+
1280
+ var handlePaste = function () {
1281
+ if (browser.isIE || (inputStateObj && inputStateObj.text != textarea.value)) {
1282
+ if (timer == undefined) {
1283
+ mode = "paste";
1284
+ saveState();
1285
+ refreshState();
1286
+ }
1287
+ }
1288
+ };
1289
+
1290
+ poller = new InputPoller(textarea, handlePaste, pastePollInterval);
1291
+
1292
+ util.addEvent(textarea, "keydown", handleCtrlYZ);
1293
+ util.addEvent(textarea, "keydown", handleModeChange);
1294
+
1295
+ util.addEvent(textarea, "mousedown", function () {
1296
+ setMode("moving");
1297
+ });
1298
+ textarea.onpaste = handlePaste;
1299
+ textarea.ondrop = handlePaste;
1300
+ };
1301
+
1302
+ var init = function () {
1303
+ setEventHandlers();
1304
+ refreshState();
1305
+ saveState();
1306
+ };
1307
+
1308
+ this.destroy = function () {
1309
+ if (poller) {
1310
+ poller.destroy();
1311
+ }
1312
+ };
1313
+
1314
+ init();
1315
+ }; //}}}
1316
+ WMDEditor.util = util;
1317
+ WMDEditor.position = position;
1318
+ WMDEditor.TextareaState = TextareaState;
1319
+ WMDEditor.InputPoller = InputPoller;
1320
+ WMDEditor.PreviewManager = PreviewManager;
1321
+ WMDEditor.UndoManager = UndoManager;
1322
+
1323
+ // A few handy aliases for readability.
1324
+ var doc = window.document;
1325
+ var re = window.RegExp;
1326
+ var nav = window.navigator;
1327
+
1328
+ function get_browser() {
1329
+ var b = {};
1330
+ b.isIE = /msie/.test(nav.userAgent.toLowerCase());
1331
+ b.isIE_5or6 = /msie 6/.test(nav.userAgent.toLowerCase()) || /msie 5/.test(nav.userAgent.toLowerCase());
1332
+ b.isIE_7plus = b.isIE && !b.isIE_5or6;
1333
+ b.isOpera = /opera/.test(nav.userAgent.toLowerCase());
1334
+ b.isKonqueror = /konqueror/.test(nav.userAgent.toLowerCase());
1335
+ return b;
1336
+ }
1337
+
1338
+ // Used to work around some browser bugs where we can't use feature testing.
1339
+ var browser = get_browser();
1340
+
1341
+ var wmdBase = function (wmd, wmd_options) { // {{{
1342
+ // Some namespaces.
1343
+ //wmd.Util = {};
1344
+ //wmd.Position = {};
1345
+ wmd.Command = {};
1346
+ wmd.Global = {};
1347
+ wmd.buttons = {};
1348
+
1349
+ wmd.showdown = window.Showdown;
1350
+
1351
+ var util = WMDEditor.util;
1352
+ var position = WMDEditor.position;
1353
+ var command = wmd.Command;
1354
+
1355
+ // Internet explorer has problems with CSS sprite buttons that use HTML
1356
+ // lists. When you click on the background image "button", IE will
1357
+ // select the non-existent link text and discard the selection in the
1358
+ // textarea. The solution to this is to cache the textarea selection
1359
+ // on the button's mousedown event and set a flag. In the part of the
1360
+ // code where we need to grab the selection, we check for the flag
1361
+ // and, if it's set, use the cached area instead of querying the
1362
+ // textarea.
1363
+ //
1364
+ // This ONLY affects Internet Explorer (tested on versions 6, 7
1365
+ // and 8) and ONLY on button clicks. Keyboard shortcuts work
1366
+ // normally since the focus never leaves the textarea.
1367
+ wmd.ieCachedRange = null; // cached textarea selection
1368
+ wmd.ieRetardedClick = false; // flag
1369
+ // I think my understanding of how the buttons and callbacks are stored in the array is incomplete.
1370
+ wmd.editor = function (previewRefreshCallback) { // {{{
1371
+ if (!previewRefreshCallback) {
1372
+ previewRefreshCallback = function () {};
1373
+ }
1374
+
1375
+ var inputBox = wmd.panels.input;
1376
+
1377
+ var offsetHeight = 0;
1378
+
1379
+ var editObj = this;
1380
+
1381
+ var mainDiv;
1382
+ var mainSpan;
1383
+
1384
+ var div; // This name is pretty ambiguous. I should rename this.
1385
+ // Used to cancel recurring events from setInterval.
1386
+ var creationHandle;
1387
+
1388
+ var undoMgr; // The undo manager
1389
+ // Perform the button's action.
1390
+ var doClick = function (button) {
1391
+
1392
+ inputBox.focus();
1393
+
1394
+ if (button.textOp) {
1395
+
1396
+ if (undoMgr) {
1397
+ undoMgr.setCommandMode();
1398
+ }
1399
+
1400
+ var state = new TextareaState(wmd.panels.input, wmd);
1401
+
1402
+ if (!state) {
1403
+ return;
1404
+ }
1405
+
1406
+ var chunks = state.getChunks();
1407
+
1408
+ // Some commands launch a "modal" prompt dialog. Javascript
1409
+ // can't really make a modal dialog box and the WMD code
1410
+ // will continue to execute while the dialog is displayed.
1411
+ // This prevents the dialog pattern I'm used to and means
1412
+ // I can't do something like this:
1413
+ //
1414
+ // var link = CreateLinkDialog();
1415
+ // makeMarkdownLink(link);
1416
+ //
1417
+ // Instead of this straightforward method of handling a
1418
+ // dialog I have to pass any code which would execute
1419
+ // after the dialog is dismissed (e.g. link creation)
1420
+ // in a function parameter.
1421
+ //
1422
+ // Yes this is awkward and I think it sucks, but there's
1423
+ // no real workaround. Only the image and link code
1424
+ // create dialogs and require the function pointers.
1425
+ var fixupInputArea = function () {
1426
+
1427
+ inputBox.focus();
1428
+
1429
+ if (chunks) {
1430
+ state.setChunks(chunks);
1431
+ }
1432
+
1433
+ state.restore();
1434
+ previewRefreshCallback();
1435
+ };
1436
+
1437
+ var useDefaultText = true;
1438
+ var noCleanup = button.textOp(chunks, fixupInputArea, useDefaultText);
1439
+
1440
+ if (!noCleanup) {
1441
+ fixupInputArea();
1442
+ }
1443
+
1444
+ }
1445
+
1446
+ if (button.execute) {
1447
+ button.execute(editObj);
1448
+ }
1449
+ };
1450
+
1451
+ var setUndoRedoButtonStates = function () {
1452
+ if (undoMgr) {
1453
+ if (wmd.buttons["wmd-undo-button"]) setupButton(wmd.buttons["wmd-undo-button"], undoMgr.canUndo());
1454
+ if (wmd.buttons["wmd-redo-button"]) setupButton(wmd.buttons["wmd-redo-button"], undoMgr.canRedo());
1455
+ }
1456
+ };
1457
+
1458
+ var setupButton = function (button, isEnabled) {
1459
+
1460
+ if (isEnabled) {
1461
+ button.className = button.className.replace(new RegExp("(^|\\s+)disabled(\\s+|$)"), ' ');
1462
+
1463
+ // IE tries to select the background image "button" text (it's
1464
+ // implemented in a list item) so we have to cache the selection
1465
+ // on mousedown.
1466
+ if (browser.isIE) {
1467
+ button.onmousedown = function () {
1468
+ wmd.ieRetardedClick = true;
1469
+ wmd.ieCachedRange = document.selection.createRange();
1470
+ };
1471
+ }
1472
+
1473
+ if (!button.isHelp) {
1474
+ button.onclick = function () {
1475
+ if (this.onmouseout) {
1476
+ this.onmouseout();
1477
+ }
1478
+ doClick(this);
1479
+ return false;
1480
+ };
1481
+ }
1482
+ }
1483
+ else {
1484
+ button.className += (button.className ? ' ' : '') + 'disabled';
1485
+ button.onmouseover = button.onmouseout = button.onclick = function () {};
1486
+ }
1487
+ };
1488
+
1489
+ var makeSpritedButtonRow = function () {
1490
+
1491
+ var buttonBar = (typeof wmd_options.button_bar == 'string') ? document.getElementById(wmd_options.button_bar || "wmd-button-bar") : wmd_options.button_bar;
1492
+
1493
+ var normalYShift = "0px";
1494
+ var disabledYShift = "-20px";
1495
+ var highlightYShift = "-40px";
1496
+
1497
+ var buttonRow = document.createElement("ul");
1498
+ buttonRow.className = "wmd-button-row";
1499
+ buttonRow = buttonBar.appendChild(buttonRow);
1500
+
1501
+ var xoffset = 0;
1502
+
1503
+ function createButton(name, title, textOp) {
1504
+ var button = document.createElement("li");
1505
+ wmd.buttons[name] = button;
1506
+ button.className = "wmd-button " + name;
1507
+ button.XShift = xoffset + "px";
1508
+ xoffset -= 20;
1509
+
1510
+ if (title) button.title = title;
1511
+
1512
+ if (textOp) button.textOp = textOp;
1513
+
1514
+ return button;
1515
+ }
1516
+
1517
+ function addButton(name, title, textOp) {
1518
+ var button = createButton(name, title, textOp);
1519
+
1520
+ setupButton(button, true);
1521
+ buttonRow.appendChild(button);
1522
+ return button;
1523
+ }
1524
+
1525
+ function addSpacer() {
1526
+ var spacer = document.createElement("li");
1527
+ spacer.className = "wmd-spacer";
1528
+ buttonRow.appendChild(spacer);
1529
+ return spacer;
1530
+ }
1531
+
1532
+ // Detection of mac for displaying proper modifier key.
1533
+ var modifierKey = (navigator.appVersion.indexOf("Mac")!=-1) ? "Cmd" : "Ctrl";
1534
+
1535
+ var buttonlist = wmd_options.buttons.split(' ');
1536
+ for (var i=0;i<buttonlist.length;i++) {
1537
+ switch (buttonlist[i]) {
1538
+ case "bold":
1539
+ addButton("wmd-bold-button", "Strong <strong> "+modifierKey+"+B", command.doBold);
1540
+ break;
1541
+ case "italic":
1542
+ addButton("wmd-italic-button", "Emphasis <em> "+modifierKey+"+I", command.doItalic);
1543
+ break;
1544
+ case 'link':
1545
+ addButton("wmd-link-button", "Hyperlink <a> "+modifierKey+"+L", function (chunk, postProcessing, useDefaultText) {
1546
+ return command.doLinkOrImage(chunk, postProcessing, false);
1547
+ });
1548
+ break;
1549
+ case 'blockquote':
1550
+ addButton("wmd-quote-button", "Blockquote <blockquote> "+modifierKey+"+Q", command.doBlockquote);
1551
+ break;
1552
+ case 'code':
1553
+ addButton("wmd-code-button", "Code Sample <pre><code> "+modifierKey+"+K", command.doCode);
1554
+ break;
1555
+ case 'image':
1556
+ addButton("wmd-image-button", "Image <img> "+modifierKey+"+G", function (chunk, postProcessing, useDefaultText) {
1557
+ return command.doLinkOrImage(chunk, postProcessing, true);
1558
+ });
1559
+ break;
1560
+ case 'ol':
1561
+ addButton("wmd-olist-button", "Numbered List <ol> "+modifierKey+"+O", function (chunk, postProcessing, useDefaultText) {
1562
+ command.doList(chunk, postProcessing, true, useDefaultText);
1563
+ });
1564
+ break;
1565
+ case 'ul':
1566
+ addButton("wmd-ulist-button", "Bulleted List <ul> "+modifierKey+"+U", function (chunk, postProcessing, useDefaultText) {
1567
+ command.doList(chunk, postProcessing, false, useDefaultText);
1568
+ });
1569
+ break;
1570
+ case 'heading':
1571
+ addButton("wmd-heading-button", "Heading <h1>/<h2> "+modifierKey+"+H", command.doHeading);
1572
+ break;
1573
+ case 'hr':
1574
+ addButton("wmd-hr-button", "Horizontal Rule <hr> "+modifierKey+"+R", command.doHorizontalRule);
1575
+ break;
1576
+ case 'undo':
1577
+ var undoButton = addButton("wmd-undo-button", "Undo - "+modifierKey+"+Z");
1578
+ undoButton.execute = function (manager) {
1579
+ manager.undo();
1580
+ };
1581
+ break;
1582
+ case 'redo':
1583
+ var redoButton = addButton("wmd-redo-button", "Redo - "+modifierKey+"+Y");
1584
+ if (/win/.test(nav.platform.toLowerCase())) {
1585
+ redoButton.title = "Redo - "+modifierKey+"+Y";
1586
+ }
1587
+ else {
1588
+ // mac and other non-Windows platforms
1589
+ redoButton.title = "Redo - "+modifierKey+"+Shift+Z";
1590
+ }
1591
+ redoButton.execute = function (manager) {
1592
+ manager.redo();
1593
+ };
1594
+ break;
1595
+ case 'help':
1596
+ var helpButton = createButton("wmd-help-button");
1597
+ helpButton.isHelp = true;
1598
+ setupButton(helpButton, true);
1599
+ buttonRow.appendChild(helpButton);
1600
+
1601
+ var helpAnchor = document.createElement("a");
1602
+ helpAnchor.href = wmd_options.helpLink;
1603
+ helpAnchor.target = wmd_options.helpTarget;
1604
+ helpAnchor.title = wmd_options.helpHoverTitle;
1605
+ helpButton.appendChild(helpAnchor);
1606
+ break;
1607
+ case '':
1608
+ addSpacer();
1609
+ break;
1610
+ }
1611
+ }
1612
+
1613
+ setUndoRedoButtonStates();
1614
+ };
1615
+
1616
+ var setupEditor = function () {
1617
+
1618
+ if (/\?noundo/.test(document.location.href)) {
1619
+ wmd.nativeUndo = true;
1620
+ }
1621
+
1622
+ if (!wmd.nativeUndo) {
1623
+ undoMgr = new UndoManager(wmd, wmd.panels.input, wmd.options.pastePollInterval, function () {
1624
+ previewRefreshCallback();
1625
+ setUndoRedoButtonStates();
1626
+ });
1627
+ }
1628
+
1629
+ makeSpritedButtonRow();
1630
+
1631
+
1632
+ var keyEvent = "keydown";
1633
+ if (browser.isOpera) {
1634
+ keyEvent = "keypress";
1635
+ }
1636
+
1637
+ util.addEvent(inputBox, keyEvent, function (key) {
1638
+
1639
+ // Check to see if we have a button key and, if so execute the callback.
1640
+ if (wmd.options.modifierKeys && (key.ctrlKey || key.metaKey)) {
1641
+
1642
+ var keyCode = key.charCode || key.keyCode;
1643
+ var keyCodeStr = String.fromCharCode(keyCode).toLowerCase();
1644
+
1645
+ switch (keyCodeStr) {
1646
+ case wmd.options.modifierKeys.bold:
1647
+ if (wmd.buttons["wmd-bold-button"]) doClick(wmd.buttons["wmd-bold-button"]);
1648
+ else return;
1649
+ break;
1650
+ case wmd.options.modifierKeys.italic:
1651
+ if (wmd.buttons["wmd-italic-button"]) doClick(wmd.buttons["wmd-italic-button"]);
1652
+ else return;
1653
+ break;
1654
+ case wmd.options.modifierKeys.link:
1655
+ if (wmd.buttons["wmd-link-button"]) doClick(wmd.buttons["wmd-link-button"]);
1656
+ else return;
1657
+ break;
1658
+ case wmd.options.modifierKeys.quote:
1659
+ if (wmd.buttons["wmd-quote-button"]) doClick(wmd.buttons["wmd-quote-button"]);
1660
+ else return;
1661
+ break;
1662
+ case wmd.options.modifierKeys.code:
1663
+ if (wmd.buttons["wmd-code-button"]) doClick(wmd.buttons["wmd-code-button"]);
1664
+ else return;
1665
+ break;
1666
+ case wmd.options.modifierKeys.image:
1667
+ if (wmd.buttons["wmd-image-button"]) doClick(wmd.buttons["wmd-image-button"]);
1668
+ else return;
1669
+ break;
1670
+ case wmd.options.modifierKeys.orderedList:
1671
+ if (wmd.buttons["wmd-olist-button"]) doClick(wmd.buttons["wmd-olist-button"]);
1672
+ else return;
1673
+ break;
1674
+ case wmd.options.modifierKeys.unorderedList:
1675
+ if (wmd.buttons["wmd-ulist-button"]) doClick(wmd.buttons["wmd-ulist-button"]);
1676
+ else return;
1677
+ break;
1678
+ case wmd.options.modifierKeys.heading:
1679
+ if (wmd.buttons["wmd-heading-button"]) doClick(wmd.buttons["wmd-heading-button"]);
1680
+ else return;
1681
+ break;
1682
+ case wmd.options.modifierKeys.horizontalRule:
1683
+ if (wmd.buttons["wmd-hr-button"]) doClick(wmd.buttons["wmd-hr-button"]);
1684
+ else return;
1685
+ break;
1686
+ case wmd.options.modifierKeys.redo:
1687
+ if (wmd.buttons["wmd-redo-button"]) doClick(wmd.buttons["wmd-redo-button"]);
1688
+ else return;
1689
+ break;
1690
+ case wmd.options.modifierKeys.undo:
1691
+ if (key.shiftKey) {
1692
+ if (wmd.buttons["wmd-redo-button"]) doClick(wmd.buttons["wmd-redo-button"]);
1693
+ else return;
1694
+ } else {
1695
+ if (wmd.buttons["wmd-undo-button"]) doClick(wmd.buttons["wmd-undo-button"]);
1696
+ else return;
1697
+ }
1698
+ break;
1699
+ default:
1700
+ return;
1701
+ }
1702
+
1703
+
1704
+ if (key.preventDefault) {
1705
+ key.preventDefault();
1706
+ }
1707
+
1708
+ if (window.event) {
1709
+ window.event.returnValue = false;
1710
+ }
1711
+ }
1712
+ });
1713
+
1714
+ // Auto-continue lists, code blocks and block quotes when
1715
+ // the enter key is pressed.
1716
+ util.addEvent(inputBox, "keyup", function (key) {
1717
+ if (!key.shiftKey && !key.ctrlKey && !key.metaKey) {
1718
+ var keyCode = key.charCode || key.keyCode;
1719
+ // Key code 13 is Enter
1720
+ if (keyCode === 13) {
1721
+ fakeButton = {};
1722
+ fakeButton.textOp = command.doAutoindent;
1723
+ doClick(fakeButton);
1724
+ }
1725
+ }
1726
+ });
1727
+
1728
+ // Disable ESC clearing the input textarea on IE
1729
+ if (browser.isIE) {
1730
+ util.addEvent(inputBox, "keydown", function (key) {
1731
+ var code = key.keyCode;
1732
+ // Key code 27 is ESC
1733
+ if (code === 27) {
1734
+ return false;
1735
+ }
1736
+ });
1737
+ }
1738
+ };
1739
+
1740
+
1741
+ this.undo = function () {
1742
+ if (undoMgr) {
1743
+ undoMgr.undo();
1744
+ }
1745
+ };
1746
+
1747
+ this.redo = function () {
1748
+ if (undoMgr) {
1749
+ undoMgr.redo();
1750
+ }
1751
+ };
1752
+
1753
+ // This is pretty useless. The setupEditor function contents
1754
+ // should just be copied here.
1755
+ var init = function () {
1756
+ setupEditor();
1757
+ };
1758
+
1759
+ this.destroy = function () {
1760
+ if (undoMgr) {
1761
+ undoMgr.destroy();
1762
+ }
1763
+ if (div.parentNode) {
1764
+ div.parentNode.removeChild(div);
1765
+ }
1766
+ if (inputBox) {
1767
+ inputBox.style.marginTop = "";
1768
+ }
1769
+ window.clearInterval(creationHandle);
1770
+ };
1771
+
1772
+ init();
1773
+ }; // }}}
1774
+ // command {{{
1775
+ // The markdown symbols - 4 spaces = code, > = blockquote, etc.
1776
+ command.prefixes = "(?:\\s{4,}|\\s*>|\\s*-\\s+|\\s*\\d+\\.|=|\\+|-|_|\\*|#|\\s*\\[[^\n]]+\\]:)";
1777
+
1778
+ // Remove markdown symbols from the chunk selection.
1779
+ command.unwrap = function (chunk) {
1780
+ var txt = new re("([^\\n])\\n(?!(\\n|" + command.prefixes + "))", "g");
1781
+ chunk.selection = chunk.selection.replace(txt, "$1 $2");
1782
+ };
1783
+
1784
+ command.wrap = function (chunk, len) {
1785
+ command.unwrap(chunk);
1786
+ var regex = new re("(.{1," + len + "})( +|$\\n?)", "gm");
1787
+
1788
+ chunk.selection = chunk.selection.replace(regex, function (line, marked) {
1789
+ if (new re("^" + command.prefixes, "").test(line)) {
1790
+ return line;
1791
+ }
1792
+ return marked + "\n";
1793
+ });
1794
+
1795
+ chunk.selection = chunk.selection.replace(/\s+$/, "");
1796
+ };
1797
+
1798
+ command.doBold = function (chunk, postProcessing, useDefaultText) {
1799
+ return command.doBorI(chunk, 2, "strong text");
1800
+ };
1801
+
1802
+ command.doItalic = function (chunk, postProcessing, useDefaultText) {
1803
+ return command.doBorI(chunk, 1, "emphasized text");
1804
+ };
1805
+
1806
+ // chunk: The selected region that will be enclosed with */**
1807
+ // nStars: 1 for italics, 2 for bold
1808
+ // insertText: If you just click the button without highlighting text, this gets inserted
1809
+ command.doBorI = function (chunk, nStars, insertText) {
1810
+
1811
+ // Get rid of whitespace and fixup newlines.
1812
+ chunk.trimWhitespace();
1813
+ chunk.selection = chunk.selection.replace(/\n{2,}/g, "\n");
1814
+
1815
+ // Look for stars before and after. Is the chunk already marked up?
1816
+ chunk.before.search(/(\**$)/);
1817
+ var starsBefore = re.$1;
1818
+
1819
+ chunk.after.search(/(^\**)/);
1820
+ var starsAfter = re.$1;
1821
+
1822
+ var prevStars = Math.min(starsBefore.length, starsAfter.length);
1823
+
1824
+ // Remove stars if we have to since the button acts as a toggle.
1825
+ if ((prevStars >= nStars) && (prevStars != 2 || nStars != 1)) {
1826
+ chunk.before = chunk.before.replace(re("[*]{" + nStars + "}$", ""), "");
1827
+ chunk.after = chunk.after.replace(re("^[*]{" + nStars + "}", ""), "");
1828
+ }
1829
+ else if (!chunk.selection && starsAfter) {
1830
+ // It's not really clear why this code is necessary. It just moves
1831
+ // some arbitrary stuff around.
1832
+ chunk.after = chunk.after.replace(/^([*_]*)/, "");
1833
+ chunk.before = chunk.before.replace(/(\s?)$/, "");
1834
+ var whitespace = re.$1;
1835
+ chunk.before = chunk.before + starsAfter + whitespace;
1836
+ }
1837
+ else {
1838
+
1839
+ // In most cases, if you don't have any selected text and click the button
1840
+ // you'll get a selected, marked up region with the default text inserted.
1841
+ if (!chunk.selection && !starsAfter) {
1842
+ chunk.selection = insertText;
1843
+ }
1844
+
1845
+ // Add the true markup.
1846
+ var markup = nStars <= 1 ? "*" : "**"; // shouldn't the test be = ?
1847
+ chunk.before = chunk.before + markup;
1848
+ chunk.after = markup + chunk.after;
1849
+ }
1850
+
1851
+ return;
1852
+ };
1853
+
1854
+ command.stripLinkDefs = function (text, defsToAdd) {
1855
+
1856
+ text = text.replace(/^[ ]{0,3}\[(\d+)\]:[ \t]*\n?[ \t]*<?(\S+?)>?[ \t]*\n?[ \t]*(?:(\n*)["(](.+?)[")][ \t]*)?(?:\n+|$)/gm, function (totalMatch, id, link, newlines, title) {
1857
+ defsToAdd[id] = totalMatch.replace(/\s*$/, "");
1858
+ if (newlines) {
1859
+ // Strip the title and return that separately.
1860
+ defsToAdd[id] = totalMatch.replace(/["(](.+?)[")]$/, "");
1861
+ return newlines + title;
1862
+ }
1863
+ return "";
1864
+ });
1865
+
1866
+ return text;
1867
+ };
1868
+
1869
+ command.addLinkDef = function (chunk, linkDef) {
1870
+
1871
+ var refNumber = 0; // The current reference number
1872
+ var defsToAdd = {}; //
1873
+ // Start with a clean slate by removing all previous link definitions.
1874
+ chunk.before = command.stripLinkDefs(chunk.before, defsToAdd);
1875
+ chunk.selection = command.stripLinkDefs(chunk.selection, defsToAdd);
1876
+ chunk.after = command.stripLinkDefs(chunk.after, defsToAdd);
1877
+
1878
+ var defs = "";
1879
+ var regex = /(\[(?:\[[^\]]*\]|[^\[\]])*\][ ]?(?:\n[ ]*)?\[)(\d+)(\])/g;
1880
+
1881
+ var addDefNumber = function (def) {
1882
+ refNumber++;
1883
+ def = def.replace(/^[ ]{0,3}\[(\d+)\]:/, " [" + refNumber + "]:");
1884
+ defs += "\n" + def;
1885
+ };
1886
+
1887
+ var getLink = function (wholeMatch, link, id, end) {
1888
+
1889
+ if (defsToAdd[id]) {
1890
+ addDefNumber(defsToAdd[id]);
1891
+ return link + refNumber + end;
1892
+
1893
+ }
1894
+ return wholeMatch;
1895
+ };
1896
+
1897
+ chunk.before = chunk.before.replace(regex, getLink);
1898
+
1899
+ if (linkDef) {
1900
+ addDefNumber(linkDef);
1901
+ }
1902
+ else {
1903
+ chunk.selection = chunk.selection.replace(regex, getLink);
1904
+ }
1905
+
1906
+ var refOut = refNumber;
1907
+
1908
+ chunk.after = chunk.after.replace(regex, getLink);
1909
+
1910
+ if (chunk.after) {
1911
+ chunk.after = chunk.after.replace(/\n*$/, "");
1912
+ }
1913
+ if (!chunk.after) {
1914
+ chunk.selection = chunk.selection.replace(/\n*$/, "");
1915
+ }
1916
+
1917
+ chunk.after += "\n\n" + defs;
1918
+
1919
+ return refOut;
1920
+ };
1921
+
1922
+ command.doLinkOrImage = function (chunk, postProcessing, isImage) {
1923
+
1924
+ chunk.trimWhitespace();
1925
+ chunk.findTags(/\s*!?\[/, /\][ ]?(?:\n[ ]*)?(\[.*?\])?/);
1926
+
1927
+ if (chunk.endTag.length > 1) {
1928
+
1929
+ chunk.startTag = chunk.startTag.replace(/!?\[/, "");
1930
+ chunk.endTag = "";
1931
+ command.addLinkDef(chunk, null);
1932
+
1933
+ }
1934
+ else {
1935
+
1936
+ if (/\n\n/.test(chunk.selection)) {
1937
+ command.addLinkDef(chunk, null);
1938
+ return;
1939
+ }
1940
+
1941
+ // The function to be executed when you enter a link and press OK or Cancel.
1942
+ // Marks up the link and adds the ref.
1943
+ var makeLinkMarkdown = function (link) {
1944
+ if (link !== null) {
1945
+
1946
+ chunk.startTag = chunk.endTag = "";
1947
+ var linkDef = " [999]: " + link;
1948
+
1949
+ var num = command.addLinkDef(chunk, linkDef);
1950
+ chunk.startTag = isImage ? "![" : "[";
1951
+ chunk.endTag = "][" + num + "]";
1952
+
1953
+ if (!chunk.selection) {
1954
+ if (isImage) {
1955
+ chunk.selection = "alt text";
1956
+ }
1957
+ else {
1958
+ chunk.selection = "link text";
1959
+ }
1960
+ }
1961
+ }
1962
+ postProcessing();
1963
+ };
1964
+
1965
+ if (isImage) {
1966
+ util.prompt(wmd_options.imageDialogText, wmd_options.imageDefaultText, makeLinkMarkdown, 'Image');
1967
+ }
1968
+ else {
1969
+ util.prompt(wmd_options.linkDialogText, wmd_options.linkDefaultText, makeLinkMarkdown, 'Link');
1970
+ }
1971
+ return true;
1972
+ }
1973
+ };
1974
+
1975
+ // Moves the cursor to the next line and continues lists, quotes and code.
1976
+ command.doAutoindent = function (chunk, postProcessing, useDefaultText) {
1977
+ if (!wmd.options.autoFormatting) return;
1978
+
1979
+ if (wmd.options.autoFormatting.list) chunk.before = chunk.before.replace(/(\n|^)[ ]{0,3}([*+-]|\d+[.])[ \t]*\n$/, "\n\n");
1980
+ if (wmd.options.autoFormatting.quote) chunk.before = chunk.before.replace(/(\n|^)[ ]{0,3}>[ \t]*\n$/, "\n\n");
1981
+ if (wmd.options.autoFormatting.code) chunk.before = chunk.before.replace(/(\n|^)[ \t]+\n$/, "\n\n");
1982
+
1983
+ useDefaultText = false;
1984
+
1985
+ if (/(\n|^)[ ]{0,3}([*+-])[ \t]+.*\n$/.test(chunk.before)) {
1986
+ if (command.doList && wmd.options.autoFormatting.list) {
1987
+ command.doList(chunk, postProcessing, false, true);
1988
+ }
1989
+ }
1990
+ if (/(\n|^)[ ]{0,3}(\d+[.])[ \t]+.*\n$/.test(chunk.before)) {
1991
+ if (command.doList && wmd.options.autoFormatting.list) {
1992
+ command.doList(chunk, postProcessing, true, true);
1993
+ }
1994
+ }
1995
+ if (/(\n|^)[ ]{0,3}>[ \t]+.*\n$/.test(chunk.before)) {
1996
+ if (command.doBlockquote && wmd.options.autoFormatting.quote) {
1997
+ command.doBlockquote(chunk, postProcessing, useDefaultText);
1998
+ }
1999
+ }
2000
+ if (/(\n|^)(\t|[ ]{4,}).*\n$/.test(chunk.before)) {
2001
+ if (command.doCode && wmd.options.autoFormatting.code) {
2002
+ command.doCode(chunk, postProcessing, useDefaultText);
2003
+ }
2004
+ }
2005
+ };
2006
+
2007
+ command.doBlockquote = function (chunk, postProcessing, useDefaultText) {
2008
+
2009
+ chunk.selection = chunk.selection.replace(/^(\n*)([^\r]+?)(\n*)$/, function (totalMatch, newlinesBefore, text, newlinesAfter) {
2010
+ chunk.before += newlinesBefore;
2011
+ chunk.after = newlinesAfter + chunk.after;
2012
+ return text;
2013
+ });
2014
+
2015
+ chunk.before = chunk.before.replace(/(>[ \t]*)$/, function (totalMatch, blankLine) {
2016
+ chunk.selection = blankLine + chunk.selection;
2017
+ return "";
2018
+ });
2019
+
2020
+ var defaultText = useDefaultText ? "Blockquote" : "";
2021
+ chunk.selection = chunk.selection.replace(/^(\s|>)+$/, "");
2022
+ chunk.selection = chunk.selection || defaultText;
2023
+
2024
+ if (chunk.before) {
2025
+ chunk.before = chunk.before.replace(/\n?$/, "\n");
2026
+ }
2027
+ if (chunk.after) {
2028
+ chunk.after = chunk.after.replace(/^\n?/, "\n");
2029
+ }
2030
+
2031
+ chunk.before = chunk.before.replace(/(((\n|^)(\n[ \t]*)*>(.+\n)*.*)+(\n[ \t]*)*$)/, function (totalMatch) {
2032
+ chunk.startTag = totalMatch;
2033
+ return "";
2034
+ });
2035
+
2036
+ chunk.after = chunk.after.replace(/^(((\n|^)(\n[ \t]*)*>(.+\n)*.*)+(\n[ \t]*)*)/, function (totalMatch) {
2037
+ chunk.endTag = totalMatch;
2038
+ return "";
2039
+ });
2040
+
2041
+ var replaceBlanksInTags = function (useBracket) {
2042
+
2043
+ var replacement = useBracket ? "> " : "";
2044
+
2045
+ if (chunk.startTag) {
2046
+ chunk.startTag = chunk.startTag.replace(/\n((>|\s)*)\n$/, function (totalMatch, markdown) {
2047
+ return "\n" + markdown.replace(/^[ ]{0,3}>?[ \t]*$/gm, replacement) + "\n";
2048
+ });
2049
+ }
2050
+ if (chunk.endTag) {
2051
+ chunk.endTag = chunk.endTag.replace(/^\n((>|\s)*)\n/, function (totalMatch, markdown) {
2052
+ return "\n" + markdown.replace(/^[ ]{0,3}>?[ \t]*$/gm, replacement) + "\n";
2053
+ });
2054
+ }
2055
+ };
2056
+
2057
+ if (/^(?![ ]{0,3}>)/m.test(chunk.selection)) {
2058
+ command.wrap(chunk, wmd_options.lineLength - 2);
2059
+ chunk.selection = chunk.selection.replace(/^/gm, "> ");
2060
+ replaceBlanksInTags(true);
2061
+ chunk.addBlankLines();
2062
+ }
2063
+ else {
2064
+ chunk.selection = chunk.selection.replace(/^[ ]{0,3}> ?/gm, "");
2065
+ command.unwrap(chunk);
2066
+ replaceBlanksInTags(false);
2067
+
2068
+ if (!/^(\n|^)[ ]{0,3}>/.test(chunk.selection) && chunk.startTag) {
2069
+ chunk.startTag = chunk.startTag.replace(/\n{0,2}$/, "\n\n");
2070
+ }
2071
+
2072
+ if (!/(\n|^)[ ]{0,3}>.*$/.test(chunk.selection) && chunk.endTag) {
2073
+ chunk.endTag = chunk.endTag.replace(/^\n{0,2}/, "\n\n");
2074
+ }
2075
+ }
2076
+
2077
+ if (!/\n/.test(chunk.selection)) {
2078
+ chunk.selection = chunk.selection.replace(/^(> *)/, function (wholeMatch, blanks) {
2079
+ chunk.startTag += blanks;
2080
+ return "";
2081
+ });
2082
+ }
2083
+ };
2084
+
2085
+ command.doCode = function (chunk, postProcessing, useDefaultText) {
2086
+
2087
+ var hasTextBefore = /\S[ ]*$/.test(chunk.before);
2088
+ var hasTextAfter = /^[ ]*\S/.test(chunk.after);
2089
+
2090
+ // Use 'four space' markdown if the selection is on its own
2091
+ // line or is multiline.
2092
+ if ((!hasTextAfter && !hasTextBefore) || /\n/.test(chunk.selection)) {
2093
+
2094
+ chunk.before = chunk.before.replace(/[ ]{4}$/, function (totalMatch) {
2095
+ chunk.selection = totalMatch + chunk.selection;
2096
+ return "";
2097
+ });
2098
+
2099
+ var nLinesBefore = 1;
2100
+ var nLinesAfter = 1;
2101
+
2102
+
2103
+ if (/\n(\t|[ ]{4,}).*\n$/.test(chunk.before) || chunk.after === "") {
2104
+ nLinesBefore = 0;
2105
+ }
2106
+ if (/^\n(\t|[ ]{4,})/.test(chunk.after)) {
2107
+ nLinesAfter = 0; // This needs to happen on line 1
2108
+ }
2109
+
2110
+ chunk.addBlankLines(nLinesBefore, nLinesAfter);
2111
+
2112
+ if (!chunk.selection) {
2113
+ chunk.startTag = " ";
2114
+ chunk.selection = useDefaultText ? "enter code here" : "";
2115
+ }
2116
+ else {
2117
+ if (/^[ ]{0,3}\S/m.test(chunk.selection)) {
2118
+ chunk.selection = chunk.selection.replace(/^/gm, " ");
2119
+ }
2120
+ else {
2121
+ chunk.selection = chunk.selection.replace(/^[ ]{4}/gm, "");
2122
+ }
2123
+ }
2124
+ }
2125
+ else {
2126
+ // Use backticks (`) to delimit the code block.
2127
+ chunk.trimWhitespace();
2128
+ chunk.findTags(/`/, /`/);
2129
+
2130
+ if (!chunk.startTag && !chunk.endTag) {
2131
+ chunk.startTag = chunk.endTag = "`";
2132
+ if (!chunk.selection) {
2133
+ chunk.selection = useDefaultText ? "enter code here" : "";
2134
+ }
2135
+ }
2136
+ else if (chunk.endTag && !chunk.startTag) {
2137
+ chunk.before += chunk.endTag;
2138
+ chunk.endTag = "";
2139
+ }
2140
+ else {
2141
+ chunk.startTag = chunk.endTag = "";
2142
+ }
2143
+ }
2144
+ };
2145
+
2146
+ command.doList = function (chunk, postProcessing, isNumberedList, useDefaultText) {
2147
+
2148
+ // These are identical except at the very beginning and end.
2149
+ // Should probably use the regex extension function to make this clearer.
2150
+ var previousItemsRegex = /(\n|^)(([ ]{0,3}([*+-]|\d+[.])[ \t]+.*)(\n.+|\n{2,}([*+-].*|\d+[.])[ \t]+.*|\n{2,}[ \t]+\S.*)*)\n*$/;
2151
+ var nextItemsRegex = /^\n*(([ ]{0,3}([*+-]|\d+[.])[ \t]+.*)(\n.+|\n{2,}([*+-].*|\d+[.])[ \t]+.*|\n{2,}[ \t]+\S.*)*)\n*/;
2152
+
2153
+ // The default bullet is a dash but others are possible.
2154
+ // This has nothing to do with the particular HTML bullet,
2155
+ // it's just a markdown bullet.
2156
+ var bullet = "-";
2157
+
2158
+ // The number in a numbered list.
2159
+ var num = 1;
2160
+
2161
+ // Get the item prefix - e.g. " 1. " for a numbered list, " - " for a bulleted list.
2162
+ var getItemPrefix = function () {
2163
+ var prefix;
2164
+ if (isNumberedList) {
2165
+ prefix = " " + num + ". ";
2166
+ num++;
2167
+ }
2168
+ else {
2169
+ prefix = " " + bullet + " ";
2170
+ }
2171
+ return prefix;
2172
+ };
2173
+
2174
+ // Fixes the prefixes of the other list items.
2175
+ var getPrefixedItem = function (itemText) {
2176
+
2177
+ // The numbering flag is unset when called by autoindent.
2178
+ if (isNumberedList === undefined) {
2179
+ isNumberedList = /^\s*\d/.test(itemText);
2180
+ }
2181
+
2182
+ // Renumber/bullet the list element.
2183
+ itemText = itemText.replace(/^[ ]{0,3}([*+-]|\d+[.])\s/gm, function (_) {
2184
+ return getItemPrefix();
2185
+ });
2186
+
2187
+ return itemText;
2188
+ };
2189
+
2190
+ chunk.findTags(/(\n|^)*[ ]{0,3}([*+-]|\d+[.])\s+/, null);
2191
+
2192
+ if (chunk.before && !/\n$/.test(chunk.before) && !/^\n/.test(chunk.startTag)) {
2193
+ chunk.before += chunk.startTag;
2194
+ chunk.startTag = "";
2195
+ }
2196
+
2197
+ if (chunk.startTag) {
2198
+
2199
+ var hasDigits = /\d+[.]/.test(chunk.startTag);
2200
+ chunk.startTag = "";
2201
+ chunk.selection = chunk.selection.replace(/\n[ ]{4}/g, "\n");
2202
+ command.unwrap(chunk);
2203
+ chunk.addBlankLines();
2204
+
2205
+ if (hasDigits) {
2206
+ // Have to renumber the bullet points if this is a numbered list.
2207
+ chunk.after = chunk.after.replace(nextItemsRegex, getPrefixedItem);
2208
+ }
2209
+ if (isNumberedList == hasDigits) {
2210
+ return;
2211
+ }
2212
+ }
2213
+
2214
+ var nLinesBefore = 1;
2215
+
2216
+ chunk.before = chunk.before.replace(previousItemsRegex, function (itemText) {
2217
+ if (/^\s*([*+-])/.test(itemText)) {
2218
+ bullet = re.$1;
2219
+ }
2220
+ nLinesBefore = /[^\n]\n\n[^\n]/.test(itemText) ? 1 : 0;
2221
+ return getPrefixedItem(itemText);
2222
+ });
2223
+
2224
+ if (!chunk.selection) {
2225
+ chunk.selection = useDefaultText ? "List item" : " ";
2226
+ }
2227
+
2228
+ var prefix = getItemPrefix();
2229
+
2230
+ var nLinesAfter = 1;
2231
+
2232
+ chunk.after = chunk.after.replace(nextItemsRegex, function (itemText) {
2233
+ nLinesAfter = /[^\n]\n\n[^\n]/.test(itemText) ? 1 : 0;
2234
+ return getPrefixedItem(itemText);
2235
+ });
2236
+
2237
+ chunk.trimWhitespace(true);
2238
+ chunk.addBlankLines(nLinesBefore, nLinesAfter, true);
2239
+ chunk.startTag = prefix;
2240
+ var spaces = prefix.replace(/./g, " ");
2241
+ command.wrap(chunk, wmd_options.lineLength - spaces.length);
2242
+ chunk.selection = chunk.selection.replace(/\n/g, "\n" + spaces);
2243
+
2244
+ };
2245
+
2246
+ command.doHeading = function (chunk, postProcessing, useDefaultText) {
2247
+
2248
+ // Remove leading/trailing whitespace and reduce internal spaces to single spaces.
2249
+ chunk.selection = chunk.selection.replace(/\s+/g, " ");
2250
+ chunk.selection = chunk.selection.replace(/(^\s+|\s+$)/g, "");
2251
+
2252
+ // If we clicked the button with no selected text, we just
2253
+ // make a level 2 hash header around some default text.
2254
+ if (!chunk.selection) {
2255
+ chunk.startTag = "## ";
2256
+ chunk.selection = "Heading";
2257
+ chunk.endTag = " ##";
2258
+ return;
2259
+ }
2260
+
2261
+ var headerLevel = 0; // The existing header level of the selected text.
2262
+ // Remove any existing hash heading markdown and save the header level.
2263
+ chunk.findTags(/#+[ ]*/, /[ ]*#+/);
2264
+ if (/#+/.test(chunk.startTag)) {
2265
+ headerLevel = re.lastMatch.length;
2266
+ }
2267
+ chunk.startTag = chunk.endTag = "";
2268
+
2269
+ // Try to get the current header level by looking for - and = in the line
2270
+ // below the selection.
2271
+ chunk.findTags(null, /\s?(-+|=+)/);
2272
+ if (/=+/.test(chunk.endTag)) {
2273
+ headerLevel = 1;
2274
+ }
2275
+ if (/-+/.test(chunk.endTag)) {
2276
+ headerLevel = 2;
2277
+ }
2278
+
2279
+ // Skip to the next line so we can create the header markdown.
2280
+ chunk.startTag = chunk.endTag = "";
2281
+ chunk.addBlankLines(1, 1);
2282
+
2283
+ // We make a level 2 header if there is no current header.
2284
+ // If there is a header level, we substract one from the header level.
2285
+ // If it's already a level 1 header, it's removed.
2286
+ var headerLevelToCreate = headerLevel == 0 ? 2 : headerLevel - 1;
2287
+
2288
+ if (headerLevelToCreate > 0) {
2289
+
2290
+ // The button only creates level 1 and 2 underline headers.
2291
+ // Why not have it iterate over hash header levels? Wouldn't that be easier and cleaner?
2292
+ var headerChar = headerLevelToCreate >= 2 ? "-" : "=";
2293
+ var len = chunk.selection.length;
2294
+ if (len > wmd_options.lineLength) {
2295
+ len = wmd_options.lineLength;
2296
+ }
2297
+ chunk.endTag = "\n";
2298
+ while (len--) {
2299
+ chunk.endTag += headerChar;
2300
+ }
2301
+ }
2302
+ };
2303
+
2304
+ command.doHorizontalRule = function (chunk, postProcessing, useDefaultText) {
2305
+ chunk.startTag = "----------\n";
2306
+ chunk.selection = "";
2307
+ chunk.addBlankLines(2, 1, true);
2308
+ };
2309
+ // }}}
2310
+ }; // }}}
2311
+ })();
2312
+
2313
+ // For backward compatibility
2314
+
2315
+ function setup_wmd(options) {
2316
+ return new WMDEditor(options);
2317
+ }