bootstrap_markdown_rails 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 5ee0ee461d6f2f895ec3193e44491e9c270b8b9c
4
+ data.tar.gz: 87d8526218e4756df505685dbc7a8bab5dc2d560
5
+ SHA512:
6
+ metadata.gz: cd735d79320fca6eca1d559944a3a05fec672eaa7bf25426c5e5eb20ecfd50922053684e5cb3314a643c5121e7a5987cb2c343069f4d4800ce8ccebb7b8e6bbe
7
+ data.tar.gz: 8f473af618fd370c9c05b6d731e6ed6ae7c7a0b9ae4292922b74e5af0ccdcf8fd2fbd85a12cd4ec26cdf6f02f44f725ff24e2432d195593154d05d5796d92514
data/.gitignore ADDED
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in bootstrap_markdown_rails.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Phuong 'J' Le H.
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,31 @@
1
+ # BootstrapMarkdownRails
2
+
3
+ TODO: Write a gem description
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'bootstrap_markdown_rails'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install bootstrap_markdown_rails
20
+
21
+ ## Usage
22
+
23
+ TODO: Write usage instructions here
24
+
25
+ ## Contributing
26
+
27
+ 1. Fork it ( https://github.com/yeuem1vannam/bootstrap_markdown_rails/fork )
28
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
29
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
30
+ 4. Push to the branch (`git push origin my-new-feature`)
31
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
@@ -0,0 +1,1336 @@
1
+ /* ===================================================
2
+ * bootstrap-markdown.js v2.7.0
3
+ * http://github.com/toopay/bootstrap-markdown
4
+ * ===================================================
5
+ * Copyright 2013-2014 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, this.$element.data(), this.$element.data('options'))
34
+ this.$oldContent = null
35
+ this.$isPreview = false
36
+ this.$isFullscreen = false
37
+ this.$editor = null
38
+ this.$textarea = null
39
+ this.$handler = []
40
+ this.$callback = []
41
+ this.$nextTab = []
42
+
43
+ this.showEditor()
44
+ }
45
+
46
+ Markdown.prototype = {
47
+
48
+ constructor: Markdown
49
+
50
+ , __alterButtons: function(name,alter) {
51
+ var handler = this.$handler, isAll = (name == 'all'),that = this
52
+
53
+ $.each(handler,function(k,v) {
54
+ var halt = true
55
+ if (isAll) {
56
+ halt = false
57
+ } else {
58
+ halt = v.indexOf(name) < 0
59
+ }
60
+
61
+ if (halt == false) {
62
+ alter(that.$editor.find('button[data-handler="'+v+'"]'))
63
+ }
64
+ })
65
+ }
66
+
67
+ , __buildButtons: function(buttonsArray, container) {
68
+ var i,
69
+ ns = this.$ns,
70
+ handler = this.$handler,
71
+ callback = this.$callback
72
+
73
+ for (i=0;i<buttonsArray.length;i++) {
74
+ // Build each group container
75
+ var y, btnGroups = buttonsArray[i]
76
+ for (y=0;y<btnGroups.length;y++) {
77
+ // Build each button group
78
+ var z,
79
+ buttons = btnGroups[y].data,
80
+ btnGroupContainer = $('<div/>', {
81
+ 'class': 'btn-group'
82
+ })
83
+
84
+ for (z=0;z<buttons.length;z++) {
85
+ var button = buttons[z],
86
+ buttonContainer, buttonIconContainer,
87
+ buttonHandler = ns+'-'+button.name,
88
+ buttonIcon = this.__getIcon(button.icon),
89
+ btnText = button.btnText ? button.btnText : '',
90
+ btnClass = button.btnClass ? button.btnClass : 'btn',
91
+ tabIndex = button.tabIndex ? button.tabIndex : '-1',
92
+ hotkey = typeof button.hotkey !== 'undefined' ? button.hotkey : '',
93
+ hotkeyCaption = typeof jQuery.hotkeys !== 'undefined' && hotkey !== '' ? ' ('+hotkey+')' : ''
94
+
95
+ // Construct the button object
96
+ buttonContainer = $('<button></button>');
97
+ buttonContainer.text(' ' + this.__localize(btnText)).addClass('btn-default btn-sm').addClass(btnClass);
98
+ if(btnClass.match(/btn\-(primary|success|info|warning|danger|link)/)){
99
+ buttonContainer.removeClass('btn-default');
100
+ }
101
+ buttonContainer.attr({
102
+ 'type': 'button',
103
+ 'title': this.__localize(button.title) + hotkeyCaption,
104
+ 'tabindex': tabIndex,
105
+ 'data-provider': ns,
106
+ 'data-handler': buttonHandler,
107
+ 'data-hotkey': hotkey
108
+ });
109
+ if (button.toggle == true){
110
+ buttonContainer.attr('data-toggle', 'button');
111
+ }
112
+ buttonIconContainer = $('<span/>');
113
+ buttonIconContainer.addClass(buttonIcon);
114
+ buttonIconContainer.prependTo(buttonContainer);
115
+
116
+ // Attach the button object
117
+ btnGroupContainer.append(buttonContainer);
118
+
119
+ // Register handler and callback
120
+ handler.push(buttonHandler);
121
+ callback.push(button.callback);
122
+ }
123
+
124
+ // Attach the button group into container dom
125
+ container.append(btnGroupContainer);
126
+ }
127
+ }
128
+
129
+ return container;
130
+ }
131
+ , __setListener: function() {
132
+ // Set size and resizable Properties
133
+ var hasRows = typeof this.$textarea.attr('rows') != 'undefined',
134
+ maxRows = this.$textarea.val().split("\n").length > 5 ? this.$textarea.val().split("\n").length : '5',
135
+ rowsVal = hasRows ? this.$textarea.attr('rows') : maxRows
136
+
137
+ this.$textarea.attr('rows',rowsVal)
138
+ if (this.$options.resize) {
139
+ this.$textarea.css('resize',this.$options.resize)
140
+ }
141
+
142
+ this.$textarea
143
+ .on('focus', $.proxy(this.focus, this))
144
+ .on('keypress', $.proxy(this.keypress, this))
145
+ .on('keyup', $.proxy(this.keyup, this))
146
+ .on('change', $.proxy(this.change, this))
147
+
148
+ if (this.eventSupported('keydown')) {
149
+ this.$textarea.on('keydown', $.proxy(this.keydown, this))
150
+ }
151
+
152
+ // Re-attach markdown data
153
+ this.$textarea.data('markdown',this)
154
+ }
155
+
156
+ , __handle: function(e) {
157
+ var target = $(e.currentTarget),
158
+ handler = this.$handler,
159
+ callback = this.$callback,
160
+ handlerName = target.attr('data-handler'),
161
+ callbackIndex = handler.indexOf(handlerName),
162
+ callbackHandler = callback[callbackIndex]
163
+
164
+ // Trigger the focusin
165
+ $(e.currentTarget).focus()
166
+
167
+ callbackHandler(this)
168
+
169
+ // Trigger onChange for each button handle
170
+ this.change(this);
171
+
172
+ // Unless it was the save handler,
173
+ // focusin the textarea
174
+ if (handlerName.indexOf('cmdSave') < 0) {
175
+ this.$textarea.focus()
176
+ }
177
+
178
+ e.preventDefault()
179
+ }
180
+
181
+ , __localize: function(string) {
182
+ var messages = $.fn.markdown.messages,
183
+ language = this.$options.language
184
+ if (
185
+ typeof messages !== 'undefined' &&
186
+ typeof messages[language] !== 'undefined' &&
187
+ typeof messages[language][string] !== 'undefined'
188
+ ) {
189
+ return messages[language][string];
190
+ }
191
+ return string;
192
+ }
193
+
194
+ , __getIcon: function(src) {
195
+ return typeof src == 'object' ? src[this.$options.iconlibrary] : src;
196
+ }
197
+
198
+ , setFullscreen: function(mode) {
199
+ var $editor = this.$editor,
200
+ $textarea = this.$textarea
201
+
202
+ if (mode === true) {
203
+ $editor.addClass('md-fullscreen-mode')
204
+ $('body').addClass('md-nooverflow')
205
+ this.$options.onFullscreen(this)
206
+ } else {
207
+ $editor.removeClass('md-fullscreen-mode')
208
+ $('body').removeClass('md-nooverflow')
209
+ }
210
+
211
+ this.$isFullscreen = mode;
212
+ $textarea.focus()
213
+ }
214
+
215
+ , showEditor: function() {
216
+ var instance = this,
217
+ textarea,
218
+ ns = this.$ns,
219
+ container = this.$element,
220
+ originalHeigth = container.css('height'),
221
+ originalWidth = container.css('width'),
222
+ editable = this.$editable,
223
+ handler = this.$handler,
224
+ callback = this.$callback,
225
+ options = this.$options,
226
+ editor = $( '<div/>', {
227
+ 'class': 'md-editor',
228
+ click: function() {
229
+ instance.focus()
230
+ }
231
+ })
232
+
233
+ // Prepare the editor
234
+ if (this.$editor == null) {
235
+ // Create the panel
236
+ var editorHeader = $('<div/>', {
237
+ 'class': 'md-header btn-toolbar'
238
+ })
239
+
240
+ // Merge the main & additional button groups together
241
+ var allBtnGroups = []
242
+ if (options.buttons.length > 0) allBtnGroups = allBtnGroups.concat(options.buttons[0])
243
+ if (options.additionalButtons.length > 0) allBtnGroups = allBtnGroups.concat(options.additionalButtons[0])
244
+
245
+ // Reduce and/or reorder the button groups
246
+ if (options.reorderButtonGroups.length > 0) {
247
+ allBtnGroups = allBtnGroups
248
+ .filter(function(btnGroup) {
249
+ return options.reorderButtonGroups.indexOf(btnGroup.name) > -1
250
+ })
251
+ .sort(function(a, b) {
252
+ if (options.reorderButtonGroups.indexOf(a.name) < options.reorderButtonGroups.indexOf(b.name)) return -1
253
+ if (options.reorderButtonGroups.indexOf(a.name) > options.reorderButtonGroups.indexOf(b.name)) return 1
254
+ return 0
255
+ })
256
+ }
257
+
258
+ // Build the buttons
259
+ if (allBtnGroups.length > 0) {
260
+ editorHeader = this.__buildButtons([allBtnGroups], editorHeader)
261
+ }
262
+
263
+ if (options.fullscreen.enable) {
264
+ editorHeader.append('<div class="md-controls"><a class="md-control md-control-fullscreen" href="#"><span class="'+this.__getIcon(options.fullscreen.icons.fullscreenOn)+'"></span></a></div>').on('click', '.md-control-fullscreen', function(e) {
265
+ e.preventDefault();
266
+ instance.setFullscreen(true)
267
+ })
268
+ }
269
+
270
+ editor.append(editorHeader)
271
+
272
+ // Wrap the textarea
273
+ if (container.is('textarea')) {
274
+ container.before(editor)
275
+ textarea = container
276
+ textarea.addClass('md-input')
277
+ editor.append(textarea)
278
+ } else {
279
+ var rawContent = (typeof toMarkdown == 'function') ? toMarkdown(container.html()) : container.html(),
280
+ currentContent = $.trim(rawContent)
281
+
282
+ // This is some arbitrary content that could be edited
283
+ textarea = $('<textarea/>', {
284
+ 'class': 'md-input',
285
+ 'val' : currentContent
286
+ })
287
+
288
+ editor.append(textarea)
289
+
290
+ // Save the editable
291
+ editable.el = container
292
+ editable.type = container.prop('tagName').toLowerCase()
293
+ editable.content = container.html()
294
+
295
+ $(container[0].attributes).each(function(){
296
+ editable.attrKeys.push(this.nodeName)
297
+ editable.attrValues.push(this.nodeValue)
298
+ })
299
+
300
+ // Set editor to blocked the original container
301
+ container.replaceWith(editor)
302
+ }
303
+
304
+ var editorFooter = $('<div/>', {
305
+ 'class': 'md-footer'
306
+ }),
307
+ createFooter = false,
308
+ footer = ''
309
+ // Create the footer if savable
310
+ if (options.savable) {
311
+ createFooter = true;
312
+ var saveHandler = 'cmdSave'
313
+
314
+ // Register handler and callback
315
+ handler.push(saveHandler)
316
+ callback.push(options.onSave)
317
+
318
+ editorFooter.append('<button class="btn btn-success" data-provider="'
319
+ +ns
320
+ +'" data-handler="'
321
+ +saveHandler
322
+ +'"><i class="icon icon-white icon-ok"></i> '
323
+ +this.__localize('Save')
324
+ +'</button>')
325
+
326
+
327
+ }
328
+
329
+ footer = typeof options.footer === 'function' ? options.footer(this) : options.footer
330
+
331
+ if ($.trim(footer) !== '') {
332
+ createFooter = true;
333
+ editorFooter.append(footer);
334
+ }
335
+
336
+ if (createFooter) editor.append(editorFooter)
337
+
338
+ // Set width
339
+ if (options.width && options.width !== 'inherit') {
340
+ if (jQuery.isNumeric(options.width)) {
341
+ editor.css('display', 'table')
342
+ textarea.css('width', options.width + 'px')
343
+ } else {
344
+ editor.addClass(options.width)
345
+ }
346
+ }
347
+
348
+ // Set height
349
+ if (options.height && options.height !== 'inherit') {
350
+ if (jQuery.isNumeric(options.height)) {
351
+ var height = options.height
352
+ if (editorHeader) height = Math.max(0, height - editorHeader.outerHeight())
353
+ if (editorFooter) height = Math.max(0, height - editorFooter.outerHeight())
354
+ textarea.css('height', height + 'px')
355
+ } else {
356
+ editor.addClass(options.height)
357
+ }
358
+ }
359
+
360
+ // Reference
361
+ this.$editor = editor
362
+ this.$textarea = textarea
363
+ this.$editable = editable
364
+ this.$oldContent = this.getContent()
365
+
366
+ this.__setListener()
367
+
368
+ // Set editor attributes, data short-hand API and listener
369
+ this.$editor.attr('id',(new Date).getTime())
370
+ this.$editor.on('click', '[data-provider="bootstrap-markdown"]', $.proxy(this.__handle, this))
371
+
372
+ if (this.$element.is(':disabled') || this.$element.is('[readonly]')) {
373
+ this.$editor.addClass('md-editor-disabled');
374
+ this.disableButtons('all');
375
+ }
376
+
377
+ if (this.eventSupported('keydown') && typeof jQuery.hotkeys === 'object') {
378
+ editorHeader.find('[data-provider="bootstrap-markdown"]').each(function() {
379
+ var $button = $(this),
380
+ hotkey = $button.attr('data-hotkey')
381
+ if (hotkey.toLowerCase() !== '') {
382
+ textarea.bind('keydown', hotkey, function() {
383
+ $button.trigger('click')
384
+ return false;
385
+ })
386
+ }
387
+ })
388
+ }
389
+
390
+ if (options.initialstate === 'preview') {
391
+ this.showPreview();
392
+ } else if (options.initialstate === 'fullscreen' && options.fullscreen.enable) {
393
+ this.setFullscreen(true)
394
+ }
395
+
396
+ } else {
397
+ this.$editor.show()
398
+ }
399
+
400
+ if (options.autofocus) {
401
+ this.$textarea.focus()
402
+ this.$editor.addClass('active')
403
+ }
404
+
405
+ if (options.fullscreen.enable && options.fullscreen !== false) {
406
+ this.$editor.append('\
407
+ <div class="md-fullscreen-controls">\
408
+ <a href="#" class="exit-fullscreen" title="Exit fullscreen"><span class="'+this.__getIcon(options.fullscreen.icons.fullscreenOff)+'"></span></a>\
409
+ </div>')
410
+
411
+ this.$editor.on('click', '.exit-fullscreen', function(e) {
412
+ e.preventDefault()
413
+ instance.setFullscreen(false)
414
+ })
415
+ }
416
+
417
+ // hide hidden buttons from options
418
+ this.hideButtons(options.hiddenButtons)
419
+
420
+ // disable disabled buttons from options
421
+ this.disableButtons(options.disabledButtons)
422
+
423
+ // Trigger the onShow hook
424
+ options.onShow(this)
425
+
426
+ return this
427
+ }
428
+
429
+ , parseContent: function() {
430
+ var content,
431
+ callbackContent = this.$options.onPreview(this) // Try to get the content from callback
432
+
433
+ if (typeof callbackContent == 'string') {
434
+ // Set the content based by callback content
435
+ content = callbackContent
436
+ } else {
437
+ // Set the content
438
+ var val = this.$textarea.val();
439
+ if(typeof markdown == 'object') {
440
+ content = markdown.toHTML(val);
441
+ }else if(typeof marked == 'function') {
442
+ content = marked(val);
443
+ } else {
444
+ content = val;
445
+ }
446
+ }
447
+
448
+ return content;
449
+ }
450
+
451
+ , showPreview: function() {
452
+ var options = this.$options,
453
+ container = this.$textarea,
454
+ afterContainer = container.next(),
455
+ replacementContainer = $('<div/>',{'class':'md-preview','data-provider':'markdown-preview'}),
456
+ content
457
+
458
+ // Give flag that tell the editor enter preview mode
459
+ this.$isPreview = true
460
+ // Disable all buttons
461
+ this.disableButtons('all').enableButtons('cmdPreview')
462
+
463
+ content = this.parseContent()
464
+
465
+ // Build preview element
466
+ replacementContainer.html(content)
467
+
468
+ if (afterContainer && afterContainer.attr('class') == 'md-footer') {
469
+ // If there is footer element, insert the preview container before it
470
+ replacementContainer.insertBefore(afterContainer)
471
+ } else {
472
+ // Otherwise, just append it after textarea
473
+ container.parent().append(replacementContainer)
474
+ }
475
+
476
+ // Set the preview element dimensions
477
+ replacementContainer.css({
478
+ width: container.outerWidth() + 'px',
479
+ height: container.outerHeight() + 'px'
480
+ })
481
+
482
+ if (this.$options.resize) {
483
+ replacementContainer.css('resize',this.$options.resize)
484
+ }
485
+
486
+ // Hide the last-active textarea
487
+ container.hide()
488
+
489
+ // Attach the editor instances
490
+ replacementContainer.data('markdown',this)
491
+
492
+ if (this.$element.is(':disabled') || this.$element.is('[readonly]')) {
493
+ this.$editor.addClass('md-editor-disabled');
494
+ this.disableButtons('all');
495
+ }
496
+
497
+ return this
498
+ }
499
+
500
+ , hidePreview: function() {
501
+ // Give flag that tell the editor quit preview mode
502
+ this.$isPreview = false
503
+
504
+ // Obtain the preview container
505
+ var container = this.$editor.find('div[data-provider="markdown-preview"]')
506
+
507
+ // Remove the preview container
508
+ container.remove()
509
+
510
+ // Enable all buttons
511
+ this.enableButtons('all')
512
+ // Disable configured disabled buttons
513
+ this.disableButtons(this.$options.disabledButtons)
514
+
515
+ // Back to the editor
516
+ this.$textarea.show()
517
+ this.__setListener()
518
+
519
+ return this
520
+ }
521
+
522
+ , isDirty: function() {
523
+ return this.$oldContent != this.getContent()
524
+ }
525
+
526
+ , getContent: function() {
527
+ return this.$textarea.val()
528
+ }
529
+
530
+ , setContent: function(content) {
531
+ this.$textarea.val(content)
532
+
533
+ return this
534
+ }
535
+
536
+ , findSelection: function(chunk) {
537
+ var content = this.getContent(), startChunkPosition
538
+
539
+ if (startChunkPosition = content.indexOf(chunk), startChunkPosition >= 0 && chunk.length > 0) {
540
+ var oldSelection = this.getSelection(), selection
541
+
542
+ this.setSelection(startChunkPosition,startChunkPosition+chunk.length)
543
+ selection = this.getSelection()
544
+
545
+ this.setSelection(oldSelection.start,oldSelection.end)
546
+
547
+ return selection
548
+ } else {
549
+ return null
550
+ }
551
+ }
552
+
553
+ , getSelection: function() {
554
+
555
+ var e = this.$textarea[0]
556
+
557
+ return (
558
+
559
+ ('selectionStart' in e && function() {
560
+ var l = e.selectionEnd - e.selectionStart
561
+ return { start: e.selectionStart, end: e.selectionEnd, length: l, text: e.value.substr(e.selectionStart, l) }
562
+ }) ||
563
+
564
+ /* browser not supported */
565
+ function() {
566
+ return null
567
+ }
568
+
569
+ )()
570
+
571
+ }
572
+
573
+ , setSelection: function(start,end) {
574
+
575
+ var e = this.$textarea[0]
576
+
577
+ return (
578
+
579
+ ('selectionStart' in e && function() {
580
+ e.selectionStart = start
581
+ e.selectionEnd = end
582
+ return
583
+ }) ||
584
+
585
+ /* browser not supported */
586
+ function() {
587
+ return null
588
+ }
589
+
590
+ )()
591
+
592
+ }
593
+
594
+ , replaceSelection: function(text) {
595
+
596
+ var e = this.$textarea[0]
597
+
598
+ return (
599
+
600
+ ('selectionStart' in e && function() {
601
+ e.value = e.value.substr(0, e.selectionStart) + text + e.value.substr(e.selectionEnd, e.value.length)
602
+ // Set cursor to the last replacement end
603
+ e.selectionStart = e.value.length
604
+ return this
605
+ }) ||
606
+
607
+ /* browser not supported */
608
+ function() {
609
+ e.value += text
610
+ return jQuery(e)
611
+ }
612
+
613
+ )()
614
+
615
+ }
616
+
617
+ , getNextTab: function() {
618
+ // Shift the nextTab
619
+ if (this.$nextTab.length == 0) {
620
+ return null
621
+ } else {
622
+ var nextTab, tab = this.$nextTab.shift()
623
+
624
+ if (typeof tab == 'function') {
625
+ nextTab = tab()
626
+ } else if (typeof tab == 'object' && tab.length > 0) {
627
+ nextTab = tab
628
+ }
629
+
630
+ return nextTab
631
+ }
632
+ }
633
+
634
+ , setNextTab: function(start,end) {
635
+ // Push new selection into nextTab collections
636
+ if (typeof start == 'string') {
637
+ var that = this
638
+ this.$nextTab.push(function(){
639
+ return that.findSelection(start)
640
+ })
641
+ } else if (typeof start == 'number' && typeof end == 'number') {
642
+ var oldSelection = this.getSelection()
643
+
644
+ this.setSelection(start,end)
645
+ this.$nextTab.push(this.getSelection())
646
+
647
+ this.setSelection(oldSelection.start,oldSelection.end)
648
+ }
649
+
650
+ return
651
+ }
652
+
653
+ , __parseButtonNameParam: function(nameParam) {
654
+ var buttons = []
655
+
656
+ if (typeof nameParam == 'string') {
657
+ buttons.push(nameParam)
658
+ } else {
659
+ buttons = nameParam
660
+ }
661
+
662
+ return buttons
663
+ }
664
+
665
+ , enableButtons: function(name) {
666
+ var buttons = this.__parseButtonNameParam(name),
667
+ that = this
668
+
669
+ $.each(buttons, function(i, v) {
670
+ that.__alterButtons(buttons[i], function (el) {
671
+ el.removeAttr('disabled')
672
+ });
673
+ })
674
+
675
+ return this;
676
+ }
677
+
678
+ , disableButtons: function(name) {
679
+ var buttons = this.__parseButtonNameParam(name),
680
+ that = this
681
+
682
+ $.each(buttons, function(i, v) {
683
+ that.__alterButtons(buttons[i], function (el) {
684
+ el.attr('disabled','disabled')
685
+ });
686
+ })
687
+
688
+ return this;
689
+ }
690
+
691
+ , hideButtons: function(name) {
692
+ var buttons = this.__parseButtonNameParam(name),
693
+ that = this
694
+
695
+ $.each(buttons, function(i, v) {
696
+ that.__alterButtons(buttons[i], function (el) {
697
+ el.addClass('hidden');
698
+ });
699
+ })
700
+
701
+ return this;
702
+
703
+ }
704
+
705
+ , showButtons: function(name) {
706
+ var buttons = this.__parseButtonNameParam(name),
707
+ that = this
708
+
709
+ $.each(buttons, function(i, v) {
710
+ that.__alterButtons(buttons[i], function (el) {
711
+ el.removeClass('hidden');
712
+ });
713
+ })
714
+
715
+ return this;
716
+
717
+ }
718
+
719
+ , eventSupported: function(eventName) {
720
+ var isSupported = eventName in this.$element
721
+ if (!isSupported) {
722
+ this.$element.setAttribute(eventName, 'return;')
723
+ isSupported = typeof this.$element[eventName] === 'function'
724
+ }
725
+ return isSupported
726
+ }
727
+
728
+ , keyup: function (e) {
729
+ var blocked = false
730
+ switch(e.keyCode) {
731
+ case 40: // down arrow
732
+ case 38: // up arrow
733
+ case 16: // shift
734
+ case 17: // ctrl
735
+ case 18: // alt
736
+ break
737
+
738
+ case 9: // tab
739
+ var nextTab
740
+ if (nextTab = this.getNextTab(),nextTab != null) {
741
+ // Get the nextTab if exists
742
+ var that = this
743
+ setTimeout(function(){
744
+ that.setSelection(nextTab.start,nextTab.end)
745
+ },500)
746
+
747
+ blocked = true
748
+ } else {
749
+ // The next tab memory contains nothing...
750
+ // check the cursor position to determine tab action
751
+ var cursor = this.getSelection()
752
+
753
+ if (cursor.start == cursor.end && cursor.end == this.getContent().length) {
754
+ // The cursor already reach the end of the content
755
+ blocked = false
756
+
757
+ } else {
758
+ // Put the cursor to the end
759
+ this.setSelection(this.getContent().length,this.getContent().length)
760
+
761
+ blocked = true
762
+ }
763
+ }
764
+
765
+ break
766
+
767
+ case 13: // enter
768
+ blocked = false
769
+ break
770
+ case 27: // escape
771
+ if (this.$isFullscreen) this.setFullscreen(false)
772
+ blocked = false
773
+ break
774
+
775
+ default:
776
+ blocked = false
777
+ }
778
+
779
+ if (blocked) {
780
+ e.stopPropagation()
781
+ e.preventDefault()
782
+ }
783
+
784
+ this.$options.onChange(this)
785
+ }
786
+
787
+ , change: function(e) {
788
+ this.$options.onChange(this);
789
+ return this;
790
+ }
791
+
792
+ , focus: function (e) {
793
+ var options = this.$options,
794
+ isHideable = options.hideable,
795
+ editor = this.$editor
796
+
797
+ editor.addClass('active')
798
+
799
+ // Blur other markdown(s)
800
+ $(document).find('.md-editor').each(function(){
801
+ if ($(this).attr('id') != editor.attr('id')) {
802
+ var attachedMarkdown
803
+
804
+ if (attachedMarkdown = $(this).find('textarea').data('markdown'),
805
+ attachedMarkdown == null) {
806
+ attachedMarkdown = $(this).find('div[data-provider="markdown-preview"]').data('markdown')
807
+ }
808
+
809
+ if (attachedMarkdown) {
810
+ attachedMarkdown.blur()
811
+ }
812
+ }
813
+ })
814
+
815
+ // Trigger the onFocus hook
816
+ options.onFocus(this);
817
+
818
+ return this
819
+ }
820
+
821
+ , blur: function (e) {
822
+ var options = this.$options,
823
+ isHideable = options.hideable,
824
+ editor = this.$editor,
825
+ editable = this.$editable
826
+
827
+ if (editor.hasClass('active') || this.$element.parent().length == 0) {
828
+ editor.removeClass('active')
829
+
830
+ if (isHideable) {
831
+
832
+ // Check for editable elements
833
+ if (editable.el != null) {
834
+ // Build the original element
835
+ var oldElement = $('<'+editable.type+'/>'),
836
+ content = this.getContent(),
837
+ currentContent = (typeof markdown == 'object') ? markdown.toHTML(content) : content
838
+
839
+ $(editable.attrKeys).each(function(k,v) {
840
+ oldElement.attr(editable.attrKeys[k],editable.attrValues[k])
841
+ })
842
+
843
+ // Get the editor content
844
+ oldElement.html(currentContent)
845
+
846
+ editor.replaceWith(oldElement)
847
+ } else {
848
+ editor.hide()
849
+
850
+ }
851
+ }
852
+
853
+ // Trigger the onBlur hook
854
+ options.onBlur(this)
855
+ }
856
+
857
+ return this
858
+ }
859
+
860
+ }
861
+
862
+ /* MARKDOWN PLUGIN DEFINITION
863
+ * ========================== */
864
+
865
+ var old = $.fn.markdown
866
+
867
+ $.fn.markdown = function (option) {
868
+ return this.each(function () {
869
+ var $this = $(this)
870
+ , data = $this.data('markdown')
871
+ , options = typeof option == 'object' && option
872
+ if (!data) $this.data('markdown', (data = new Markdown(this, options)))
873
+ })
874
+ }
875
+
876
+ $.fn.markdown.messages = {}
877
+
878
+ $.fn.markdown.defaults = {
879
+ /* Editor Properties */
880
+ autofocus: false,
881
+ hideable: false,
882
+ savable:false,
883
+ width: 'inherit',
884
+ height: 'inherit',
885
+ resize: 'none',
886
+ iconlibrary: 'glyph',
887
+ language: 'en',
888
+ initialstate: 'editor',
889
+
890
+ /* Buttons Properties */
891
+ buttons: [
892
+ [{
893
+ name: 'groupFont',
894
+ data: [{
895
+ name: 'cmdBold',
896
+ hotkey: 'Ctrl+B',
897
+ title: 'Bold',
898
+ icon: { glyph: 'glyphicon glyphicon-bold', fa: 'fa fa-bold', 'fa-3': 'icon-bold' },
899
+ callback: function(e){
900
+ // Give/remove ** surround the selection
901
+ var chunk, cursor, selected = e.getSelection(), content = e.getContent()
902
+
903
+ if (selected.length == 0) {
904
+ // Give extra word
905
+ chunk = e.__localize('strong text')
906
+ } else {
907
+ chunk = selected.text
908
+ }
909
+
910
+ // transform selection and set the cursor into chunked text
911
+ if (content.substr(selected.start-2,2) == '**'
912
+ && content.substr(selected.end,2) == '**' ) {
913
+ e.setSelection(selected.start-2,selected.end+2)
914
+ e.replaceSelection(chunk)
915
+ cursor = selected.start-2
916
+ } else {
917
+ e.replaceSelection('**'+chunk+'**')
918
+ cursor = selected.start+2
919
+ }
920
+
921
+ // Set the cursor
922
+ e.setSelection(cursor,cursor+chunk.length)
923
+ }
924
+ },{
925
+ name: 'cmdItalic',
926
+ title: 'Italic',
927
+ hotkey: 'Ctrl+I',
928
+ icon: { glyph: 'glyphicon glyphicon-italic', fa: 'fa fa-italic', 'fa-3': 'icon-italic' },
929
+ callback: function(e){
930
+ // Give/remove * surround the selection
931
+ var chunk, cursor, selected = e.getSelection(), content = e.getContent()
932
+
933
+ if (selected.length == 0) {
934
+ // Give extra word
935
+ chunk = e.__localize('emphasized text')
936
+ } else {
937
+ chunk = selected.text
938
+ }
939
+
940
+ // transform selection and set the cursor into chunked text
941
+ if (content.substr(selected.start-1,1) == '_'
942
+ && content.substr(selected.end,1) == '_' ) {
943
+ e.setSelection(selected.start-1,selected.end+1)
944
+ e.replaceSelection(chunk)
945
+ cursor = selected.start-1
946
+ } else {
947
+ e.replaceSelection('_'+chunk+'_')
948
+ cursor = selected.start+1
949
+ }
950
+
951
+ // Set the cursor
952
+ e.setSelection(cursor,cursor+chunk.length)
953
+ }
954
+ },{
955
+ name: 'cmdHeading',
956
+ title: 'Heading',
957
+ hotkey: 'Ctrl+H',
958
+ icon: { glyph: 'glyphicon glyphicon-header', fa: 'fa fa-font', 'fa-3': 'icon-font' },
959
+ callback: function(e){
960
+ // Append/remove ### surround the selection
961
+ var chunk, cursor, selected = e.getSelection(), content = e.getContent(), pointer, prevChar
962
+
963
+ if (selected.length == 0) {
964
+ // Give extra word
965
+ chunk = e.__localize('heading text')
966
+ } else {
967
+ chunk = selected.text + '\n';
968
+ }
969
+
970
+ // transform selection and set the cursor into chunked text
971
+ if ((pointer = 4, content.substr(selected.start-pointer,pointer) == '### ')
972
+ || (pointer = 3, content.substr(selected.start-pointer,pointer) == '###')) {
973
+ e.setSelection(selected.start-pointer,selected.end)
974
+ e.replaceSelection(chunk)
975
+ cursor = selected.start-pointer
976
+ } else if (selected.start > 0 && (prevChar = content.substr(selected.start-1,1), !!prevChar && prevChar != '\n')) {
977
+ e.replaceSelection('\n\n### '+chunk)
978
+ cursor = selected.start+6
979
+ } else {
980
+ // Empty string before element
981
+ e.replaceSelection('### '+chunk)
982
+ cursor = selected.start+4
983
+ }
984
+
985
+ // Set the cursor
986
+ e.setSelection(cursor,cursor+chunk.length)
987
+ }
988
+ }]
989
+ },{
990
+ name: 'groupLink',
991
+ data: [{
992
+ name: 'cmdUrl',
993
+ title: 'URL/Link',
994
+ hotkey: 'Ctrl+L',
995
+ icon: { glyph: 'glyphicon glyphicon-link', fa: 'fa fa-link', 'fa-3': 'icon-link' },
996
+ callback: function(e){
997
+ // Give [] surround the selection and prepend the link
998
+ var chunk, cursor, selected = e.getSelection(), content = e.getContent(), link
999
+
1000
+ if (selected.length == 0) {
1001
+ // Give extra word
1002
+ chunk = e.__localize('enter link description here')
1003
+ } else {
1004
+ chunk = selected.text
1005
+ }
1006
+
1007
+ link = prompt(e.__localize('Insert Hyperlink'),'http://')
1008
+
1009
+ if (link != null && link != '' && link != 'http://' && link.substr(0,4) == 'http') {
1010
+ var sanitizedLink = $('<div>'+link+'</div>').text()
1011
+
1012
+ // transform selection and set the cursor into chunked text
1013
+ e.replaceSelection('['+chunk+']('+sanitizedLink+')')
1014
+ cursor = selected.start+1
1015
+
1016
+ // Set the cursor
1017
+ e.setSelection(cursor,cursor+chunk.length)
1018
+ }
1019
+ }
1020
+ },{
1021
+ name: 'cmdImage',
1022
+ title: 'Image',
1023
+ hotkey: 'Ctrl+G',
1024
+ icon: { glyph: 'glyphicon glyphicon-picture', fa: 'fa fa-picture-o', 'fa-3': 'icon-picture' },
1025
+ callback: function(e){
1026
+ // Give ![] surround the selection and prepend the image link
1027
+ var chunk, cursor, selected = e.getSelection(), content = e.getContent(), link
1028
+
1029
+ if (selected.length == 0) {
1030
+ // Give extra word
1031
+ chunk = e.__localize('enter image description here')
1032
+ } else {
1033
+ chunk = selected.text
1034
+ }
1035
+
1036
+ link = prompt(e.__localize('Insert Image Hyperlink'),'http://')
1037
+
1038
+ if (link != null && link != '' && link != 'http://' && link.substr(0,4) == 'http') {
1039
+ var sanitizedLink = $('<div>'+link+'</div>').text()
1040
+
1041
+ // transform selection and set the cursor into chunked text
1042
+ e.replaceSelection('!['+chunk+']('+sanitizedLink+' "'+e.__localize('enter image title here')+'")')
1043
+ cursor = selected.start+2
1044
+
1045
+ // Set the next tab
1046
+ e.setNextTab(e.__localize('enter image title here'))
1047
+
1048
+ // Set the cursor
1049
+ e.setSelection(cursor,cursor+chunk.length)
1050
+ }
1051
+ }
1052
+ }]
1053
+ },{
1054
+ name: 'groupMisc',
1055
+ data: [{
1056
+ name: 'cmdList',
1057
+ hotkey: 'Ctrl+U',
1058
+ title: 'Unordered List',
1059
+ icon: { glyph: 'glyphicon glyphicon-list', fa: 'fa fa-list', 'fa-3': 'icon-list-ul' },
1060
+ callback: function(e){
1061
+ // Prepend/Give - surround the selection
1062
+ var chunk, cursor, selected = e.getSelection(), content = e.getContent()
1063
+
1064
+ // transform selection and set the cursor into chunked text
1065
+ if (selected.length == 0) {
1066
+ // Give extra word
1067
+ chunk = e.__localize('list text here')
1068
+
1069
+ e.replaceSelection('- '+chunk)
1070
+ // Set the cursor
1071
+ cursor = selected.start+2
1072
+
1073
+ } else {
1074
+ if (selected.text.indexOf('\n') < 0) {
1075
+ chunk = selected.text
1076
+
1077
+ e.replaceSelection('- '+chunk)
1078
+
1079
+ // Set the cursor
1080
+ cursor = selected.start+2
1081
+ } else {
1082
+ var list = []
1083
+
1084
+ list = selected.text.split('\n')
1085
+ chunk = list[0]
1086
+
1087
+ $.each(list,function(k,v) {
1088
+ list[k] = '- '+v
1089
+ })
1090
+
1091
+ e.replaceSelection('\n\n'+list.join('\n'))
1092
+
1093
+ // Set the cursor
1094
+ cursor = selected.start+4
1095
+ }
1096
+ }
1097
+
1098
+ // Set the cursor
1099
+ e.setSelection(cursor,cursor+chunk.length)
1100
+ }
1101
+ },
1102
+ {
1103
+ name: 'cmdListO',
1104
+ hotkey: 'Ctrl+O',
1105
+ title: 'Ordered List',
1106
+ icon: { glyph: 'glyphicon glyphicon-th-list', fa: 'fa fa-list-ol', 'fa-3': 'icon-list-ol' },
1107
+ callback: function(e) {
1108
+
1109
+ // Prepend/Give - surround the selection
1110
+ var chunk, cursor, selected = e.getSelection(), content = e.getContent()
1111
+
1112
+ // transform selection and set the cursor into chunked text
1113
+ if (selected.length == 0) {
1114
+ // Give extra word
1115
+ chunk = e.__localize('list text here')
1116
+ e.replaceSelection('1. '+chunk)
1117
+ // Set the cursor
1118
+ cursor = selected.start+3
1119
+
1120
+ } else {
1121
+ if (selected.text.indexOf('\n') < 0) {
1122
+ chunk = selected.text
1123
+
1124
+ e.replaceSelection('1. '+chunk)
1125
+
1126
+ // Set the cursor
1127
+ cursor = selected.start+3
1128
+ } else {
1129
+ var list = []
1130
+
1131
+ list = selected.text.split('\n')
1132
+ chunk = list[0]
1133
+
1134
+ $.each(list,function(k,v) {
1135
+ list[k] = '1. '+v
1136
+ })
1137
+
1138
+ e.replaceSelection('\n\n'+list.join('\n'))
1139
+
1140
+ // Set the cursor
1141
+ cursor = selected.start+5
1142
+ }
1143
+ }
1144
+
1145
+ // Set the cursor
1146
+ e.setSelection(cursor,cursor+chunk.length)
1147
+ }
1148
+ },
1149
+ {
1150
+ name: 'cmdCode',
1151
+ hotkey: 'Ctrl+K',
1152
+ title: 'Code',
1153
+ icon: { glyph: 'glyphicon glyphicon-asterisk', fa: 'fa fa-code', 'fa-3': 'icon-code' },
1154
+ callback: function(e) {
1155
+
1156
+ // Give/remove ** surround the selection
1157
+ var chunk, cursor, selected = e.getSelection(), content = e.getContent()
1158
+
1159
+ if (selected.length == 0) {
1160
+ // Give extra word
1161
+ chunk = e.__localize('code text here')
1162
+ } else {
1163
+ chunk = selected.text
1164
+ }
1165
+
1166
+ // transform selection and set the cursor into chunked text
1167
+ if (content.substr(selected.start-1,1) == '`'
1168
+ && content.substr(selected.end,1) == '`' ) {
1169
+ e.setSelection(selected.start-1,selected.end+1)
1170
+ e.replaceSelection(chunk)
1171
+ cursor = selected.start-1
1172
+ } else {
1173
+ e.replaceSelection('`'+chunk+'`')
1174
+ cursor = selected.start+1
1175
+ }
1176
+
1177
+ // Set the cursor
1178
+ e.setSelection(cursor,cursor+chunk.length)
1179
+ }
1180
+ },
1181
+ {
1182
+ name: 'cmdQuote',
1183
+ hotkey: 'Ctrl+Q',
1184
+ title: 'Quote',
1185
+ icon: { glyph: 'glyphicon glyphicon-comment', fa: 'fa fa-quote-left', 'fa-3': 'icon-quote-left' },
1186
+ callback: function(e) {
1187
+ // Prepend/Give - surround the selection
1188
+ var chunk, cursor, selected = e.getSelection(), content = e.getContent()
1189
+
1190
+ // transform selection and set the cursor into chunked text
1191
+ if (selected.length == 0) {
1192
+ // Give extra word
1193
+ chunk = e.__localize('quote here')
1194
+ e.replaceSelection('> '+chunk)
1195
+ // Set the cursor
1196
+ cursor = selected.start+2
1197
+
1198
+ } else {
1199
+ if (selected.text.indexOf('\n') < 0) {
1200
+ chunk = selected.text
1201
+
1202
+ e.replaceSelection('> '+chunk)
1203
+
1204
+ // Set the cursor
1205
+ cursor = selected.start+2
1206
+ } else {
1207
+ var list = []
1208
+
1209
+ list = selected.text.split('\n')
1210
+ chunk = list[0]
1211
+
1212
+ $.each(list,function(k,v) {
1213
+ list[k] = '> '+v
1214
+ })
1215
+
1216
+ e.replaceSelection('\n\n'+list.join('\n'))
1217
+
1218
+ // Set the cursor
1219
+ cursor = selected.start+4
1220
+ }
1221
+ }
1222
+
1223
+ // Set the cursor
1224
+ e.setSelection(cursor,cursor+chunk.length)
1225
+ }
1226
+ }]
1227
+ },{
1228
+ name: 'groupUtil',
1229
+ data: [{
1230
+ name: 'cmdPreview',
1231
+ toggle: true,
1232
+ hotkey: 'Ctrl+P',
1233
+ title: 'Preview',
1234
+ btnText: 'Preview',
1235
+ btnClass: 'btn btn-primary btn-sm',
1236
+ icon: { glyph: 'glyphicon glyphicon-search', fa: 'fa fa-search', 'fa-3': 'icon-search' },
1237
+ callback: function(e){
1238
+ // Check the preview mode and toggle based on this flag
1239
+ var isPreview = e.$isPreview,content
1240
+
1241
+ if (isPreview == false) {
1242
+ // Give flag that tell the editor enter preview mode
1243
+ e.showPreview()
1244
+ } else {
1245
+ e.hidePreview()
1246
+ }
1247
+ }
1248
+ }]
1249
+ }]
1250
+ ],
1251
+ additionalButtons:[], // Place to hook more buttons by code
1252
+ reorderButtonGroups:[],
1253
+ hiddenButtons:[], // Default hidden buttons
1254
+ disabledButtons:[], // Default disabled buttons
1255
+ footer: '',
1256
+ fullscreen: {
1257
+ enable: true,
1258
+ icons: {
1259
+ fullscreenOn: {
1260
+ fa: 'fa fa-expand',
1261
+ glyph: 'glyphicon glyphicon-fullscreen',
1262
+ 'fa-3': 'icon-resize-full'
1263
+ },
1264
+ fullscreenOff: {
1265
+ fa: 'fa fa-compress',
1266
+ glyph: 'glyphicon glyphicon-fullscreen',
1267
+ 'fa-3': 'icon-resize-small'
1268
+ }
1269
+ }
1270
+ },
1271
+
1272
+ /* Events hook */
1273
+ onShow: function (e) {},
1274
+ onPreview: function (e) {},
1275
+ onSave: function (e) {},
1276
+ onBlur: function (e) {},
1277
+ onFocus: function (e) {},
1278
+ onChange: function(e) {},
1279
+ onFullscreen: function(e) {}
1280
+ }
1281
+
1282
+ $.fn.markdown.Constructor = Markdown
1283
+
1284
+
1285
+ /* MARKDOWN NO CONFLICT
1286
+ * ==================== */
1287
+
1288
+ $.fn.markdown.noConflict = function () {
1289
+ $.fn.markdown = old
1290
+ return this
1291
+ }
1292
+
1293
+ /* MARKDOWN GLOBAL FUNCTION & DATA-API
1294
+ * ==================================== */
1295
+ var initMarkdown = function(el) {
1296
+ var $this = el
1297
+
1298
+ if ($this.data('markdown')) {
1299
+ $this.data('markdown').showEditor()
1300
+ return
1301
+ }
1302
+
1303
+ $this.markdown()
1304
+ }
1305
+
1306
+ var blurNonFocused = function(e) {
1307
+ var $activeElement = $(document.activeElement)
1308
+
1309
+ // Blur event
1310
+ $(document).find('.md-editor').each(function(){
1311
+ var $this = $(this),
1312
+ focused = $activeElement.closest('.md-editor')[0] === this,
1313
+ attachedMarkdown = $this.find('textarea').data('markdown') ||
1314
+ $this.find('div[data-provider="markdown-preview"]').data('markdown')
1315
+
1316
+ if (attachedMarkdown && !focused) {
1317
+ attachedMarkdown.blur()
1318
+ }
1319
+ })
1320
+ }
1321
+
1322
+ $(document)
1323
+ .on('click.markdown.data-api', '[data-provide="markdown-editable"]', function (e) {
1324
+ initMarkdown($(this))
1325
+ e.preventDefault()
1326
+ })
1327
+ .on('click focusin', function (e) {
1328
+ blurNonFocused(e)
1329
+ })
1330
+ .ready(function(){
1331
+ $('textarea[data-provide="markdown"]').each(function(){
1332
+ initMarkdown($(this))
1333
+ })
1334
+ })
1335
+
1336
+ }(window.jQuery);