1/* 2 Implement Github like autocomplete mentions 3 http://ichord.github.com/At.js 4 5 Copyright (c) 2013 [email protected] 6 Licensed under the MIT license. 7*/ 8 9 10/* 11æ¬æä»¶æä½ textarea æè input å çæå ¥ç¬¦ 12åªå®ç°äºè·å¾æå ¥ç¬¦å¨ææ¬æ¡ä¸çä½ç½®ï¼æè®¾ç½® 13æå ¥ç¬¦çä½ç½®. 14*/ 15 16/** 17 * -------------------- 18 * Vanilla Forums NOTE: 19 * -------------------- 20 * 21 * This file has been highly modified to work with iframes, as well 22 * as custom username handling with quotation marks and spaces for Vanilla. 23 * Do not just replace with a more current version. At the time of 24 * development there was no support for iframes, or spaces in names. 25 * This may have changed, so if you do decide to upgrade this library, 26 * you're going to have to update the code that uses this library as well. 27 * It's all wrapped up in a function called `atCompleteInit`. 28 */ 29 30 31(function() { 32 (function(factory) { 33 if (typeof define === 'function' && define.amd) { 34 return define(['jquery'], factory); 35 } else { 36 return factory(window.jQuery); 37 } 38 })(function($) { 39 "use strict"; 40 ////var EditableCaret, InputCaret, Mirror, Utils, methods, pluginName; 41 var EditableCaret, InputCaret, Mirror, Utils, methods, pluginName, cWin; 42 43 pluginName = 'caret'; 44 EditableCaret = (function() { 45 function EditableCaret($inputor) { 46 this.$inputor = $inputor; 47 this.domInputor = this.$inputor[0]; 48 } 49 50 EditableCaret.prototype.setPos = function(pos) { 51 return this.domInputor; 52 }; 53 54 EditableCaret.prototype.getIEPosition = function() { 55 return $.noop(); 56 }; 57 58 EditableCaret.prototype.getPosition = function() { 59 return $.noop(); 60 }; 61 62 EditableCaret.prototype.getOldIEPos = function() { 63 var preCaretTextRange, textRange; 64 65 textRange = document.selection.createRange(); 66 preCaretTextRange = document.body.createTextRange(); 67 preCaretTextRange.moveToElementText(this.domInputor); 68 preCaretTextRange.setEndPoint("EndToEnd", textRange);
69 return preCaretTextRange.text.length; 70 }; 71 72 EditableCaret.prototype.getPos = function() { 73 var clonedRange, pos, range; 74 75 if (range = this.range()) { 76 clonedRange = range.cloneRange(); 77 clonedRange.selectNodeContents(this.domInputor); 78 clonedRange.setEnd(range.endContainer, range.endOffset); 79 pos = clonedRange.toString().length; 80 clonedRange.detach(); 81 return pos; 82 } else if (document.selection) { 83 return this.getOldIEPos(); 84 } 85 }; 86 87 EditableCaret.prototype.getOldIEOffset = function() { 88 var range, rect; 89 90 range = document.selection.createRange().duplicate(); 91 range.moveStart("character", -1); 92 rect = range.getBoundingClientRect(); 93 return { 94 height: rect.bottom - rect.top, 95 left: rect.left, 96 top: rect.top 97 }; 98 }; 99 100 EditableCaret.prototype.getOffset = function(pos) { 101 var clonedRange, offset, range, rect; 102 103 offset = null; 104 ////if (window.getSelection && (range = this.range())) { 105 if (cWin.getSelection && (range = this.range())) { 106 if (range.endOffset - 1 < 0) { 107 return null; 108 } 109 clonedRange = range.cloneRange(); 110 clonedRange.setStart(range.endContainer, range.endOffset - 1); 111 clonedRange.setEnd(range.endContainer, range.endOffset); 112 rect = clonedRange.getBoundingClientRect(); 113 offset = { 114 height: rect.height, 115 left: rect.left + rect.width, 116 top: rect.top 117 }; 118 clonedRange.detach(); 119 offset; 120 } else if (document.selection) { 121 this.getOldIEOffset(); 122 } 123 return Utils.adjustOffset(offset, this.$inputor); 124 }; 125 126 EditableCaret.prototype.range = function() { 127 var sel; 128 129 ////if (!window.getSelection) { 130 if (!cWin.getSelection) { 131 return; 132 } 133 ////sel = window.getSelection(); 134 sel = cWin.getSelection(); 135 if (sel.rangeCount > 0) { 136 return sel.getRangeAt(0); 137 } else { 138 return null; 139 } 140 }; 141 142 return EditableCaret; 143 144 })(); 145 InputCaret = (function() { 146 function InputCaret($inputor) { 147 this.$inputor = $inputor; 148 this.domInputor = this.$inputor[0]; 149 } 150 151 InputCaret.prototype.getIEPos = function() { 152 var endRange, inputor, len, normalizedValue, pos, range, textInputRange; 153 154 inputor = this.domInputor; 155 range = document.selection.createRange(); 156 pos = 0; 157 if (range && range.parentElement() === inputor) { 158 normalizedValue = inputor.value.replace(/\r\n/g, "\n"); 159 len = normalizedValue.length; 160 textInputRange = inputor.createTextRange(); 161 textInputRange.moveToBookmark(range.getBookmark()); 162 endRange = inputor.createTextRange(); 163 endRange.collapse(false); 164 if (textInputRange.compareEndPoints("StartToEnd", endRange) > -1) { 165 pos = len; 166 } else { 167 pos = -textInputRange.moveStart("character", -len); 168 } 169 } 170 return pos; 171 }; 172 173 InputCaret.prototype.getPos = function() { 174 if (document.selection) { 175 return this.getIEPos(); 176 } else { 177 return this.domInputor.selectionStart; 178 } 179 }; 180 181 InputCaret.prototype.setPos = function(pos) { 182 var inputor, range; 183 184 inputor = this.domInputor; 185 if (document.selection) { 186 range = inputor.createTextRange(); 187 range.move("character", pos); 188 range.select(); 189 } else if (inputor.setSelectionRange) { 190 inputor.setSelectionRange(pos, pos); 191 } 192 return inputor; 193 }; 194 195 InputCaret.prototype.getIEOffset = function(pos) { 196 var h, range, textRange, x, y; 197 198 textRange = this.domInputor.createTextRange(); 199 if (pos) { 200 textRange.move('character', pos); 201 } else { 202 range = document.selection.createRange(); 203 textRange.moveToBookmark(range.getBookmark()); 204 } 205 x = textRange.boundingLeft; 206 y = textRange.boundingTop; 207 h = textRange.boundingHeight; 208 return { 209 left: x, 210 top: y, 211 height: h 212 }; 213 }; 214 215 InputCaret.prototype.getOffset = function(pos) { 216 var $inputor, offset, position; 217 218 $inputor = this.$inputor; 219 if (document.selection) { 220 return Utils.adjustOffset(this.getIEOffset(pos), $inputor); 221 } else { 222 offset = $inputor.offset(); 223 position = this.getPosition(pos); 224 return offset = { 225 left: offset.left + position.left, 226 top: offset.top + position.top, 227 height: position.height 228 }; 229 } 230 }; 231 232 InputCaret.prototype.getPosition = function(pos) { 233 var $inputor, at_rect, format, html, mirror, start_range; 234 235 $inputor = this.$inputor; 236 format = function(value) { 237 return value.replace(/</g, '<').replace(/>/g, '>').replace(/`/g, '`').replace(/"/g, '"').replace(/\r\n|\r|\n/g, "<br />"); 238 }; 239 if (pos === void 0) { 240 pos = this.getPos(); 241 } 242 start_range = $inputor.val().slice(0, pos); 243 html = "<span>" + format(start_range) + "</span>"; 244 html += "<span id='caret'>|</span>"; 245 mirror = new Mirror($inputor); 246 return at_rect = mirror.create(html).rect(); 247 }; 248 249 InputCaret.prototype.getIEPosition = function(pos) { 250 var h, inputorOffset, offset, x, y; 251 252 offset = this.getIEOffset(pos); 253 inputorOffset = this.$inputor.offset(); 254 x = offset.left - inputorOffset.left; 255 y = offset.top - inputorOffset.top; 256 h = offset.height; 257 return { 258 left: x, 259 top: y, 260 height: h 261 }; 262 }; 263 264 return InputCaret; 265 266 })(); 267 Mirror = (function() { 268 Mirror.prototype.css_attr = ["overflowY", "height", "width", "paddingTop", "paddingLeft", "paddingRight", "paddingBottom", "marginTop", "marginLeft", "marginRight", "marginBottom", "fontFamily", "borderStyle", "borderWidth", "wordWrap", "fontSize", "lineHeight", "overflowX", "text-align"]; 269 270 function Mirror($inputor) { 271 this.$inputor = $inputor; 272 } 273 274 Mirror.prototype.mirrorCss = function() { 275 var css, 276 _this = this; 277 278 css = { 279 position: 'absolute', 280 left: -9999, 281 top: 0, 282 zIndex: -20000, 283 'white-space': 'pre-wrap' 284 }; 285 $.each(this.css_attr, function(i, p) { 286 return css[p] = _this.$inputor.css(p); 287 }); 288 return css; 289 }; 290 291 Mirror.prototype.create = function(html) { 292 this.$mirror = $('<div></div>'); 293 this.$mirror.css(this.mirrorCss()); 294 this.$mirror.html(html); 295 this.$inputor.after(this.$mirror); 296 return this; 297 }; 298 299 Mirror.prototype.rect = function() { 300 var $flag, pos, rect; 301 302 $flag = this.$mirror.find("#caret"); 303 pos = $flag.position(); 304 rect = { 305 left: pos.left, 306 top: pos.top, 307 height: $flag.height() 308 }; 309 this.$mirror.remove(); 310 return rect; 311 }; 312 313 return Mirror; 314 315 })(); 316 Utils = { 317 adjustOffset: function(offset, $inputor) { 318 if (!offset) { 319 return; 320 } 321 offset.top += $(window).scrollTop() + $inputor.scrollTop(); 322 offset.left += +$(window).scrollLeft() + $inputor.scrollLeft(); 323 return offset; 324 }, 325 contentEditable: function($inputor) { 326 return !!($inputor[0].contentEditable && $inputor[0].contentEditable === 'true'); 327 } 328 }; 329 methods = { 330 pos: function(pos) { 331 if (pos) { 332 return this.setPos(pos); 333 } else { 334 return this.getPos(); 335 } 336 }, 337 position: function(pos) { 338 if (document.selection) { 339 return this.getIEPosition(pos); 340 } else { 341 return this.getPosition(pos); 342 } 343 }, 344 offset: function(pos) { 345 return this.getOffset(pos); 346 } 347 }; 348 ////$.fn.caret = function(method) {
349 $.fn.caret = function(method, aWin) { 350 var caret; 351 352 //// 353 cWin = aWin; 354 355 caret = Utils.contentEditable(this) ? new EditableCaret(this) : new InputCaret(this); 356 if (methods[method]) { 357 ////return methods[method].apply(caret, Array.prototype.slice.call(arguments, 1)); 358 ///////return methods[method].apply(caret, Array.prototype.slice.call(arguments, method == 'pos' ? 2 : 1)); 359 return methods[method].apply(caret, Array.prototype.slice.call(arguments, 2)); 360 } else { 361 return $.error("Method " + method + " does not exist on jQuery.caret"); 362 } 363 }; 364 $.fn.caret.EditableCaret = EditableCaret; 365 $.fn.caret.InputCaret = InputCaret; 366 $.fn.caret.Utils = Utils; 367 return $.fn.caret.apis = methods; 368 }); 369 370}).call(this); 371 372 373/* 374 Implement Github like autocomplete mentions 375 http://ichord.github.com/At.js 376 377 Copyright (c) 2013 [email protected] 378 Licensed under the MIT license. 379*/ 380 381 382(function() { 383 var __slice = [].slice; 384 385 (function(factory) { 386 if (typeof define === 'function' && define.amd) { 387 return define(['jquery'], factory); 388 } else { 389 return factory(window.jQuery); 390 } 391 })(function($) { 392 var $CONTAINER, Api, App, Atwho, Controller, DEFAULT_CALLBACKS, KEY_CODE, Model, View; 393 App = (function() { 394 395 function App(inputor) { 396 this.current_flag = null; 397 this.controllers = {}; 398 this.$inputor = $(inputor); 399 this.listen(); 400 } 401 402 App.prototype.controller = function(at) { 403 return this.controllers[at || this.current_flag]; 404 }; 405 406 App.prototype.set_context_for = function(at) { 407 this.current_flag = at; 408 return this; 409 }; 410 411 App.prototype.reg = function(flag, setting) { 412 var controller, _base; 413 controller = (_base = this.controllers)[flag] || (_base[flag] = new Controller(this, flag)); 414 if (setting.alias) { 415 this.controllers[setting.alias] = controller; 416 } 417 controller.init(setting); 418 return this; 419 }; 420 421 App.prototype.listen = function() { 422 var _this = this; 423 return this.$inputor.on('keyup.atwho', function(e) { 424 return _this.on_keyup(e); 425 }).on('keydown.atwho', function(e) { 426 return _this.on_keydown(e); 427 }).on('scroll.atwho', function(e) { 428 var _ref; 429 return (_ref = _this.controller()) != null ? _ref.view.hide() : void 0; 430 }).on('blur.atwho', function(e) { 431 var c; 432 if (c = _this.controller()) { 433 return c.view.hide(c.get_opt("display_timeout")); 434 } 435 }); 436 }; 437 438 App.prototype.dispatch = function() { 439 var _this = this; 440 return $.map(this.controllers, function(c) { 441 if (c.look_up()) { 442 return _this.set_context_for(c.at); 443 } 444 }); 445 }; 446 447 App.prototype.on_keyup = function(e) { 448 var _ref; 449 switch (e.keyCode) { 450 case KEY_CODE.ESC: 451 case KEY_CODE.TAB: 452 case KEY_CODE.ENTER: 453 e.preventDefault(); 454 if ((_ref = this.controller()) != null) { 455 _ref.view.hide(); 456 } 457 break; 458 case KEY_CODE.DOWN: 459 case KEY_CODE.UP: 460 $.noop(); 461 break; 462 default: 463 this.dispatch(); 464 } 465 }; 466 467 App.prototype.on_keydown = function(e) { 468 var view, _ref; 469 view = (_ref = this.controller()) != null ? _ref.view : void 0; 470 if (!(view && view.visible())) { 471 return; 472 } 473 switch (e.keyCode) { 474 case KEY_CODE.ESC: 475 e.preventDefault(); 476 view.hide(); 477 break; 478 case KEY_CODE.UP: 479 e.preventDefault(); 480 view.prev(); 481 break; 482 case KEY_CODE.DOWN: 483 e.preventDefault(); 484 view.next(); 485 break; 486 case KEY_CODE.TAB: 487 case KEY_CODE.ENTER: 488 if (!view.visible()) { 489 return; 490 } 491 view.choose(e); 492 break; 493 default: 494 $.noop(); 495 } 496 }; 497 498 return App; 499 500 })(); 501 Controller = (function() { 502 var uuid, _uuid; 503 504 _uuid = 0; 505 506 uuid = function() { 507 return _uuid += 1; 508 }; 509 510 function Controller(app, at) { 511 this.app = app; 512 this.at = at; 513 this.$inputor = this.app.$inputor; 514 this.id = this.$inputor[0].id || uuid(); 515 this.setting = null; 516 this.query = null; 517 this.pos = 0; 518 this.cur_rect = null; 519 this.range = null; 520 $CONTAINER.append(this.$el = $("<div id='atwho-ground-" + this.id + "'></div>")); 521 this.model = new Model(this); 522 this.view = new View(this); 523 } 524 525 Controller.prototype.init = function(setting) { 526 this.setting = $.extend({}, this.setting || $.fn.atwho["default"], setting); 527 this.view.init(); 528 return this.model.reload(this.setting.data); 529 }; 530 531 Controller.prototype.call_default = function() { 532 var args, func_name; 533 func_name = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : []; 534 try { 535 return DEFAULT_CALLBACKS[func_name].apply(this, args); 536 } catch (error) { 537 return $.error("" + error + " Or maybe At.js doesn't have function " + func_name); 538 } 539 }; 540 541 Controller.prototype.trigger = function(name, data) { 542 var alias, event_name; 543 data.push(this); 544 alias = this.get_opt('alias'); 545 event_name = alias ? "" + name + "-" + alias + ".atwho" : "" + name + ".atwho"; 546 return this.$inputor.trigger(event_name, data); 547 }; 548 549 Controller.prototype.callbacks = function(func_name) { 550 return this.get_opt("callbacks")[func_name] || DEFAULT_CALLBACKS[func_name]; 551 }; 552 553 Controller.prototype.get_opt = function(at, default_value) { 554 try { 555 return this.setting[at]; 556 } catch (e) { 557 return null; 558 } 559 }; 560 561 Controller.prototype.content = function() { 562 var result = { 563 content: null, 564 offset: 0 565 }; 566 567 if (this.$inputor.is('textarea, input')) { 568 result.content = this.$inputor.val(); 569 } else { 570 var textNode = $(document.createElement('div')); 571 var html = this.$inputor.html(); 572 var breaks = /<br(\s+)?(\/)?>/g; 573 result.offset = html.match(breaks) ? html.match(breaks).length : 0; 574 textNode.html(html.replace(breaks, "\n")); 575 result.content = textNode.text(); 576 } 577 578 return result; 579 }; 580 581 Controller.prototype.catch_query = function() { 582 var caret_pos, contents, end, query, start, subtext; 583 contents = this.content(); 584 ////caret_pos = this.$inputor.caret('pos'); 585 caret_pos = this.$inputor.caret('pos', this.setting.cWindow) + contents.offset; 586 subtext = contents.content.slice(0, caret_pos); 587 query = this.callbacks("matcher").call(this, this.at, subtext, this.get_opt('start_with_space')); 588 if (typeof query === "string" && query.length <= this.get_opt('max_len', 20)) { 589 start = caret_pos - query.length; 590 end = start + query.length; 591 this.pos = start; 592 query = { 593 'text': query.toLowerCase(), 594 'head_pos': start, 595 'end_pos': end 596 }; 597 this.trigger("matched", [this.at, query.text]); 598 } else { 599 this.view.hide(); 600 } 601 return this.query = query; 602 }; 603 604 Controller.prototype.rect = function() { 605 var c, scale_bottom; 606 /////if (!(c = this.$inputor.caret('offset', this.pos - 1))) { 607 if (!(c = this.$inputor.caret('offset', this.setting.cWindow, this.pos - 1))) { 608 return; 609 } 610 if (this.$inputor.attr('contentEditable') === 'true') { 611 c = (this.cur_rect || (this.cur_rect = c)) || c; 612 } 613 scale_bottom = document.selection ? 0 : 2; 614 return { 615 left: c.left, 616 top: c.top, 617 bottom: c.top + c.height + scale_bottom 618 }; 619 }; 620 621 Controller.prototype.reset_rect = function() { 622 if (this.$inputor.attr('contentEditable') === 'true') { 623 return this.cur_rect = null; 624 } 625 }; 626 627 Controller.prototype.mark_range = function() { 628 return this.range = this.get_range() || this.get_ie_range(); 629 }; 630 631 Controller.prototype.clear_range = function() { 632 return this.range = null; 633 }; 634 635 Controller.prototype.get_range = function() { 636 ////return this.range || (window.getSelection ? window.getSelection().getRangeAt(0) : void 0); 637 638 639 var thisWin = this.setting.cWindow; 640 //////return this.range || (thisWin.getSelection ? thisWin.getSelection().getRangeAt(0) : void 0); 641 return thisWin.getSelection ? thisWin.getSelection().getRangeAt(0) : (this.range || void 0); 642 643 }; 644 645 Controller.prototype.get_ie_range = function() { 646 return this.range || (document.selection ? document.selection.createRange() : void 0); 647 }; 648 649 Controller.prototype.insert_content_for = function($li) { 650 var data, data_value, tpl; 651 data_value = $li.data('value'); 652 tpl = this.get_opt('insert_tpl'); 653 if (this.$inputor.is('textarea, input') || !tpl) { 654 return data_value; 655 } 656 data = $.extend({}, $li.data('item-data'), { 657 'atwho-data-value': data_value, 658 'atwho-at': this.at 659 }); 660 return this.callbacks("tpl_eval").call(this, tpl, data); 661 }; 662 663 Controller.prototype.insert = function(content, $li) { 664 ////var $inputor, $insert_node, class_name, content_node, insert_node, pos, range, sel, source, start_str, text; 665
666 //////////var $inputor, $insert_node, class_name, content_node, insert_node, pos, range, sel, source, start_str, text, thisWin; 667 var $inputor, $insert_node, class_name, content_editable, content_node, insert_node, pos, range, sel, source, start_str, text; 668 $inputor = this.$inputor; 669 670 if ($inputor.attr('contentEditable') === 'true') { 671 ////////// 672 content_editable = '' + /firefox/i.test(navigator.userAgent); 673 674 //class_name = "atwho-view-flag atwho-view-flag-" + (this.get_opt('alias') || this.at); 675 class_name = "vanilla-mention-" + (this.get_opt('alias') || this.at); 676 //////////content_node = "" + content + "<span contenteditable='false'> <span>"; 677 //////////insert_node = "<span contenteditable='false' class='" + class_name + "'>" + content_node + "</span>"; 678 //content_node = ("" + content + "<span contenteditable='") + content_editable + "'> <span>"; 679 content_node = "" + content + " "; 680 //content_node = "" + content; 681 682 //insert_node = "<span contenteditable='" + content_editable + ("' class='" + class_name + "'>" + content_node + "</span>"); 683 insert_node = '<span class="' + class_name + '">' + content_node + '</span>'; 684 685 $insert_node = $(insert_node).data('atwho-data-item', $li.data('item-data')); 686 if (document.selection) { 687 $insert_node = $("<span contenteditable='true'></span>").html($insert_node); 688 } 689 } 690 691 if ($inputor.is('textarea, input')) { 692 content = '' + content; 693 source = $inputor.val(); 694 start_str = source.slice(0, Math.max(this.query.head_pos - this.at.length, 0)); 695 text = "" + start_str + content + " " + (source.slice(this.query['end_pos'] || 0)); 696 $inputor.val(text); 697 ////$inputor.caret('pos', start_str.length + content.length + 1); 698 $inputor.caret('pos', this.setting.cWindow, start_str.length + content.length + 1); 699 } else if (range = this.get_range()) { 700 //// 701 thisWin = this.setting.cWindow; 702 703 pos = range.startOffset - (this.query.end_pos - this.query.head_pos) - this.at.length; 704 range.setStart(range.endContainer, Math.max(pos, 0)); 705 range.setEnd(range.endContainer, range.endOffset); 706 range.deleteContents(); 707 range.insertNode(document.createTextNode(content + " ")); 708 //range.insertNode($insert_node[0]); 709 range.collapse(false); 710 711 ////sel = window.getSelection(); 712 sel = thisWin.getSelection(); 713 714 sel.removeAllRanges(); 715 sel.addRange(range); 716 } else if (range = this.get_ie_range()) { 717 range.moveStart('character', this.query.end_pos - this.query.head_pos - this.at.length);
718 /////// 719 range.pasteHTML($insert_node[0]); 720 range.collapse(false); 721 range.select(); 722 } 723 $inputor.focus(); 724 return $inputor.change(); 725 }; 726 727 Controller.prototype.render_view = function(data) { 728 var search_key; 729 search_key = this.get_opt("search_key"); 730 data = this.callbacks("sorter").call(this, this.query.text, data.slice(0, 1001), search_key); 731 return this.view.render(data.slice(0, this.get_opt('limit'))); 732 }; 733 734 Controller.prototype.look_up = function() { 735 var query, _callback; 736 if (!(query = this.catch_query())) { 737 return; 738 } 739 _callback = function(data) { 740 if (data && data.length > 0) { 741 return this.render_view(data); 742 } else { 743 return this.view.hide(); 744 } 745 }; 746 this.model.query(query.text, $.proxy(_callback, this)); 747 return query; 748 }; 749 750 return Controller; 751 752 })(); 753 Model = (function() { 754 var _storage; 755 756 _storage = {}; 757 758 function Model(context) { 759 this.context = context; 760 this.at = this.context.at; 761 } 762 763 Model.prototype.saved = function() { 764 return this.fetch() > 0; 765 }; 766 767 Model.prototype.query = function(query, callback) { 768 var data, search_key, _ref; 769 data = this.fetch(); 770 search_key = this.context.get_opt("search_key"); 771 callback(data = this.context.callbacks('filter').call(this.context, query, data, search_key)); 772 if (!(data && data.length > 0)) { 773 return (_ref = this.context.callbacks('remote_filter')) != null ? _ref.call(this.context, query, callback) : void 0; 774 } 775 }; 776 777 Model.prototype.fetch = function() { 778 return _storage[this.at] || []; 779 }; 780 781 Model.prototype.save = function(data) { 782 return _storage[this.at] = this.context.callbacks("before_save").call(this.context, data || []); 783 }; 784 785 Model.prototype.load = function(data) { 786 if (!(this.saved() || !data)) { 787 return this._load(data); 788 } 789 }; 790 791 Model.prototype.reload = function(data) { 792 return this._load(data); 793 }; 794 795 Model.prototype._load = function(data) { 796 var _this = this; 797 if (typeof data === "string") { 798 return $.ajax(data, { 799 dataType: "json" 800 }).done(function(data) { 801 return _this.save(data); 802 }); 803 } else { 804 return this.save(data); 805 } 806 }; 807 808 return Model; 809 810 })(); 811 View = (function() { 812 813 function View(context) { 814 this.context = context; 815 this.$el = $("<div class='atwho-view'><ul class='atwho-view-ul'></ul></div>"); 816 this.timeout_id = null; 817 this.context.$el.append(this.$el); 818 this.bind_event(); 819 } 820 821 View.prototype.init = function() { 822 var id; 823 id = this.context.get_opt("alias") || this.context.at.charCodeAt(0); 824 return this.$el.attr({ 825 'id': "at-view-" + id 826 }); 827 }; 828 829 View.prototype.bind_event = function() { 830 var $menu, 831 _this = this; 832 $menu = this.$el.find('ul'); 833 $menu.on('mouseenter.atwho-view', 'li', function(e) { 834 $menu.find('.cur').removeClass('cur'); 835 return $(e.currentTarget).addClass('cur'); 836 }).on('click', function(e) { 837 _this.choose(e); 838 return e.preventDefault(); 839 }); 840 return this.$el.on('mouseenter.atwho-view', 'ul', function(e) { 841 return _this.context.mark_range(); 842 }).on('mouseleave.atwho-view', 'ul', function(e) { 843 return _this.context.clear_range(); 844 }); 845 }; 846 847 View.prototype.visible = function() { 848 return this.$el.is(":visible"); 849 }; 850 851 View.prototype.choose = function(event) { 852 var $li, content; 853 if (($li = this.$el.find(".cur")).length) { 854 event.preventDefault(); 855 content = this.context.insert_content_for($li); 856 this.context.insert(this.context.callbacks("before_insert").call(this.context, content, $li), $li); 857 this.context.trigger("inserted", [$li]); 858 return this.hide(); 859 } 860 }; 861 862 View.prototype.reposition = function(rect) { 863 ////var offset; 864 ////if (rect.bottom + this.$el.height() - $(window).scrollTop() > $(window).height()) { 865 866 var offset; 867 868 //// 869 // Make sure the non-iframe version still references window. 870 var thisWin = (this.context.setting.cWindow) 871 ? null 872 : window; 873 874 if (rect.bottom + this.$el.height() - $(thisWin).scrollTop() > $(thisWin).height()) { 875 rect.bottom = rect.top - this.$el.height(); 876 } 877 offset = { 878 left: rect.left, 879 top: rect.bottom 880 }; 881 this.$el.offset(offset); 882 return this.context.trigger("reposition", [offset]); 883 }; 884 885 View.prototype.next = function() { 886 var cur, next; 887 cur = this.$el.find('.cur').removeClass('cur'); 888 next = cur.next(); 889 if (!next.length) { 890 next = this.$el.find('li:first'); 891 } 892 return next.addClass('cur'); 893 }; 894 895 View.prototype.prev = function() { 896 var cur, prev; 897 cur = this.$el.find('.cur').removeClass('cur'); 898 prev = cur.prev(); 899 if (!prev.length) { 900 prev = this.$el.find('li:last'); 901 } 902 return prev.addClass('cur'); 903 }; 904 905 View.prototype.show = function() { 906 var rect; 907 if (!this.visible()) { 908 this.$el.show(); 909 } 910 if (rect = this.context.rect()) { 911 return this.reposition(rect); 912 } 913 }; 914 915 View.prototype.hide = function(time) { 916 var callback, 917 _this = this; 918 if (isNaN(time && this.visible())) { 919 this.context.reset_rect(); 920 return this.$el.hide(); 921 } else { 922 callback = function() { 923 return _this.hide(); 924 }; 925 clearTimeout(this.timeout_id); 926 return this.timeout_id = setTimeout(callback, time); 927 } 928 }; 929 930 View.prototype.render = function(list) { 931 var $li, $ul, item, li, tpl, _i, _len; 932 if (!$.isArray(list || list.length <= 0)) { 933 this.hide(); 934 return; 935 } 936 this.$el.find('ul').empty(); 937 $ul = this.$el.find('ul'); 938 tpl = this.context.get_opt('tpl'); 939 for (_i = 0, _len = list.length; _i < _len; _i++) { 940 item = list[_i]; 941 item = $.extend({}, item, { 942 'atwho-at': this.context.at 943 }); 944 li = this.context.callbacks("tpl_eval").call(this.context, tpl, item); 945 $li = $(this.context.callbacks("highlighter").call(this.context, li, this.context.query.text)); 946 $li.data("item-data", item); 947 $ul.append($li); 948 } 949 this.show(); 950 return $ul.find("li:first").addClass("cur"); 951 }; 952 953 return View; 954 955 })(); 956 KEY_CODE = { 957 DOWN: 40, 958 UP: 38, 959 ESC: 27, 960 TAB: 9, 961 ENTER: 13 962 }; 963 DEFAULT_CALLBACKS = { 964 before_save: function(data) { 965 var item, _i, _len, _results; 966 if (!$.isArray(data)) { 967 return data; 968 } 969 _results = []; 970 for (_i = 0, _len = data.length; _i < _len; _i++) { 971 item = data[_i]; 972 if ($.isPlainObject(item)) { 973 _results.push(item); 974 } else { 975 _results.push({ 976 name: item 977 }); 978 } 979 } 980 return _results; 981 }, 982 matcher: function(flag, subtext, should_start_with_space) { 983 var match, regexp; 984 flag = flag.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); 985 if (should_start_with_space) { 986 flag = '(?:^|\\s)' + flag; 987 } 988 regexp = new RegExp(flag + '([A-Za-z0-9_\+\-]*)$|' + flag + '([^\\x00-\\xff]*)$', 'gi'); 989 match = regexp.exec(subtext); 990 if (match) { 991 return match[2] || match[1]; 992 } else { 993 return null; 994 } 995 }, 996 filter: function(query, data, search_key) { 997 var item, _i, _len, _results; 998 _results = []; 999 for (_i = 0, _len = data.length; _i < _len; _i++) { 1000 item = data[_i]; 1001 if (~item[search_key].toLowerCase().indexOf(query)) { 1002 _results.push(item); 1003 } 1004 } 1005 return _results; 1006 }, 1007 remote_filter: null, 1008 sorter: function(query, items, search_key) { 1009 var item, _i, _len, _results; 1010 if (!query) { 1011 return items; 1012 } 1013 _results = []; 1014 for (_i = 0, _len = items.length; _i < _len; _i++) { 1015 item = items[_i]; 1016 item.atwho_order = item[search_key].toLowerCase().indexOf(query); 1017 if (item.atwho_order > -1) { 1018 _results.push(item); 1019 } 1020 } 1021 return _results.sort(function(a, b) { 1022 return a.atwho_order - b.atwho_order; 1023 }); 1024 }, 1025 tpl_eval: function(tpl, map) { 1026 try { 1027 return tpl.replace(/\$\{([^\}]*)\}/g, function(tag, key, pos) { 1028 return map[key]; 1029 }); 1030 } catch (error) { 1031 return ""; 1032 } 1033 },
1034 highlighter: function(li, query) { 1035 var regexp; 1036 if (!query) { 1037 return li; 1038 } 1039 regexp = new RegExp(">\\s*(\\w*)(" + query.replace("+", "\\+") + ")(\\w*)\\s*<", 'ig'); 1040 return li.replace(regexp, function(str, $1, $2, $3) { 1041 return '> ' + $1 + '<strong>' + $2 + '</strong>' + $3 + ' <'; 1042 }); 1043 }, 1044 before_insert: function(value, $li) { 1045 return value; 1046 } 1047 }; 1048 Api = { 1049 load: function(at, data) { 1050 var c; 1051 if (c = this.controller(at)) { 1052 return c.model.load(data); 1053 } 1054 }, 1055 getInsertedItemsWithIDs: function(at) { 1056 var c, ids, items; 1057 if (!(c = this.controller(at))) { 1058 return [null, null]; 1059 } 1060 if (at) { 1061 at = "-" + (c.get_opt('alias') || c.at); 1062 } 1063 ids = []; 1064 items = $.map(this.$inputor.find("span.atwho-view-flag" + (at || "")), function(item) { 1065 var data; 1066 data = $(item).data('atwho-data-item'); 1067 if (ids.indexOf(data.id) > -1) { 1068 return; 1069 } 1070 if (data.id) { 1071 ids.push = data.id; 1072 } 1073 return data; 1074 }); 1075 return [ids, items]; 1076 }, 1077 getInsertedItems: function(at) { 1078 return Api.getInsertedItemsWithIDs.apply(this, [at])[1]; 1079 }, 1080 getInsertedIDs: function(at) { 1081 return Api.getInsertedItemsWithIDs.apply(this, [at])[0]; 1082 }, 1083 run: function() { 1084 return this.dispatch(); 1085 } 1086 }; 1087 Atwho = { 1088 init: function(options) { 1089 var $this, app; 1090 app = ($this = $(this)).data("atwho"); 1091 if (!app) { 1092 $this.data('atwho', (app = new App(this))); 1093 } 1094 app.reg(options.at, options); 1095 return this; 1096 } 1097 }; 1098 $CONTAINER = $("<div id='atwho-container'></div>"); 1099 $.fn.atwho = function(method) { 1100 var result, _args; 1101 _args = arguments; 1102 $('body').append($CONTAINER); 1103 result = null; 1104 1105 this.filter('textarea, input, [contenteditable=true]').each(function() { 1106 1107 //console.log('at inloop'); 1108 //console.log(this); 1109 1110 var app; 1111 if (typeof method === 'object' || !method) { 1112 return Atwho.init.apply(this, _args); 1113 } else if (Api[method]) { 1114 if (app = $(this).data('atwho')) { 1115 return result = Api[method].apply(app, Array.prototype.slice.call(_args, 1)); 1116 } 1117 } else { 1118 return $.error("Method " + method + " does not exist on jQuery.caret"); 1119 } 1120 }); 1121 return result || this; 1122 }; 1123 return $.fn.atwho["default"] = { 1124 at: void 0, 1125 alias: void 0, 1126 data: null, 1127 tpl: "<li data-value='${atwho-at}${name}'>${name}</li>", 1128 insert_tpl: "<span>${atwho-data-value}</span>", 1129 callbacks: DEFAULT_CALLBACKS, 1130 search_key: "name", 1131 start_with_space: true, 1132 limit: 5, 1133 max_len: 20, 1134 display_timeout: 300, 1135 //// 1136 cWindow: window 1137 }; 1138 }); 1139 1140}).call(this);
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.