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('+'")') 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);