bhf 0.3.3 → 0.3.4

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