1// ---------------------------------------------------------------------------- 2// markItUp! Universal MarkUp Engine, JQuery plugin 3// v 1.1.x 4// Dual licensed under the MIT and GPL licenses. 5// ---------------------------------------------------------------------------- 6// Copyright (C) 2007-2012 Jay Salvat 7// http://markitup.jaysalvat.com/ 8// ---------------------------------------------------------------------------- 9// Permission is hereby granted, free of charge, to any person obtaining a copy 10// of this software and associated documentation files (the "Software"), to deal 11// in the Software without restriction, including without limitation the rights 12// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13// copies of the Software, and to permit persons to whom the Software is 14// furnished to do so, subject to the following conditions: 15// 16// The above copyright notice and this permission notice shall be included in 17// all copies or substantial portions of the Software. 18// 19// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25// THE SOFTWARE. 26// ---------------------------------------------------------------------------- 27(function($) { 28 $.fn.markItUp = function(settings, extraSettings) { 29 var method, params, options, ctrlKey, shiftKey, altKey; ctrlKey = shiftKey = altKey = false; 30 31 if (typeof settings == 'string') { 32 method = settings; 33 params = extraSettings; 34 } 35 36 options = { id: '', 37 nameSpace: '', 38 root: '', 39 previewHandler: false, 40 previewInWindow: '', // 'width=800, height=600, resizable=yes, scrollbars=yes' 41 previewInElement: '', 42 previewAutoRefresh: true, 43 previewPosition: 'after', 44 previewTemplatePath: '~/templates/preview.html', 45 previewParser: false, 46 previewParserPath: '', 47 previewParserVar: 'data', 48 resizeHandle: true, 49 beforeInsert: '', 50 afterInsert: '', 51 onEnter: {}, 52 onShiftEnter: {}, 53 onCtrlEnter: {}, 54 onTab: {}, 55 markupSet: [ { /* set */ } ] 56 }; 57 $.extend(options, settings, extraSettings); 58 59 // compute markItUp! path 60 if (!options.root) { 61 $('script').each(function(a, tag) { 62 miuScript = $(tag).get(0).src.match(/(.*)jquery\.markitup(\.pack)?\.js$/); 63 if (miuScript !== null) { 64 options.root = miuScript[1]; 65 } 66 }); 67 } 68 69 // Quick patch to keep compatibility with jQuery 1.9 70 var uaMatch = function(ua) { 71 ua = ua.toLowerCase(); 72 73 var match = /(chrome)[ \/]([\w.]+)/.exec(ua) || 74 /(webkit)[ \/]([\w.]+)/.exec(ua) || 75 /(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) || 76 /(msie) ([\w.]+)/.exec(ua) || 77 ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) || 78 []; 79 80 return { 81 browser: match[ 1 ] || "", 82 version: match[ 2 ] || "0" 83 }; 84 }; 85 var matched = uaMatch( navigator.userAgent ); 86 var browser = {}; 87 88 if (matched.browser) { 89 browser[matched.browser] = true; 90 browser.version = matched.version; 91 } 92 if (browser.chrome) { 93 browser.webkit = true; 94 } else if (browser.webkit) { 95 browser.safari = true; 96 } 97 98 return this.each(function() { 99 var $$, textarea, levels, scrollPosition, caretPosition, caretOffset, 100 clicked, hash, header, footer, previewWindow, template, iFrame, abort; 101 $$ = $(this); 102 textarea = this; 103 levels = []; 104 abort = false;
105 scrollPosition = caretPosition = 0; 106 caretOffset = -1; 107 108 options.previewParserPath = localize(options.previewParserPath); 109 options.previewTemplatePath = localize(options.previewTemplatePath); 110 111 if (method) { 112 switch(method) { 113 case 'remove': 114 remove(); 115 break; 116 case 'insert': 117 markup(params); 118 break; 119 default: 120 $.error('Method ' + method + ' does not exist on jQuery.markItUp'); 121 } 122 return; 123 } 124 125 // apply the computed path to ~/ 126 function localize(data, inText) { 127 if (inText) { 128 return data.replace(/("|')~\//g, "$1"+options.root); 129 } 130 return data.replace(/^~\//, options.root); 131 } 132 133 // init and build editor 134 function init() { 135 id = ''; nameSpace = ''; 136 if (options.id) { 137 id = 'id="'+options.id+'"'; 138 } else if ($$.attr("id")) { 139 id = 'id="markItUp'+($$.attr("id").substr(0, 1).toUpperCase())+($$.attr("id").substr(1))+'"'; 140 141 } 142 if (options.nameSpace) { 143 nameSpace = 'class="'+options.nameSpace+'"'; 144 } 145 $$.wrap('<div '+nameSpace+'></div>'); 146 $$.wrap('<div '+id+' class="markItUp"></div>'); 147 $$.wrap('<div class="markItUpContainer"></div>'); 148 $$.addClass("markItUpEditor"); 149 150 // add the header before the textarea 151 header = $('<div class="markItUpHeader"></div>').insertBefore($$); 152 $(dropMenus(options.markupSet)).appendTo(header); 153 154 // add the footer after the textarea 155 footer = $('<div class="markItUpFooter"></div>').insertAfter($$); 156 157 // add the resize handle after textarea 158 if (options.resizeHandle === true && browser.safari !== true) { 159 resizeHandle = $('<div class="markItUpResizeHandle"></div>') 160 .insertAfter($$) 161 .bind("mousedown.markItUp", function(e) { 162 var h = $$.height(), y = e.clientY, mouseMove, mouseUp; 163 mouseMove = function(e) { 164 $$.css("height", Math.max(20, e.clientY+h-y)+"px"); 165 return false; 166 }; 167 mouseUp = function(e) { 168 $("html").unbind("mousemove.markItUp", mouseMove).unbind("mouseup.markItUp", mouseUp); 169 return false; 170 }; 171 $("html").bind("mousemove.markItUp", mouseMove).bind("mouseup.markItUp", mouseUp); 172 }); 173 footer.append(resizeHandle); 174 } 175 176 // listen key events 177 $$.bind('keydown.markItUp', keyPressed).bind('keyup', keyPressed); 178 179 // bind an event to catch external calls 180 $$.bind("insertion.markItUp", function(e, settings) { 181 if (settings.target !== false) { 182 get(); 183 } 184 if (textarea === $.markItUp.focused) { 185 markup(settings); 186 } 187 }); 188 189 // remember the last focus 190 $$.bind('focus.markItUp', function() { 191 $.markItUp.focused = this; 192 }); 193 194 if (options.previewInElement) { 195 refreshPreview(); 196 } 197 } 198 199 // recursively build header with dropMenus from markupset 200 function dropMenus(markupSet) { 201 var ul = $('<ul></ul>'), i = 0; 202 $('li:hover > ul', ul).css('display', 'block'); 203 $.each(markupSet, function() { 204 var button = this, t = '', title, li, j; 205 title = (button.key) ? (button.name||'')+' [Ctrl+'+button.key+']' : (button.name||''); 206 key = (button.key) ? 'accesskey="'+button.key+'"' : ''; 207 if (button.separator) { 208 li = $('<li class="markItUpSeparator">'+(button.separator||'')+'</li>').appendTo(ul); 209 } else { 210 i++; 211 for (j = levels.length -1; j >= 0; j--) { 212 t += levels[j]+"-"; 213 } 214 li = $('<li class="markItUpButton markItUpButton'+t+(i)+' '+(button.className||'')+'"><a href="" '+key+' title="'+title+'">'+(button.name||'')+'</a></li>') 215 .bind("contextmenu.markItUp", function() { // prevent contextmenu on mac and allow ctrl+click 216 return false; 217 }).bind('click.markItUp', function(e) { 218 e.preventDefault(); 219 }).bind("focusin.markItUp", function(){ 220 $$.focus(); 221 }).bind('mouseup', function() { 222 if (button.call) { 223 eval(button.call)(); 224 } 225 setTimeout(function() { markup(button) },1); 226 return false; 227 }).bind('mouseenter.markItUp', function() { 228 $('> ul', this).show(); 229 $(document).one('click', function() { // close dropmenu if click outside 230 $('ul ul', header).hide(); 231 } 232 ); 233 }).bind('mouseleave.markItUp', function() { 234 $('> ul', this).hide(); 235 }).appendTo(ul); 236 if (button.dropMenu) { 237 levels.push(i);
238 $(li).addClass('markItUpDropMenu').append(dropMenus(button.dropMenu)); 239 } 240 } 241 }); 242 levels.pop(); 243 return ul; 244 } 245 246 // markItUp! markups 247 function magicMarkups(string) { 248 if (string) { 249 string = string.toString(); 250 string = string.replace(/\(\!\(([\s\S]*?)\)\!\)/g, 251 function(x, a) { 252 var b = a.split('|!|'); 253 if (altKey === true) { 254 return (b[1] !== undefined) ? b[1] : b[0]; 255 } else { 256 return (b[1] === undefined) ? "" : b[0]; 257 } 258 } 259 ); 260 // [![prompt]!], [![prompt:!:value]!] 261 string = string.replace(/\[\!\[([\s\S]*?)\]\!\]/g, 262 function(x, a) { 263 var b = a.split(':!:'); 264 if (abort === true) { 265 return false; 266 } 267 value = prompt(b[0], (b[1]) ? b[1] : ''); 268 if (value === null) { 269 abort = true; 270 } 271 return value; 272 } 273 ); 274 return string; 275 } 276 return ""; 277 } 278 279 // prepare action 280 function prepare(action) { 281 if ($.isFunction(action)) { 282 action = action(hash); 283 } 284 return magicMarkups(action); 285 } 286 287 // build block to insert 288 function build(string) { 289 var openWith = prepare(clicked.openWith); 290 var placeHolder = prepare(clicked.placeHolder); 291 var replaceWith = prepare(clicked.replaceWith); 292 var closeWith = prepare(clicked.closeWith); 293 var openBlockWith = prepare(clicked.openBlockWith); 294 var closeBlockWith = prepare(clicked.closeBlockWith); 295 var multiline = clicked.multiline; 296 297 if (replaceWith !== "") { 298 block = openWith + replaceWith + closeWith; 299 } else if (selection === '' && placeHolder !== '') { 300 block = openWith + placeHolder + closeWith; 301 } else { 302 string = string || selection; 303 304 var lines = [string], blocks = []; 305 306 if (multiline === true) { 307 lines = string.split(/\r?\n/); 308 } 309 310 for (var l = 0; l < lines.length; l++) { 311 line = lines[l]; 312 var trailingSpaces; 313 if (trailingSpaces = line.match(/ *$/)) { 314 blocks.push(openWith + line.replace(/ *$/g, '') + closeWith + trailingSpaces); 315 } else { 316 blocks.push(openWith + line + closeWith); 317 } 318 } 319 320 block = blocks.join("\n"); 321 } 322 323 block = openBlockWith + block + closeBlockWith; 324 325 return { block:block, 326 openBlockWith:openBlockWith, 327 openWith:openWith, 328 replaceWith:replaceWith, 329 placeHolder:placeHolder, 330 closeWith:closeWith, 331 closeBlockWith:closeBlockWith 332 }; 333 } 334 335 // define markup to insert 336 function markup(button) { 337 var len, j, n, i; 338 hash = clicked = button; 339 get(); 340 $.extend(hash, { line:"", 341 root:options.root, 342 textarea:textarea, 343 selection:(selection||''), 344 caretPosition:caretPosition, 345 ctrlKey:ctrlKey, 346 shiftKey:shiftKey, 347 altKey:altKey 348 } 349 ); 350 // callbacks before insertion 351 prepare(options.beforeInsert); 352 prepare(clicked.beforeInsert);
353 if ((ctrlKey === true && shiftKey === true) || button.multiline === true) { 354 prepare(clicked.beforeMultiInsert); 355 } 356 $.extend(hash, { line:1 }); 357 358 if ((ctrlKey === true && shiftKey === true)) { 359 lines = selection.split(/\r?\n/); 360 for (j = 0, n = lines.length, i = 0; i < n; i++) { 361 if ($.trim(lines[i]) !== '') { 362 $.extend(hash, { line:++j, selection:lines[i] } ); 363 lines[i] = build(lines[i]).block; 364 } else { 365 lines[i] = ""; 366 } 367 } 368 369 string = { block:lines.join('\n')}; 370 start = caretPosition; 371 len = string.block.length + ((browser.opera) ? n-1 : 0); 372 } else if (ctrlKey === true) { 373 string = build(selection); 374 start = caretPosition + string.openWith.length; 375 len = string.block.length - string.openWith.length - string.closeWith.length; 376 len = len - (string.block.match(/ $/) ? 1 : 0); 377 len -= fixIeBug(string.block); 378 } else if (shiftKey === true) { 379 string = build(selection); 380 start = caretPosition; 381 len = string.block.length; 382 len -= fixIeBug(string.block); 383 } else { 384 string = build(selection); 385 start = caretPosition + string.block.length ; 386 len = 0; 387 start -= fixIeBug(string.block); 388 } 389 if ((selection === '' && string.replaceWith === '')) { 390 caretOffset += fixOperaBug(string.block); 391 392 start = caretPosition + string.openBlockWith.length + string.openWith.length; 393 len = string.block.length - string.openBlockWith.length - string.openWith.length - string.closeWith.length - string.closeBlockWith.length; 394 395 caretOffset = $$.val().substring(caretPosition, $$.val().length).length; 396 caretOffset -= fixOperaBug($$.val().substring(0, caretPosition)); 397 } 398 $.extend(hash, { caretPosition:caretPosition, scrollPosition:scrollPosition } ); 399 400 if (string.block !== selection && abort === false) { 401 insert(string.block); 402 set(start, len); 403 } else { 404 caretOffset = -1; 405 } 406 get(); 407 408 $.extend(hash, { line:'', selection:selection }); 409 410 // callbacks after insertion
411 if ((ctrlKey === true && shiftKey === true) || button.multiline === true) { 412 prepare(clicked.afterMultiInsert); 413 } 414 prepare(clicked.afterInsert); 415 prepare(options.afterInsert); 416 417 // refresh preview if opened 418 if (previewWindow && options.previewAutoRefresh) { 419 refreshPreview(); 420 } 421 422 // reinit keyevent 423 shiftKey = altKey = ctrlKey = abort = false; 424 } 425 426 // Substract linefeed in Opera 427 function fixOperaBug(string) { 428 if (browser.opera) { 429 return string.length - string.replace(/\n*/g, '').length; 430 } 431 return 0; 432 } 433 // Substract linefeed in IE 434 function fixIeBug(string) { 435 if (browser.msie) { 436 return string.length - string.replace(/\r*/g, '').length; 437 } 438 return 0; 439 } 440 441 // add markup 442 function insert(block) { 443 if (document.selection) { 444 var newSelection = document.selection.createRange(); 445 newSelection.text = block; 446 } else { 447 textarea.value = textarea.value.substring(0, caretPosition) + block + textarea.value.substring(caretPosition + selection.length, textarea.value.length); 448 } 449 } 450 451 // set a selection 452 function set(start, len) { 453 if (textarea.createTextRange){ 454 // quick fix to make it work on Opera 9.5 455 if (browser.opera && browser.version >= 9.5 && len == 0) { 456 return false; 457 } 458 range = textarea.createTextRange(); 459 range.collapse(true); 460 range.moveStart('character', start); 461 range.moveEnd('character', len); 462 range.select(); 463 } else if (textarea.setSelectionRange ){ 464 textarea.setSelectionRange(start, start + len); 465 } 466 textarea.scrollTop = scrollPosition; 467 textarea.focus(); 468 } 469 470 // get the selection 471 function get() { 472 textarea.focus(); 473 474 scrollPosition = textarea.scrollTop; 475 if (document.selection) { 476 selection = document.selection.createRange().text; 477 if (browser.msie) { // ie 478 var range = document.selection.createRange(), rangeCopy = range.duplicate(); 479 rangeCopy.moveToElementText(textarea); 480 caretPosition = -1; 481 while(rangeCopy.inRange(range)) { 482 rangeCopy.moveStart('character'); 483 caretPosition ++; 484 } 485 } else { // opera 486 caretPosition = textarea.selectionStart; 487 } 488 } else { // gecko & webkit 489 caretPosition = textarea.selectionStart; 490 491 selection = textarea.value.substring(caretPosition, textarea.selectionEnd); 492 } 493 return selection; 494 } 495 496 // open preview window 497 function preview() { 498 if (typeof options.previewHandler === 'function') { 499 previewWindow = true; 500 } else if (options.previewInElement) { 501 previewWindow = $(options.previewInElement); 502 } else if (!previewWindow || previewWindow.closed) { 503 if (options.previewInWindow) { 504 previewWindow = window.open('', 'preview', options.previewInWindow); 505 $(window).unload(function() { 506 previewWindow.close(); 507 }); 508 } else { 509 iFrame = $('<iframe class="markItUpPreviewFrame"></iframe>'); 510 if (options.previewPosition == 'after') { 511 iFrame.insertAfter(footer); 512 } else { 513 iFrame.insertBefore(header); 514 } 515 previewWindow = iFrame[iFrame.length - 1].contentWindow || frame[iFrame.length - 1]; 516 } 517 } else if (altKey === true) { 518 if (iFrame) { 519 iFrame.remove(); 520 } else { 521 previewWindow.close(); 522 } 523 previewWindow = iFrame = false; 524 } 525 if (!options.previewAutoRefresh) { 526 refreshPreview(); 527 } 528 if (options.previewInWindow) { 529 previewWindow.focus(); 530 } 531 } 532 533 // refresh Preview window 534 function refreshPreview() { 535 renderPreview(); 536 } 537 538 function renderPreview() { 539 var phtml; 540 if (options.previewHandler && typeof options.previewHandler === 'function') { 541 options.previewHandler( $$.val() ); 542 } else if (options.previewParser && typeof options.previewParser === 'function') { 543 var data = options.previewParser( $$.val() ); 544 writeInPreview(localize(data, 1) ); 545 } else if (options.previewParserPath !== '') { 546 $.ajax({ 547 type: 'POST', 548 dataType: 'text', 549 global: false, 550 url: options.previewParserPath, 551 data: options.previewParserVar+'='+encodeURIComponent($$.val()), 552 success: function(data) { 553 writeInPreview( localize(data, 1) ); 554 } 555 }); 556 } else { 557 if (!template) { 558 $.ajax({ 559 url: options.previewTemplatePath, 560 dataType: 'text', 561 global: false, 562 success: function(data) { 563 writeInPreview( localize(data, 1).replace(/<!-- content -->/g, $$.val()) ); 564 } 565 }); 566 } 567 } 568 return false;
569 } 570 571 function writeInPreview(data) { 572 if (options.previewInElement) { 573 $(options.previewInElement).html(data); 574 } else if (previewWindow && previewWindow.document) { 575 try { 576 sp = previewWindow.document.documentElement.scrollTop 577 } catch(e) { 578 sp = 0; 579 } 580 previewWindow.document.open(); 581 previewWindow.document.write(data); 582 previewWindow.document.close(); 583 previewWindow.document.documentElement.scrollTop = sp; 584 } 585 } 586 587 // set keys pressed 588 function keyPressed(e) { 589 shiftKey = e.shiftKey; 590 altKey = e.altKey; 591 ctrlKey = (!(e.altKey && e.ctrlKey)) ? (e.ctrlKey || e.metaKey) : false; 592 593 if (e.type === 'keydown') { 594 if (ctrlKey === true) { 595 li = $('a[accesskey="'+((e.keyCode == 13) ? '\\n' : String.fromCharCode(e.keyCode))+'"]', header).parent('li'); 596 if (li.length !== 0) { 597 ctrlKey = false; 598 setTimeout(function() { 599 li.triggerHandler('mouseup'); 600 },1); 601 return false; 602 } 603 } 604 if (e.keyCode === 13 || e.keyCode === 10) { // Enter key 605 if (ctrlKey === true) { // Enter + Ctrl 606 ctrlKey = false; 607 markup(options.onCtrlEnter); 608 return options.onCtrlEnter.keepDefault; 609 } else if (shiftKey === true) { // Enter + Shift 610 shiftKey = false; 611 markup(options.onShiftEnter); 612 return options.onShiftEnter.keepDefault; 613 } else { // only Enter 614 markup(options.onEnter); 615 return options.onEnter.keepDefault; 616 } 617 } 618 if (e.keyCode === 9) { // Tab key 619 if (shiftKey == true || ctrlKey == true || altKey == true) { 620 return false; 621 } 622 if (caretOffset !== -1) { 623 get(); 624 caretOffset = $$.val().length - caretOffset; 625 set(caretOffset, 0); 626 caretOffset = -1; 627 return false; 628 } else { 629 markup(options.onTab); 630 return options.onTab.keepDefault; 631 } 632 } 633 } 634 } 635 636 function remove() { 637 $$.unbind(".markItUp").removeClass('markItUpEditor'); 638 $$.parent('div').parent('div.markItUp').parent('div').replaceWith($$); 639 $$.data('markItUp', null); 640 } 641 642 init(); 643 }); 644 }; 645 646 $.fn.markItUpRemove = function() { 647 return this.each(function() { 648 $(this).markItUp('remove'); 649 } 650 ); 651 }; 652 653 $.markItUp = function(settings) { 654 var options = { target:false }; 655 $.extend(options, settings); 656 if (options.target) { 657 return $(options.target).each(function() { 658 $(this).focus(); 659 $(this).trigger('insertion', [options]); 660 }); 661 } else { 662 $('textarea').trigger('insertion', [options]); 663 } 664 }; 665})(jQuery);
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.