Administrator
2022-09-14 58d006e05dcf2a20d0ec5367dd03d66a61db6849
提交 | 用户 | 时间
58d006 1 /* ===================================================
A 2  * bootstrap-markdown.js v2.1.0
3  * http://github.com/toopay/bootstrap-markdown
4  * ===================================================
5  * Copyright 2013 Taufan Aditya
6  *
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  * http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  * ========================================================== */
19
20 !function ($) {
21
22   "use strict"; // jshint ;_;
23
24
25   /* MARKDOWN CLASS DEFINITION
26    * ========================== */
27
28   var Markdown = function (element, options) {
29     // Class Properties
30     this.$ns          = 'bootstrap-markdown'
31     this.$element     = $(element)
32     this.$editable    = {el:null, type:null,attrKeys:[], attrValues:[], content:null}
33     this.$options     = $.extend(true, {}, $.fn.markdown.defaults, options)
34     this.$oldContent  = null
35     this.$isPreview   = false
36     this.$editor      = null
37     this.$textarea    = null
38     this.$handler     = []
39     this.$callback    = []
40     this.$nextTab     = []
41
42     this.showEditor()
43   }
44
45   Markdown.prototype = {
46
47     constructor: Markdown
48
49   , __alterButtons: function(name,alter) {
50       var handler = this.$handler, isAll = (name == 'all'),that = this
51
52       $.each(handler,function(k,v) {
53         var halt = true
54         if (isAll) {
55           halt = false
56         } else {
57           halt = v.indexOf(name) < 0
58         }
59
60         if (halt == false) {
61           alter(that.$editor.find('button[data-handler="'+v+'"]'))
62         }
63       })
64     }
65     
66   , __buildButtons: function(buttonsArray, container) {
67       var i,
68           ns = this.$ns,
69           handler = this.$handler,
70           callback = this.$callback
71
72       for (i=0;i<buttonsArray.length;i++) {
73         // Build each group container
74         var y, btnGroups = buttonsArray[i]
75         for (y=0;y<btnGroups.length;y++) {
76           // Build each button group
77           var z,
78               buttons = btnGroups[y].data,
79               btnGroupContainer = $('<div/>', {
80                                     'class': 'btn-group'
81                                   })
82
83           for (z=0;z<buttons.length;z++) {
84             var button = buttons[z],
85                 buttonToggle = '',
86                 buttonHandler = ns+'-'+button.name,
87                 btnText = button.btnText ? button.btnText : '',
88                 btnClass = button.btnClass ? button.btnClass : 'btn',
89                 tabIndex = button.tabIndex ? button.tabIndex : '-1'
90
91             if (button.toggle == true) {
92               buttonToggle = ' data-toggle="button"'
93             }
94
95             // Attach the button object
96             btnGroupContainer.append('<button class="'
97                                     +btnClass
98                                     +' btn-default btn-sm" title="'
99                                     +button.title
100                                     +'" tabindex="'
101                                     +tabIndex
102                                     +'" data-provider="'
103                                     +ns
104                                     +'" data-handler="'
105                                     +buttonHandler
106                                     +'"'
107                                     +buttonToggle
108                                     +'><span class="'
109                                     +button.icon
110                                     +'"></span> '
111                                     +btnText
112                                     +'</button>')
113
114             // Register handler and callback
115             handler.push(buttonHandler)
116             callback.push(button.callback)
117           }
118
119           // Attach the button group into container dom
120           container.append(btnGroupContainer)
121         }
122       }
123
124       return container
125     }
126   , __setListener: function() {
127       // Set size and resizable Properties
128       var hasRows = typeof this.$textarea.attr('rows') != 'undefined',
129           maxRows = this.$textarea.val().split("\n").length > 5 ? this.$textarea.val().split("\n").length : '5',
130           rowsVal = hasRows ? this.$textarea.attr('rows') : maxRows
131
132       this.$textarea.attr('rows',rowsVal)
133       this.$textarea.css('resize','none')
134
135       this.$textarea
136         .on('focus',    $.proxy(this.focus, this))
137         .on('keypress', $.proxy(this.keypress, this))
138         .on('keyup',    $.proxy(this.keyup, this))
139
140       if (this.eventSupported('keydown')) {
141         this.$textarea.on('keydown', $.proxy(this.keydown, this))
142       }
143
144       // Re-attach markdown data
145       this.$textarea.data('markdown',this)
146     }
147
148   , __handle: function(e) {
149       var target = $(e.currentTarget),
150           handler = this.$handler,
151           callback = this.$callback,
152           handlerName = target.attr('data-handler'),
153           callbackIndex = handler.indexOf(handlerName),
154           callbackHandler = callback[callbackIndex]
155
156       // Trigger the focusin
157       $(e.currentTarget).focus()
158
159       callbackHandler(this)
160
161       // Unless it was the save handler,
162       // focusin the textarea
163       if (handlerName.indexOf('cmdSave') < 0) {
164         this.$textarea.focus()
165       }
166
167       e.preventDefault()
168     }
169
170   , showEditor: function() {
171       var instance = this,
172           textarea, 
173           ns = this.$ns,
174           container = this.$element,
175           originalHeigth = container.css('height'), 
176           originalWidth = container.css('width'),
177           editable = this.$editable,
178           handler = this.$handler,
179           callback = this.$callback,
180           options = this.$options,
181           editor = $( '<div/>', {
182                       'class': 'md-editor',
183                       click: function() {
184                         instance.focus()
185                       }
186                     })
187
188       // Prepare the editor
189       if (this.$editor == null) {
190         // Create the panel
191         var editorHeader = $('<div/>', {
192                             'class': 'md-header btn-toolbar'
193                             })
194
195         // Build the main buttons
196         if (options.buttons.length > 0) {
197           editorHeader = this.__buildButtons(options.buttons, editorHeader)
198         }
199
200         // Build the additional buttons
201         if (options.additionalButtons.length > 0) {
202           editorHeader = this.__buildButtons(options.additionalButtons, editorHeader)
203         }
204
205         editor.append(editorHeader)
206
207         // Wrap the textarea
208         if (container.is('textarea')) {
209           container.before(editor)
210           textarea = container
211           textarea.addClass('md-input')
212           editor.append(textarea)
213         } else {
214           var rawContent = (typeof toMarkdown == 'function') ? toMarkdown(container.html()) : container.html(),
215               currentContent = $.trim(rawContent)
216
217           // This is some arbitrary content that could be edited
218           textarea = $('<textarea/>', {
219                        'class': 'md-input',
220                        'val' : currentContent
221                       })
222
223           editor.append(textarea)
224
225           // Save the editable
226           editable.el = container
227           editable.type = container.prop('tagName').toLowerCase()
228           editable.content = container.html()
229
230           $(container[0].attributes).each(function(){
231             editable.attrKeys.push(this.nodeName)
232             editable.attrValues.push(this.nodeValue)
233           })
234
235           // Set editor to blocked the original container
236           container.replaceWith(editor)
237         }
238
239         // Create the footer if savable
240         if (options.savable) {
241           var editorFooter = $('<div/>', {
242                            'class': 'md-footer'
243                          }),
244               saveHandler = 'cmdSave'
245
246           // Register handler and callback
247           handler.push(saveHandler)
248           callback.push(options.onSave)
249
250           editorFooter.append('<button class="btn btn-success" data-provider="'
251                               +ns
252                               +'" data-handler="'
253                               +saveHandler
254                               +'"><i class="icon icon-white icon-ok"></i> Save</button>')
255
256           editor.append(editorFooter)
257         }
258
259         // Set width/height
260         $.each(['height','width'],function(k,attr){
261           if (options[attr] != 'inherit') {
262             if (jQuery.isNumeric(options[attr])) {
263               editor.css(attr,options[attr]+'px')
264             } else {
265               editor.addClass(options[attr])
266             }
267           }
268         })
269
270         // Reference
271         this.$editor     = editor
272         this.$textarea   = textarea
273         this.$editable   = editable
274         this.$oldContent = this.getContent()
275
276         this.__setListener()
277
278         // Set editor attributes, data short-hand API and listener
279         this.$editor.attr('id',(new Date).getTime())
280         this.$editor.on('click', '[data-provider="bootstrap-markdown"]', $.proxy(this.__handle, this))
281
282       } else {
283         this.$editor.show()
284       }
285
286       if (options.autofocus) {
287         this.$textarea.focus()
288         this.$editor.addClass('active')
289       }
290
291       // Trigger the onShow hook
292       options.onShow(this)
293
294       return this
295     }
296
297   , showPreview: function() {
298       var options = this.$options,
299           callbackContent = options.onPreview(this), // Try to get the content from callback
300           container = this.$textarea,
301           afterContainer = container.next(),
302           replacementContainer = $('<div/>',{'class':'md-preview','data-provider':'markdown-preview'}),
303           content
304
305       // Give flag that tell the editor enter preview mode
306       this.$isPreview = true
307       // Disable all buttons
308       this.disableButtons('all').enableButtons('cmdPreview')
309
310       if (typeof callbackContent == 'string') {
311         // Set the content based by callback content
312         content = callbackContent
313       } else {
314         // Set the content
315         content = (typeof markdown == 'object') ? markdown.toHTML(container.val()) : container.val()
316       }
317
318       // Build preview element
319       replacementContainer.html(content)
320
321       if (afterContainer && afterContainer.attr('class') == 'md-footer') {
322         // If there is footer element, insert the preview container before it
323         replacementContainer.insertBefore(afterContainer)
324       } else {
325         // Otherwise, just append it after textarea
326         container.parent().append(replacementContainer)
327       }
328
329       // Hide the last-active textarea
330       container.hide()
331
332       // Attach the editor instances
333       replacementContainer.data('markdown',this)
334
335       return this
336     }
337
338   , hidePreview: function() {
339       // Give flag that tell the editor quit preview mode
340       this.$isPreview = false
341
342       // Obtain the preview container
343       var container = this.$editor.find('div[data-provider="markdown-preview"]')
344
345       // Remove the preview container
346       container.remove()
347
348       // Enable all buttons
349       this.enableButtons('all')
350
351       // Back to the editor
352       this.$textarea.show()
353       this.__setListener()
354
355       return this
356     }
357
358   , isDirty: function() {
359       return this.$oldContent != this.getContent()
360     }
361
362   , getContent: function() {
363       return this.$textarea.val()
364     }
365
366   , setContent: function(content) {
367       this.$textarea.val(content)
368
369       return this
370     }
371
372   , findSelection: function(chunk) {
373     var content = this.getContent(), startChunkPosition
374
375     if (startChunkPosition = content.indexOf(chunk), startChunkPosition >= 0 && chunk.length > 0) {
376       var oldSelection = this.getSelection(), selection
377
378       this.setSelection(startChunkPosition,startChunkPosition+chunk.length)
379       selection = this.getSelection()
380
381       this.setSelection(oldSelection.start,oldSelection.end)
382
383       return selection
384     } else {
385       return null
386     }
387   }
388
389   , getSelection: function() {
390
391       var e = this.$textarea[0]
392
393       return (
394
395           ('selectionStart' in e && function() {
396               var l = e.selectionEnd - e.selectionStart
397               return { start: e.selectionStart, end: e.selectionEnd, length: l, text: e.value.substr(e.selectionStart, l) }
398           }) ||
399
400           /* browser not supported */
401           function() { 
402             return null
403           }
404
405       )()
406
407     }
408
409   , setSelection: function(start,end) {
410
411       var e = this.$textarea[0]
412
413       return (
414
415           ('selectionStart' in e && function() {
416               e.selectionStart = start
417               e.selectionEnd = end
418               return 
419           }) ||
420
421           /* browser not supported */
422           function() { 
423             return null
424           }
425
426       )()
427
428     }
429
430   , replaceSelection: function(text) {
431
432       var e = this.$textarea[0]
433
434       return (
435
436           ('selectionStart' in e && function() {
437               e.value = e.value.substr(0, e.selectionStart) + text + e.value.substr(e.selectionEnd, e.value.length)
438               // Set cursor to the last replacement end
439               e.selectionStart = e.value.length
440               return this
441           }) ||
442
443           /* browser not supported */
444           function() {
445               e.value += text
446               return jQuery(e)
447           }
448
449       )()
450
451     }
452
453   , getNextTab: function() {
454       // Shift the nextTab
455       if (this.$nextTab.length == 0) {
456         return null
457       } else {
458         var nextTab, tab = this.$nextTab.shift()
459
460         if (typeof tab == 'function') {
461           nextTab = tab()
462         } else if (typeof tab == 'object' && tab.length > 0) {
463           nextTab = tab
464         }
465
466         return nextTab
467       } 
468     }
469
470   , setNextTab: function(start,end) {
471       // Push new selection into nextTab collections
472       if (typeof start == 'string') {
473         var that = this
474         this.$nextTab.push(function(){
475           return that.findSelection(start)
476         })
477       } else if (typeof start == 'numeric' && typeof end == 'numeric') {
478         var oldSelection = this.getSelection()
479
480         this.setSelection(start,end)
481         this.$nextTab.push(this.getSelection())
482
483         this.setSelection(oldSelection.start,oldSelection.end)
484       }
485
486       return
487     }
488
489   , enableButtons: function(name) {
490       var alter = function (el) {
491         el.removeAttr('disabled')
492       }
493
494       this.__alterButtons(name,alter)
495
496       return this
497     }
498
499   , disableButtons: function(name) {
500       var alter = function (el) {
501         el.attr('disabled','disabled')
502       }
503
504       this.__alterButtons(name,alter)
505
506       return this
507     }
508
509   , eventSupported: function(eventName) {
510       var isSupported = eventName in this.$element
511       if (!isSupported) {
512         this.$element.setAttribute(eventName, 'return;')
513         isSupported = typeof this.$element[eventName] === 'function'
514       }
515       return isSupported
516     }
517
518   , keydown: function (e) {
519       this.suppressKeyPressRepeat = ~$.inArray(e.keyCode, [40,38,9,13,27])
520       this.keyup(e)
521     }
522
523   , keypress: function (e) {
524       if (this.suppressKeyPressRepeat) return
525       this.keyup(e)
526     }
527
528   , keyup: function (e) {
529       var blocked = false
530       switch(e.keyCode) {
531         case 40: // down arrow
532         case 38: // up arrow
533         case 16: // shift
534         case 17: // ctrl
535         case 18: // alt
536           break
537
538         case 9: // tab
539           var nextTab
540           if (nextTab = this.getNextTab(),nextTab != null) {
541             // Get the nextTab if exists
542             var that = this
543             setTimeout(function(){
544               that.setSelection(nextTab.start,nextTab.end)
545             },500)
546
547             blocked = true
548           } else {
549             // The next tab memory contains nothing...
550             // check the cursor position to determine tab action
551             var cursor = this.getSelection() 
552
553             if (cursor.start == cursor.end && cursor.end == this.getContent().length) {
554               // The cursor already reach the end of the content
555               blocked = false
556
557             } else {
558               // Put the cursor to the end
559               this.setSelection(this.getContent().length,this.getContent().length)
560               
561               blocked = true
562             }
563           }
564
565           break
566
567         case 13: // enter
568         case 27: // escape
569           blocked = false
570           break
571
572         default:
573           blocked = false
574       }
575
576       if (blocked) {
577         e.stopPropagation()
578         e.preventDefault()
579       }
580   }
581
582   , focus: function (e) {
583       var options = this.$options,
584           isHideable = options.hideable,
585           editor = this.$editor
586
587       editor.addClass('active')
588
589       // Blur other markdown(s)
590       $(document).find('.md-editor').each(function(){
591         if ($(this).attr('id') != editor.attr('id')) {
592           var attachedMarkdown
593
594           if (attachedMarkdown = $(this).find('textarea').data('markdown'),
595               attachedMarkdown == null) {
596               attachedMarkdown = $(this).find('div[data-provider="markdown-preview"]').data('markdown')
597           }
598
599           if (attachedMarkdown) {
600             attachedMarkdown.blur()
601           }
602         }
603       })
604
605       return this
606     }
607
608   , blur: function (e) {
609       var options = this.$options,
610           isHideable = options.hideable,
611           editor = this.$editor,
612           editable = this.$editable
613
614       if (editor.hasClass('active') || this.$element.parent().length == 0) {
615         editor.removeClass('active')
616         
617         if (isHideable) {
618         
619           // Check for editable elements
620           if (editable.el != null) {
621             // Build the original element
622             var oldElement = $('<'+editable.type+'/>'),
623                 content = this.getContent(),
624                 currentContent = (typeof markdown == 'object') ? markdown.toHTML(content) : content 
625
626             $(editable.attrKeys).each(function(k,v) {
627               oldElement.attr(editable.attrKeys[k],editable.attrValues[k])
628             })
629
630             // Get the editor content
631             oldElement.html(currentContent)
632
633             editor.replaceWith(oldElement)
634           } else {
635             editor.hide()
636             
637           }
638         }
639
640         // Trigger the onBlur hook
641         options.onBlur(this)
642       }
643
644       return this
645     }
646
647   }
648
649  /* MARKDOWN PLUGIN DEFINITION
650   * ========================== */
651
652   var old = $.fn.markdown
653
654   $.fn.markdown = function (option) {
655     return this.each(function () {
656       var $this = $(this)
657         , data = $this.data('markdown')
658         , options = typeof option == 'object' && option
659       if (!data) $this.data('markdown', (data = new Markdown(this, options)))
660     })
661   }
662
663   $.fn.markdown.defaults = {
664     /* Editor Properties */
665     autofocus: false,
666     hideable: false,
667     savable:false,
668     width: 'inherit',
669     height: 'inherit',
670
671     /* Buttons Properties */
672     buttons: [
673       [{
674         name: 'groupFont',
675         data: [{
676           name: 'cmdBold',
677           title: 'Bold',
678           icon: 'glyphicon glyphicon-bold',
679           callback: function(e){
680             // Give/remove ** surround the selection
681             var chunk, cursor, selected = e.getSelection(), content = e.getContent()
682
683             if (selected.length == 0) {
684               // Give extra word
685               chunk = 'strong text'
686             } else {
687               chunk = selected.text
688             }
689
690             // transform selection and set the cursor into chunked text
691             if (content.substr(selected.start-2,2) == '**' 
692                 && content.substr(selected.end,2) == '**' ) {
693               e.setSelection(selected.start-2,selected.end+2)
694               e.replaceSelection(chunk)
695               cursor = selected.start-2
696             } else {
697               e.replaceSelection('**'+chunk+'**')
698               cursor = selected.start+2
699             }
700
701             // Set the cursor
702             e.setSelection(cursor,cursor+chunk.length)
703           }
704         },{
705           name: 'cmdItalic',
706           title: 'Italic',
707           icon: 'glyphicon glyphicon-italic',
708           callback: function(e){
709             // Give/remove * surround the selection
710             var chunk, cursor, selected = e.getSelection(), content = e.getContent()
711
712             if (selected.length == 0) {
713               // Give extra word
714               chunk = 'emphasized text'
715             } else {
716               chunk = selected.text
717             }
718
719             // transform selection and set the cursor into chunked text
720             if (content.substr(selected.start-1,1) == '*' 
721                 && content.substr(selected.end,1) == '*' ) {
722               e.setSelection(selected.start-1,selected.end+1)
723               e.replaceSelection(chunk)
724               cursor = selected.start-1
725             } else {
726               e.replaceSelection('*'+chunk+'*')
727               cursor = selected.start+1
728             }
729
730             // Set the cursor
731             e.setSelection(cursor,cursor+chunk.length)
732           }
733         },{
734           name: 'cmdHeading',
735           title: 'Heading',
736           icon: 'glyphicon glyphicon-font',
737           callback: function(e){
738             // Append/remove ### surround the selection
739             var chunk, cursor, selected = e.getSelection(), content = e.getContent(), pointer, prevChar
740
741             if (selected.length == 0) {
742               // Give extra word
743               chunk = 'heading text'
744             } else {
745               chunk = selected.text
746             }
747
748             // transform selection and set the cursor into chunked text
749             if ((pointer = 4, content.substr(selected.start-pointer,pointer) == '### ') 
750                 || (pointer = 3, content.substr(selected.start-pointer,pointer) == '###')) {
751               e.setSelection(selected.start-pointer,selected.end)
752               e.replaceSelection(chunk)
753               cursor = selected.start-pointer
754             } else if (prevChar = content.substr(selected.start-1,1), !!prevChar && prevChar != '\n') {
755               e.replaceSelection('\n\n### '+chunk+'\n')
756               cursor = selected.start+6
757             } else {
758               // Empty string before element
759               e.replaceSelection('### '+chunk+'\n')
760               cursor = selected.start+4
761             }
762
763             // Set the cursor
764             e.setSelection(cursor,cursor+chunk.length)
765           }
766         }]
767       },{
768         name: 'groupLink',
769         data: [{
770           name: 'cmdUrl',
771           title: 'URL/Link',
772           icon: 'glyphicon glyphicon-globe',
773           callback: function(e){
774             // Give [] surround the selection and prepend the link
775             var chunk, cursor, selected = e.getSelection(), content = e.getContent(), link
776
777             if (selected.length == 0) {
778               // Give extra word
779               chunk = 'enter link description here'
780             } else {
781               chunk = selected.text
782             }
783
784             link = prompt('Insert Hyperlink','http://')
785
786             if (link != null) {
787               // transform selection and set the cursor into chunked text
788               e.replaceSelection('['+chunk+']('+link+')')
789               cursor = selected.start+1
790
791               // Set the cursor
792               e.setSelection(cursor,cursor+chunk.length)
793             }
794           }
795         },{
796           name: 'cmdImage',
797           title: 'Image',
798           icon: 'glyphicon glyphicon-picture',
799           callback: function(e){
800             // Give ![] surround the selection and prepend the image link
801             var chunk, cursor, selected = e.getSelection(), content = e.getContent(), link
802
803             if (selected.length == 0) {
804               // Give extra word
805               chunk = 'enter image description here'
806             } else {
807               chunk = selected.text
808             }
809
810             link = prompt('Insert Image Hyperlink','http://')
811
812             if (link != null) {
813               // transform selection and set the cursor into chunked text
814               e.replaceSelection('!['+chunk+']('+link+' "enter image title here")')
815               cursor = selected.start+2
816
817               // Set the next tab
818               e.setNextTab('enter image title here')
819
820               // Set the cursor
821               e.setSelection(cursor,cursor+chunk.length)
822             }
823           }
824         }]
825       },{
826         name: 'groupMisc',
827         data: [{
828           name: 'cmdList',
829           title: 'List',
830           icon: 'glyphicon glyphicon-list',
831           callback: function(e){
832             // Prepend/Give - surround the selection
833             var chunk, cursor, selected = e.getSelection(), content = e.getContent()
834
835             // transform selection and set the cursor into chunked text
836             if (selected.length == 0) {
837               // Give extra word
838               chunk = 'list text here'
839                 
840               e.replaceSelection('- '+chunk)
841
842               // Set the cursor
843               cursor = selected.start+2
844             } else {
845               if (selected.text.indexOf('\n') < 0) {
846                 chunk = selected.text
847
848                 e.replaceSelection('- '+chunk)
849
850                 // Set the cursor
851                 cursor = selected.start+2
852               } else {
853                 var list = []
854
855                 list = selected.text.split('\n')
856                 chunk = list[0]
857
858                 $.each(list,function(k,v) {
859                   list[k] = '- '+v
860                 })
861
862                 e.replaceSelection('\n\n'+list.join('\n'))
863
864                 // Set the cursor
865                 cursor = selected.start+4
866               }
867             }
868
869            
870
871             // Set the cursor
872             e.setSelection(cursor,cursor+chunk.length)
873           }
874         }]
875       },{
876         name: 'groupUtil',
877         data: [{
878           name: 'cmdPreview',
879           toggle: true,
880           title: 'Preview',
881           btnText: 'Preview',
882           btnClass: 'btn btn-primary btn-sm',
883           icon: 'glyphicon glyphicon-search',
884           callback: function(e){
885             // Check the preview mode and toggle based on this flag
886             var isPreview = e.$isPreview,content
887
888             if (isPreview == false) {
889               // Give flag that tell the editor enter preview mode
890               e.showPreview()
891             } else {
892               e.hidePreview()
893             }
894           }
895         }]
896       }]
897     ],
898     additionalButtons:[], // Place to hook more buttons by code
899
900     /* Events hook */
901     onShow: function (e) {},
902     onPreview: function (e) {},
903     onSave: function (e) {},
904     onBlur: function (e) {}
905   }
906
907   $.fn.markdown.Constructor = Markdown
908
909
910  /* MARKDOWN NO CONFLICT
911   * ==================== */
912
913   $.fn.markdown.noConflict = function () {
914     $.fn.markdown = old
915     return this
916   }
917
918   /* MARKDOWN GLOBAL FUNCTION & DATA-API
919   * ==================================== */
920   var initMarkdown = function(el) {
921     var $this = el
922
923     if ($this.data('markdown')) {
924       $this.data('markdown').showEditor()
925       return
926     }
927     $this.markdown($this.data())
928   }
929
930   var analyzeMarkdown = function(e) {
931     var blurred = false,
932         el,
933         $docEditor = $(e.currentTarget)
934
935     // Check whether it was editor childs or not
936     if ((e.type == 'focusin' || e.type == 'click') && $docEditor.length == 1 && typeof $docEditor[0] == 'object'){
937       el = $docEditor[0].activeElement
938       if ( ! $(el).data('markdown')) {
939         if (typeof $(el).parent().parent().parent().attr('class') == "undefined"
940               || $(el).parent().parent().parent().attr('class').indexOf('md-editor') < 0) {
941           if ( typeof $(el).parent().parent().attr('class') == "undefined"
942               || $(el).parent().parent().attr('class').indexOf('md-editor') < 0) {
943           
944                 blurred = true
945           }
946         } else {
947           blurred = false
948         }
949       }
950
951
952       if (blurred) {
953         // Blur event
954         $(document).find('.md-editor').each(function(){
955           var parentMd = $(el).parent()
956
957           if ($(this).attr('id') != parentMd.attr('id')) {
958             var attachedMarkdown
959
960             if (attachedMarkdown = $(this).find('textarea').data('markdown'),
961                 attachedMarkdown == null) {
962                 attachedMarkdown = $(this).find('div[data-provider="markdown-preview"]').data('markdown')
963             }
964
965             if (attachedMarkdown) {
966               attachedMarkdown.blur()
967             }
968           }
969         })
970       }
971
972       e.stopPropagation()
973     }
974   }
975
976   $(document)
977     .on('click.markdown.data-api', '[data-provide="markdown-editable"]', function (e) {
978       initMarkdown($(this))
979       e.preventDefault()
980     })
981     .on('click', function (e) {
982       analyzeMarkdown(e)
983     })
984     .on('focusin', function (e) {
985       analyzeMarkdown(e)
986     })
987     .ready(function(){
988       $('textarea[data-provide="markdown"]').each(function(){
989         initMarkdown($(this))
990       })
991     })
992
993 }(window.jQuery);