rails-bootstrap-markdown 0.0.2

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