github.com/insionng/yougam@v0.0.0-20170714101924-2bc18d833463/themes/wind/static/libs/toopay-bootstrap-markdown/js/bootstrap-markdown.js (about)

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