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