ichord-At.js-mod

Add Github like mentions autocomplete to your application.

此腳本不應該直接安裝,它是一個供其他腳本使用的函式庫。欲使用本函式庫,請在腳本 metadata 寫上: // @require https://update.gf.qytechs.cn/scripts/16996/138632/ichord-Atjs-mod.js

  1. /**
  2. * at.js - 1.5.1-mod (mod by Mottie @line 386)
  3. * Copyright (c) 2016 chord.luo <chord.luo@gmail.com>;
  4. * Homepage: http://ichord.github.com/At.js
  5. * License: MIT
  6. */
  7. (function (root, factory) {
  8. if (typeof define === 'function' && define.amd) {
  9. // AMD. Register as an anonymous module unless amdModuleId is set
  10. define(["jquery"], function (a0) {
  11. return (factory(a0));
  12. });
  13. } else if (typeof exports === 'object') {
  14. // Node. Does not work with strict CommonJS, but
  15. // only CommonJS-like environments that support module.exports,
  16. // like Node.
  17. module.exports = factory(require("jquery"));
  18. } else {
  19. factory(jQuery);
  20. }
  21. }(this, function ($) {
  22. var DEFAULT_CALLBACKS, KEY_CODE;
  23.  
  24. KEY_CODE = {
  25. DOWN: 40,
  26. UP: 38,
  27. ESC: 27,
  28. TAB: 9,
  29. ENTER: 13,
  30. CTRL: 17,
  31. A: 65,
  32. P: 80,
  33. N: 78,
  34. LEFT: 37,
  35. UP: 38,
  36. RIGHT: 39,
  37. DOWN: 40,
  38. BACKSPACE: 8,
  39. SPACE: 32
  40. };
  41.  
  42. DEFAULT_CALLBACKS = {
  43. beforeSave: function(data) {
  44. return Controller.arrayToDefaultHash(data);
  45. },
  46. matcher: function(flag, subtext, should_startWithSpace, acceptSpaceBar) {
  47. var _a, _y, match, regexp, space;
  48. flag = flag.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
  49. if (should_startWithSpace) {
  50. flag = '(?:^|\\s)' + flag;
  51. }
  52. _a = decodeURI("%C3%80");
  53. _y = decodeURI("%C3%BF");
  54. space = acceptSpaceBar ? "\ " : "";
  55. regexp = new RegExp(flag + "([A-Za-z" + _a + "-" + _y + "0-9_" + space + "\'\.\+\-]*)$|" + flag + "([^\\x00-\\xff]*)$", 'gi');
  56. match = regexp.exec(subtext);
  57. if (match) {
  58. return match[2] || match[1];
  59. } else {
  60. return null;
  61. }
  62. },
  63. filter: function(query, data, searchKey) {
  64. var _results, i, item, len;
  65. _results = [];
  66. for (i = 0, len = data.length; i < len; i++) {
  67. item = data[i];
  68. if (~new String(item[searchKey]).toLowerCase().indexOf(query.toLowerCase())) {
  69. _results.push(item);
  70. }
  71. }
  72. return _results;
  73. },
  74. remoteFilter: null,
  75. sorter: function(query, items, searchKey) {
  76. var _results, i, item, len;
  77. if (!query) {
  78. return items;
  79. }
  80. _results = [];
  81. for (i = 0, len = items.length; i < len; i++) {
  82. item = items[i];
  83. item.atwho_order = new String(item[searchKey]).toLowerCase().indexOf(query.toLowerCase());
  84. if (item.atwho_order > -1) {
  85. _results.push(item);
  86. }
  87. }
  88. return _results.sort(function(a, b) {
  89. return a.atwho_order - b.atwho_order;
  90. });
  91. },
  92. tplEval: function(tpl, map) {
  93. var error, error1, template;
  94. template = tpl;
  95. try {
  96. if (typeof tpl !== 'string') {
  97. template = tpl(map);
  98. }
  99. return template.replace(/\$\{([^\}]*)\}/g, function(tag, key, pos) {
  100. return map[key];
  101. });
  102. } catch (error1) {
  103. error = error1;
  104. return "";
  105. }
  106. },
  107. highlighter: function(li, query) {
  108. var regexp;
  109. if (!query) {
  110. return li;
  111. }
  112. regexp = new RegExp(">\\s*(\\w*?)(" + query.replace("+", "\\+") + ")(\\w*)\\s*<", 'ig');
  113. return li.replace(regexp, function(str, $1, $2, $3) {
  114. return '> ' + $1 + '<strong>' + $2 + '</strong>' + $3 + ' <';
  115. });
  116. },
  117. beforeInsert: function(value, $li, e) {
  118. return value;
  119. },
  120. beforeReposition: function(offset) {
  121. return offset;
  122. },
  123. afterMatchFailed: function(at, el) {}
  124. };
  125.  
  126. var App;
  127.  
  128. App = (function() {
  129. function App(inputor) {
  130. this.currentFlag = null;
  131. this.controllers = {};
  132. this.aliasMaps = {};
  133. this.$inputor = $(inputor);
  134. this.setupRootElement();
  135. this.listen();
  136. }
  137.  
  138. App.prototype.createContainer = function(doc) {
  139. var ref;
  140. if ((ref = this.$el) != null) {
  141. ref.remove();
  142. }
  143. return $(doc.body).append(this.$el = $("<div class='atwho-container'></div>"));
  144. };
  145.  
  146. App.prototype.setupRootElement = function(iframe, asRoot) {
  147. var error, error1;
  148. if (asRoot == null) {
  149. asRoot = false;
  150. }
  151. if (iframe) {
  152. this.window = iframe.contentWindow;
  153. this.document = iframe.contentDocument || this.window.document;
  154. this.iframe = iframe;
  155. } else {
  156. this.document = this.$inputor[0].ownerDocument;
  157. this.window = this.document.defaultView || this.document.parentWindow;
  158. try {
  159. this.iframe = this.window.frameElement;
  160. } catch (error1) {
  161. error = error1;
  162. this.iframe = null;
  163. if ($.fn.atwho.debug) {
  164. throw new Error("iframe auto-discovery is failed.\nPlease use `setIframe` to set the target iframe manually.\n" + error);
  165. }
  166. }
  167. }
  168. return this.createContainer((this.iframeAsRoot = asRoot) ? this.document : document);
  169. };
  170.  
  171. App.prototype.controller = function(at) {
  172. var c, current, currentFlag, ref;
  173. if (this.aliasMaps[at]) {
  174. current = this.controllers[this.aliasMaps[at]];
  175. } else {
  176. ref = this.controllers;
  177. for (currentFlag in ref) {
  178. c = ref[currentFlag];
  179. if (currentFlag === at) {
  180. current = c;
  181. break;
  182. }
  183. }
  184. }
  185. if (current) {
  186. return current;
  187. } else {
  188. return this.controllers[this.currentFlag];
  189. }
  190. };
  191.  
  192. App.prototype.setContextFor = function(at) {
  193. this.currentFlag = at;
  194. return this;
  195. };
  196.  
  197. App.prototype.reg = function(flag, setting) {
  198. var base, controller;
  199. controller = (base = this.controllers)[flag] || (base[flag] = this.$inputor.is('[contentEditable]') ? new EditableController(this, flag) : new TextareaController(this, flag));
  200. if (setting.alias) {
  201. this.aliasMaps[setting.alias] = flag;
  202. }
  203. controller.init(setting);
  204. return this;
  205. };
  206.  
  207. App.prototype.listen = function() {
  208. return this.$inputor.on('compositionstart', (function(_this) {
  209. return function(e) {
  210. var ref;
  211. if ((ref = _this.controller()) != null) {
  212. ref.view.hide();
  213. }
  214. _this.isComposing = true;
  215. return null;
  216. };
  217. })(this)).on('compositionend', (function(_this) {
  218. return function(e) {
  219. _this.isComposing = false;
  220. setTimeout(function(e) {
  221. return _this.dispatch(e);
  222. });
  223. return null;
  224. };
  225. })(this)).on('keyup.atwhoInner', (function(_this) {
  226. return function(e) {
  227. return _this.onKeyup(e);
  228. };
  229. })(this)).on('keydown.atwhoInner', (function(_this) {
  230. return function(e) {
  231. return _this.onKeydown(e);
  232. };
  233. })(this)).on('blur.atwhoInner', (function(_this) {
  234. return function(e) {
  235. var c;
  236. if (c = _this.controller()) {
  237. c.expectedQueryCBId = null;
  238. return c.view.hide(e, c.getOpt("displayTimeout"));
  239. }
  240. };
  241. })(this)).on('click.atwhoInner', (function(_this) {
  242. return function(e) {
  243. return _this.dispatch(e);
  244. };
  245. })(this)).on('scroll.atwhoInner', (function(_this) {
  246. return function() {
  247. var lastScrollTop;
  248. lastScrollTop = _this.$inputor.scrollTop();
  249. return function(e) {
  250. var currentScrollTop, ref;
  251. currentScrollTop = e.target.scrollTop;
  252. if (lastScrollTop !== currentScrollTop) {
  253. if ((ref = _this.controller()) != null) {
  254. ref.view.hide(e);
  255. }
  256. }
  257. lastScrollTop = currentScrollTop;
  258. return true;
  259. };
  260. };
  261. })(this)());
  262. };
  263.  
  264. App.prototype.shutdown = function() {
  265. var _, c, ref;
  266. ref = this.controllers;
  267. for (_ in ref) {
  268. c = ref[_];
  269. c.destroy();
  270. delete this.controllers[_];
  271. }
  272. this.$inputor.off('.atwhoInner');
  273. return this.$el.remove();
  274. };
  275.  
  276. App.prototype.dispatch = function(e) {
  277. var _, c, ref, results;
  278. ref = this.controllers;
  279. results = [];
  280. for (_ in ref) {
  281. c = ref[_];
  282. results.push(c.lookUp(e));
  283. }
  284. return results;
  285. };
  286.  
  287. App.prototype.onKeyup = function(e) {
  288. var ref;
  289. switch (e.keyCode) {
  290. case KEY_CODE.ESC:
  291. e.preventDefault();
  292. if ((ref = this.controller()) != null) {
  293. ref.view.hide();
  294. }
  295. break;
  296. case KEY_CODE.DOWN:
  297. case KEY_CODE.UP:
  298. case KEY_CODE.CTRL:
  299. case KEY_CODE.ENTER:
  300. $.noop();
  301. break;
  302. case KEY_CODE.P:
  303. case KEY_CODE.N:
  304. if (!e.ctrlKey) {
  305. this.dispatch(e);
  306. }
  307. break;
  308. default:
  309. this.dispatch(e);
  310. }
  311. };
  312.  
  313. App.prototype.onKeydown = function(e) {
  314. var ref, view;
  315. view = (ref = this.controller()) != null ? ref.view : void 0;
  316. if (!(view && view.visible())) {
  317. return;
  318. }
  319. switch (e.keyCode) {
  320. case KEY_CODE.ESC:
  321. e.preventDefault();
  322. view.hide(e);
  323. break;
  324. case KEY_CODE.UP:
  325. e.preventDefault();
  326. view.prev();
  327. break;
  328. case KEY_CODE.DOWN:
  329. e.preventDefault();
  330. view.next();
  331. break;
  332. case KEY_CODE.P:
  333. if (!e.ctrlKey) {
  334. return;
  335. }
  336. e.preventDefault();
  337. view.prev();
  338. break;
  339. case KEY_CODE.N:
  340. if (!e.ctrlKey) {
  341. return;
  342. }
  343. e.preventDefault();
  344. view.next();
  345. break;
  346. case KEY_CODE.TAB:
  347. case KEY_CODE.ENTER:
  348. case KEY_CODE.SPACE:
  349. if (!view.visible()) {
  350. return;
  351. }
  352. if (!this.controller().getOpt('spaceSelectsMatch') && e.keyCode === KEY_CODE.SPACE) {
  353. return;
  354. }
  355. if (!this.controller().getOpt('tabSelectsMatch') && e.keyCode === KEY_CODE.TAB) {
  356. return;
  357. }
  358. if (view.highlighted()) {
  359. e.preventDefault();
  360. view.choose(e);
  361. } else {
  362. view.hide(e);
  363. }
  364. break;
  365. default:
  366. $.noop();
  367. }
  368. };
  369.  
  370. return App;
  371.  
  372. })();
  373.  
  374. var Controller,
  375. slice = [].slice;
  376.  
  377. Controller = (function() {
  378. Controller.prototype.uid = function() {
  379. return (Math.random().toString(16) + "000000000").substr(2, 8) + (new Date().getTime());
  380. };
  381.  
  382. function Controller(app, at1) {
  383. this.app = app;
  384. this.at = at1;
  385. this.$inputor = this.app.$inputor;
  386. /* ==== START MOD ==== */
  387. /* replace whitespace and symbols in element ID - bypass GitHub bug
  388. goto: https://github.com/jquery/jquery/pull/2916/files & in the console
  389. enter $('[id*="new_inline_comment_diff"]')... the id has a space :(
  390. "r422869 new_inline_comment_diff_${anchor}_${position}"
  391. */
  392. this.id = (this.$inputor[0].id || this.uid()).replace(/\W/g,'');
  393. /* ==== END MOD ==== */
  394. this.expectedQueryCBId = null;
  395. this.setting = null;
  396. this.query = null;
  397. this.pos = 0;
  398. this.range = null;
  399. if ((this.$el = $("#atwho-ground-" + this.id, this.app.$el)).length === 0) {
  400. this.app.$el.append(this.$el = $("<div id='atwho-ground-" + this.id + "'></div>"));
  401. }
  402. this.model = new Model(this);
  403. this.view = new View(this);
  404. }
  405.  
  406. Controller.prototype.init = function(setting) {
  407. this.setting = $.extend({}, this.setting || $.fn.atwho["default"], setting);
  408. this.view.init();
  409. return this.model.reload(this.setting.data);
  410. };
  411.  
  412. Controller.prototype.destroy = function() {
  413. this.trigger('beforeDestroy');
  414. this.model.destroy();
  415. this.view.destroy();
  416. return this.$el.remove();
  417. };
  418.  
  419. Controller.prototype.callDefault = function() {
  420. var args, error, error1, funcName;
  421. funcName = arguments[0], args = 2 <= arguments.length ? slice.call(arguments, 1) : [];
  422. try {
  423. return DEFAULT_CALLBACKS[funcName].apply(this, args);
  424. } catch (error1) {
  425. error = error1;
  426. return $.error(error + " Or maybe At.js doesn't have function " + funcName);
  427. }
  428. };
  429.  
  430. Controller.prototype.trigger = function(name, data) {
  431. var alias, eventName;
  432. if (data == null) {
  433. data = [];
  434. }
  435. data.push(this);
  436. alias = this.getOpt('alias');
  437. eventName = alias ? name + "-" + alias + ".atwho" : name + ".atwho";
  438. return this.$inputor.trigger(eventName, data);
  439. };
  440.  
  441. Controller.prototype.callbacks = function(funcName) {
  442. return this.getOpt("callbacks")[funcName] || DEFAULT_CALLBACKS[funcName];
  443. };
  444.  
  445. Controller.prototype.getOpt = function(at, default_value) {
  446. var e, error1;
  447. try {
  448. return this.setting[at];
  449. } catch (error1) {
  450. e = error1;
  451. return null;
  452. }
  453. };
  454.  
  455. Controller.prototype.insertContentFor = function($li) {
  456. var data, tpl;
  457. tpl = this.getOpt('insertTpl');
  458. data = $.extend({}, $li.data('item-data'), {
  459. 'atwho-at': this.at
  460. });
  461. return this.callbacks("tplEval").call(this, tpl, data, "onInsert");
  462. };
  463.  
  464. Controller.prototype.renderView = function(data) {
  465. var searchKey;
  466. searchKey = this.getOpt("searchKey");
  467. data = this.callbacks("sorter").call(this, this.query.text, data.slice(0, 1001), searchKey);
  468. return this.view.render(data.slice(0, this.getOpt('limit')));
  469. };
  470.  
  471. Controller.arrayToDefaultHash = function(data) {
  472. var i, item, len, results;
  473. if (!$.isArray(data)) {
  474. return data;
  475. }
  476. results = [];
  477. for (i = 0, len = data.length; i < len; i++) {
  478. item = data[i];
  479. if ($.isPlainObject(item)) {
  480. results.push(item);
  481. } else {
  482. results.push({
  483. name: item
  484. });
  485. }
  486. }
  487. return results;
  488. };
  489.  
  490. Controller.prototype.lookUp = function(e) {
  491. var query, wait;
  492. if (e && e.type === 'click' && !this.getOpt('lookUpOnClick')) {
  493. return;
  494. }
  495. if (this.getOpt('suspendOnComposing') && this.app.isComposing) {
  496. return;
  497. }
  498. query = this.catchQuery(e);
  499. if (!query) {
  500. this.expectedQueryCBId = null;
  501. return query;
  502. }
  503. this.app.setContextFor(this.at);
  504. if (wait = this.getOpt('delay')) {
  505. this._delayLookUp(query, wait);
  506. } else {
  507. this._lookUp(query);
  508. }
  509. return query;
  510. };
  511.  
  512. Controller.prototype._delayLookUp = function(query, wait) {
  513. var now, remaining;
  514. now = Date.now ? Date.now() : new Date().getTime();
  515. this.previousCallTime || (this.previousCallTime = now);
  516. remaining = wait - (now - this.previousCallTime);
  517. if ((0 < remaining && remaining < wait)) {
  518. this.previousCallTime = now;
  519. this._stopDelayedCall();
  520. return this.delayedCallTimeout = setTimeout((function(_this) {
  521. return function() {
  522. _this.previousCallTime = 0;
  523. _this.delayedCallTimeout = null;
  524. return _this._lookUp(query);
  525. };
  526. })(this), wait);
  527. } else {
  528. this._stopDelayedCall();
  529. if (this.previousCallTime !== now) {
  530. this.previousCallTime = 0;
  531. }
  532. return this._lookUp(query);
  533. }
  534. };
  535.  
  536. Controller.prototype._stopDelayedCall = function() {
  537. if (this.delayedCallTimeout) {
  538. clearTimeout(this.delayedCallTimeout);
  539. return this.delayedCallTimeout = null;
  540. }
  541. };
  542.  
  543. Controller.prototype._generateQueryCBId = function() {
  544. return {};
  545. };
  546.  
  547. Controller.prototype._lookUp = function(query) {
  548. var _callback;
  549. _callback = function(queryCBId, data) {
  550. if (queryCBId !== this.expectedQueryCBId) {
  551. return;
  552. }
  553. if (data && data.length > 0) {
  554. return this.renderView(this.constructor.arrayToDefaultHash(data));
  555. } else {
  556. return this.view.hide();
  557. }
  558. };
  559. this.expectedQueryCBId = this._generateQueryCBId();
  560. return this.model.query(query.text, $.proxy(_callback, this, this.expectedQueryCBId));
  561. };
  562.  
  563. return Controller;
  564.  
  565. })();
  566.  
  567. var TextareaController,
  568. extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
  569. hasProp = {}.hasOwnProperty;
  570.  
  571. TextareaController = (function(superClass) {
  572. extend(TextareaController, superClass);
  573.  
  574. function TextareaController() {
  575. return TextareaController.__super__.constructor.apply(this, arguments);
  576. }
  577.  
  578. TextareaController.prototype.catchQuery = function() {
  579. var caretPos, content, end, isString, query, start, subtext;
  580. content = this.$inputor.val();
  581. caretPos = this.$inputor.caret('pos', {
  582. iframe: this.app.iframe
  583. });
  584. subtext = content.slice(0, caretPos);
  585. query = this.callbacks("matcher").call(this, this.at, subtext, this.getOpt('startWithSpace'), this.getOpt("acceptSpaceBar"));
  586. isString = typeof query === 'string';
  587. if (isString && query.length < this.getOpt('minLen', 0)) {
  588. return;
  589. }
  590. if (isString && query.length <= this.getOpt('maxLen', 20)) {
  591. start = caretPos - query.length;
  592. end = start + query.length;
  593. this.pos = start;
  594. query = {
  595. 'text': query,
  596. 'headPos': start,
  597. 'endPos': end
  598. };
  599. this.trigger("matched", [this.at, query.text]);
  600. } else {
  601. query = null;
  602. this.view.hide();
  603. }
  604. return this.query = query;
  605. };
  606.  
  607. TextareaController.prototype.rect = function() {
  608. var c, iframeOffset, scaleBottom;
  609. if (!(c = this.$inputor.caret('offset', this.pos - 1, {
  610. iframe: this.app.iframe
  611. }))) {
  612. return;
  613. }
  614. if (this.app.iframe && !this.app.iframeAsRoot) {
  615. iframeOffset = $(this.app.iframe).offset();
  616. c.left += iframeOffset.left;
  617. c.top += iframeOffset.top;
  618. }
  619. scaleBottom = this.app.document.selection ? 0 : 2;
  620. return {
  621. left: c.left,
  622. top: c.top,
  623. bottom: c.top + c.height + scaleBottom
  624. };
  625. };
  626.  
  627. TextareaController.prototype.insert = function(content, $li) {
  628. var $inputor, source, startStr, suffix, text;
  629. $inputor = this.$inputor;
  630. source = $inputor.val();
  631. startStr = source.slice(0, Math.max(this.query.headPos - this.at.length, 0));
  632. suffix = (suffix = this.getOpt('suffix')) === "" ? suffix : suffix || " ";
  633. content += suffix;
  634. text = "" + startStr + content + (source.slice(this.query['endPos'] || 0));
  635. $inputor.val(text);
  636. $inputor.caret('pos', startStr.length + content.length, {
  637. iframe: this.app.iframe
  638. });
  639. if (!$inputor.is(':focus')) {
  640. $inputor.focus();
  641. }
  642. return $inputor.change();
  643. };
  644.  
  645. return TextareaController;
  646.  
  647. })(Controller);
  648.  
  649. var EditableController,
  650. extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
  651. hasProp = {}.hasOwnProperty;
  652.  
  653. EditableController = (function(superClass) {
  654. extend(EditableController, superClass);
  655.  
  656. function EditableController() {
  657. return EditableController.__super__.constructor.apply(this, arguments);
  658. }
  659.  
  660. EditableController.prototype._getRange = function() {
  661. var sel;
  662. sel = this.app.window.getSelection();
  663. if (sel.rangeCount > 0) {
  664. return sel.getRangeAt(0);
  665. }
  666. };
  667.  
  668. EditableController.prototype._setRange = function(position, node, range) {
  669. if (range == null) {
  670. range = this._getRange();
  671. }
  672. if (!range) {
  673. return;
  674. }
  675. node = $(node)[0];
  676. if (position === 'after') {
  677. range.setEndAfter(node);
  678. range.setStartAfter(node);
  679. } else {
  680. range.setEndBefore(node);
  681. range.setStartBefore(node);
  682. }
  683. range.collapse(false);
  684. return this._clearRange(range);
  685. };
  686.  
  687. EditableController.prototype._clearRange = function(range) {
  688. var sel;
  689. if (range == null) {
  690. range = this._getRange();
  691. }
  692. sel = this.app.window.getSelection();
  693. if (this.ctrl_a_pressed == null) {
  694. sel.removeAllRanges();
  695. return sel.addRange(range);
  696. }
  697. };
  698.  
  699. EditableController.prototype._movingEvent = function(e) {
  700. var ref;
  701. return e.type === 'click' || ((ref = e.which) === KEY_CODE.RIGHT || ref === KEY_CODE.LEFT || ref === KEY_CODE.UP || ref === KEY_CODE.DOWN);
  702. };
  703.  
  704. EditableController.prototype._unwrap = function(node) {
  705. var next;
  706. node = $(node).unwrap().get(0);
  707. if ((next = node.nextSibling) && next.nodeValue) {
  708. node.nodeValue += next.nodeValue;
  709. $(next).remove();
  710. }
  711. return node;
  712. };
  713.  
  714. EditableController.prototype.catchQuery = function(e) {
  715. var $inserted, $query, _range, index, inserted, isString, lastNode, matched, offset, query, query_content, range;
  716. if (!(range = this._getRange())) {
  717. return;
  718. }
  719. if (!range.collapsed) {
  720. return;
  721. }
  722. if (e.which === KEY_CODE.ENTER) {
  723. ($query = $(range.startContainer).closest('.atwho-query')).contents().unwrap();
  724. if ($query.is(':empty')) {
  725. $query.remove();
  726. }
  727. ($query = $(".atwho-query", this.app.document)).text($query.text()).contents().last().unwrap();
  728. this._clearRange();
  729. return;
  730. }
  731. if (/firefox/i.test(navigator.userAgent)) {
  732. if ($(range.startContainer).is(this.$inputor)) {
  733. this._clearRange();
  734. return;
  735. }
  736. if (e.which === KEY_CODE.BACKSPACE && range.startContainer.nodeType === document.ELEMENT_NODE && (offset = range.startOffset - 1) >= 0) {
  737. _range = range.cloneRange();
  738. _range.setStart(range.startContainer, offset);
  739. if ($(_range.cloneContents()).contents().last().is('.atwho-inserted')) {
  740. inserted = $(range.startContainer).contents().get(offset);
  741. this._setRange('after', $(inserted).contents().last());
  742. }
  743. } else if (e.which === KEY_CODE.LEFT && range.startContainer.nodeType === document.TEXT_NODE) {
  744. $inserted = $(range.startContainer.previousSibling);
  745. if ($inserted.is('.atwho-inserted') && range.startOffset === 0) {
  746. this._setRange('after', $inserted.contents().last());
  747. }
  748. }
  749. }
  750. $(range.startContainer).closest('.atwho-inserted').addClass('atwho-query').siblings().removeClass('atwho-query');
  751. if (($query = $(".atwho-query", this.app.document)).length > 0 && $query.is(':empty') && $query.text().length === 0) {
  752. $query.remove();
  753. }
  754. if (!this._movingEvent(e)) {
  755. $query.removeClass('atwho-inserted');
  756. }
  757. if ($query.length > 0) {
  758. switch (e.which) {
  759. case KEY_CODE.LEFT:
  760. this._setRange('before', $query.get(0), range);
  761. $query.removeClass('atwho-query');
  762. return;
  763. case KEY_CODE.RIGHT:
  764. this._setRange('after', $query.get(0).nextSibling, range);
  765. $query.removeClass('atwho-query');
  766. return;
  767. }
  768. }
  769. if ($query.length > 0 && (query_content = $query.attr('data-atwho-at-query'))) {
  770. $query.empty().html(query_content).attr('data-atwho-at-query', null);
  771. this._setRange('after', $query.get(0), range);
  772. }
  773. _range = range.cloneRange();
  774. _range.setStart(range.startContainer, 0);
  775. matched = this.callbacks("matcher").call(this, this.at, _range.toString(), this.getOpt('startWithSpace'), this.getOpt("acceptSpaceBar"));
  776. isString = typeof matched === 'string';
  777. if ($query.length === 0 && isString && (index = range.startOffset - this.at.length - matched.length) >= 0) {
  778. range.setStart(range.startContainer, index);
  779. $query = $('<span/>', this.app.document).attr(this.getOpt("editableAtwhoQueryAttrs")).addClass('atwho-query');
  780. range.surroundContents($query.get(0));
  781. lastNode = $query.contents().last().get(0);
  782. if (/firefox/i.test(navigator.userAgent)) {
  783. range.setStart(lastNode, lastNode.length);
  784. range.setEnd(lastNode, lastNode.length);
  785. this._clearRange(range);
  786. } else {
  787. this._setRange('after', lastNode, range);
  788. }
  789. }
  790. if (isString && matched.length < this.getOpt('minLen', 0)) {
  791. return;
  792. }
  793. if (isString && matched.length <= this.getOpt('maxLen', 20)) {
  794. query = {
  795. text: matched,
  796. el: $query
  797. };
  798. this.trigger("matched", [this.at, query.text]);
  799. return this.query = query;
  800. } else {
  801. this.view.hide();
  802. this.query = {
  803. el: $query
  804. };
  805. if ($query.text().indexOf(this.at) >= 0) {
  806. if (this._movingEvent(e) && $query.hasClass('atwho-inserted')) {
  807. $query.removeClass('atwho-query');
  808. } else if (false !== this.callbacks('afterMatchFailed').call(this, this.at, $query)) {
  809. this._setRange("after", this._unwrap($query.text($query.text()).contents().first()));
  810. }
  811. }
  812. return null;
  813. }
  814. };
  815.  
  816. EditableController.prototype.rect = function() {
  817. var $iframe, iframeOffset, rect;
  818. rect = this.query.el.offset();
  819. if (this.app.iframe && !this.app.iframeAsRoot) {
  820. iframeOffset = ($iframe = $(this.app.iframe)).offset();
  821. rect.left += iframeOffset.left - this.$inputor.scrollLeft();
  822. rect.top += iframeOffset.top - this.$inputor.scrollTop();
  823. }
  824. rect.bottom = rect.top + this.query.el.height();
  825. return rect;
  826. };
  827.  
  828. EditableController.prototype.insert = function(content, $li) {
  829. var data, range, suffix, suffixNode;
  830. if (!this.$inputor.is(':focus')) {
  831. this.$inputor.focus();
  832. }
  833. suffix = (suffix = this.getOpt('suffix')) === "" ? suffix : suffix || "\u00A0";
  834. data = $li.data('item-data');
  835. this.query.el.removeClass('atwho-query').addClass('atwho-inserted').html(content).attr('data-atwho-at-query', "" + data['atwho-at'] + this.query.text);
  836. if (range = this._getRange()) {
  837. range.setEndAfter(this.query.el[0]);
  838. range.collapse(false);
  839. range.insertNode(suffixNode = this.app.document.createTextNode("\u200D" + suffix));
  840. this._setRange('after', suffixNode, range);
  841. }
  842. if (!this.$inputor.is(':focus')) {
  843. this.$inputor.focus();
  844. }
  845. return this.$inputor.change();
  846. };
  847.  
  848. return EditableController;
  849.  
  850. })(Controller);
  851.  
  852. var Model;
  853.  
  854. Model = (function() {
  855. function Model(context) {
  856. this.context = context;
  857. this.at = this.context.at;
  858. this.storage = this.context.$inputor;
  859. }
  860.  
  861. Model.prototype.destroy = function() {
  862. return this.storage.data(this.at, null);
  863. };
  864.  
  865. Model.prototype.saved = function() {
  866. return this.fetch() > 0;
  867. };
  868.  
  869. Model.prototype.query = function(query, callback) {
  870. var _remoteFilter, data, searchKey;
  871. data = this.fetch();
  872. searchKey = this.context.getOpt("searchKey");
  873. data = this.context.callbacks('filter').call(this.context, query, data, searchKey) || [];
  874. _remoteFilter = this.context.callbacks('remoteFilter');
  875. if (data.length > 0 || (!_remoteFilter && data.length === 0)) {
  876. return callback(data);
  877. } else {
  878. return _remoteFilter.call(this.context, query, callback);
  879. }
  880. };
  881.  
  882. Model.prototype.fetch = function() {
  883. return this.storage.data(this.at) || [];
  884. };
  885.  
  886. Model.prototype.save = function(data) {
  887. return this.storage.data(this.at, this.context.callbacks("beforeSave").call(this.context, data || []));
  888. };
  889.  
  890. Model.prototype.load = function(data) {
  891. if (!(this.saved() || !data)) {
  892. return this._load(data);
  893. }
  894. };
  895.  
  896. Model.prototype.reload = function(data) {
  897. return this._load(data);
  898. };
  899.  
  900. Model.prototype._load = function(data) {
  901. if (typeof data === "string") {
  902. return $.ajax(data, {
  903. dataType: "json"
  904. }).done((function(_this) {
  905. return function(data) {
  906. return _this.save(data);
  907. };
  908. })(this));
  909. } else {
  910. return this.save(data);
  911. }
  912. };
  913.  
  914. return Model;
  915.  
  916. })();
  917.  
  918. var View;
  919.  
  920. View = (function() {
  921. function View(context) {
  922. this.context = context;
  923. this.$el = $("<div class='atwho-view'><ul class='atwho-view-ul'></ul></div>");
  924. this.$elUl = this.$el.children();
  925. this.timeoutID = null;
  926. this.context.$el.append(this.$el);
  927. this.bindEvent();
  928. }
  929.  
  930. View.prototype.init = function() {
  931. var header_tpl, id;
  932. id = this.context.getOpt("alias") || this.context.at.charCodeAt(0);
  933. header_tpl = this.context.getOpt("headerTpl");
  934. if (header_tpl && this.$el.children().length === 1) {
  935. this.$el.prepend(header_tpl);
  936. }
  937. return this.$el.attr({
  938. 'id': "at-view-" + id
  939. });
  940. };
  941.  
  942. View.prototype.destroy = function() {
  943. return this.$el.remove();
  944. };
  945.  
  946. View.prototype.bindEvent = function() {
  947. var $menu, lastCoordX, lastCoordY;
  948. $menu = this.$el.find('ul');
  949. lastCoordX = 0;
  950. lastCoordY = 0;
  951. return $menu.on('mousemove.atwho-view', 'li', (function(_this) {
  952. return function(e) {
  953. var $cur;
  954. if (lastCoordX === e.clientX && lastCoordY === e.clientY) {
  955. return;
  956. }
  957. lastCoordX = e.clientX;
  958. lastCoordY = e.clientY;
  959. $cur = $(e.currentTarget);
  960. if ($cur.hasClass('cur')) {
  961. return;
  962. }
  963. $menu.find('.cur').removeClass('cur');
  964. return $cur.addClass('cur');
  965. };
  966. })(this)).on('click.atwho-view', 'li', (function(_this) {
  967. return function(e) {
  968. $menu.find('.cur').removeClass('cur');
  969. $(e.currentTarget).addClass('cur');
  970. _this.choose(e);
  971. return e.preventDefault();
  972. };
  973. })(this));
  974. };
  975.  
  976. View.prototype.visible = function() {
  977. return this.$el.is(":visible");
  978. };
  979.  
  980. View.prototype.highlighted = function() {
  981. return this.$el.find(".cur").length > 0;
  982. };
  983.  
  984. View.prototype.choose = function(e) {
  985. var $li, content;
  986. if (($li = this.$el.find(".cur")).length) {
  987. content = this.context.insertContentFor($li);
  988. this.context._stopDelayedCall();
  989. this.context.insert(this.context.callbacks("beforeInsert").call(this.context, content, $li, e), $li);
  990. this.context.trigger("inserted", [$li, e]);
  991. this.hide(e);
  992. }
  993. if (this.context.getOpt("hideWithoutSuffix")) {
  994. return this.stopShowing = true;
  995. }
  996. };
  997.  
  998. View.prototype.reposition = function(rect) {
  999. var _window, offset, overflowOffset, ref;
  1000. _window = this.context.app.iframeAsRoot ? this.context.app.window : window;
  1001. if (rect.bottom + this.$el.height() - $(_window).scrollTop() > $(_window).height()) {
  1002. rect.bottom = rect.top - this.$el.height();
  1003. }
  1004. if (rect.left > (overflowOffset = $(_window).width() - this.$el.width() - 5)) {
  1005. rect.left = overflowOffset;
  1006. }
  1007. offset = {
  1008. left: rect.left,
  1009. top: rect.bottom
  1010. };
  1011. if ((ref = this.context.callbacks("beforeReposition")) != null) {
  1012. ref.call(this.context, offset);
  1013. }
  1014. this.$el.offset(offset);
  1015. return this.context.trigger("reposition", [offset]);
  1016. };
  1017.  
  1018. View.prototype.next = function() {
  1019. var cur, next, nextEl, offset;
  1020. cur = this.$el.find('.cur').removeClass('cur');
  1021. next = cur.next();
  1022. if (!next.length) {
  1023. next = this.$el.find('li:first');
  1024. }
  1025. next.addClass('cur');
  1026. nextEl = next[0];
  1027. offset = nextEl.offsetTop + nextEl.offsetHeight + (nextEl.nextSibling ? nextEl.nextSibling.offsetHeight : 0);
  1028. return this.scrollTop(Math.max(0, offset - this.$el.height()));
  1029. };
  1030.  
  1031. View.prototype.prev = function() {
  1032. var cur, offset, prev, prevEl;
  1033. cur = this.$el.find('.cur').removeClass('cur');
  1034. prev = cur.prev();
  1035. if (!prev.length) {
  1036. prev = this.$el.find('li:last');
  1037. }
  1038. prev.addClass('cur');
  1039. prevEl = prev[0];
  1040. offset = prevEl.offsetTop + prevEl.offsetHeight + (prevEl.nextSibling ? prevEl.nextSibling.offsetHeight : 0);
  1041. return this.scrollTop(Math.max(0, offset - this.$el.height()));
  1042. };
  1043.  
  1044. View.prototype.scrollTop = function(scrollTop) {
  1045. var scrollDuration;
  1046. scrollDuration = this.context.getOpt('scrollDuration');
  1047. if (scrollDuration) {
  1048. return this.$elUl.animate({
  1049. scrollTop: scrollTop
  1050. }, scrollDuration);
  1051. } else {
  1052. return this.$elUl.scrollTop(scrollTop);
  1053. }
  1054. };
  1055.  
  1056. View.prototype.show = function() {
  1057. var rect;
  1058. if (this.stopShowing) {
  1059. this.stopShowing = false;
  1060. return;
  1061. }
  1062. if (!this.visible()) {
  1063. this.$el.show();
  1064. this.$el.scrollTop(0);
  1065. this.context.trigger('shown');
  1066. }
  1067. if (rect = this.context.rect()) {
  1068. return this.reposition(rect);
  1069. }
  1070. };
  1071.  
  1072. View.prototype.hide = function(e, time) {
  1073. var callback;
  1074. if (!this.visible()) {
  1075. return;
  1076. }
  1077. if (isNaN(time)) {
  1078. this.$el.hide();
  1079. return this.context.trigger('hidden', [e]);
  1080. } else {
  1081. callback = (function(_this) {
  1082. return function() {
  1083. return _this.hide();
  1084. };
  1085. })(this);
  1086. clearTimeout(this.timeoutID);
  1087. return this.timeoutID = setTimeout(callback, time);
  1088. }
  1089. };
  1090.  
  1091. View.prototype.render = function(list) {
  1092. var $li, $ul, i, item, len, li, tpl;
  1093. if (!($.isArray(list) && list.length > 0)) {
  1094. this.hide();
  1095. return;
  1096. }
  1097. this.$el.find('ul').empty();
  1098. $ul = this.$el.find('ul');
  1099. tpl = this.context.getOpt('displayTpl');
  1100. for (i = 0, len = list.length; i < len; i++) {
  1101. item = list[i];
  1102. item = $.extend({}, item, {
  1103. 'atwho-at': this.context.at
  1104. });
  1105. li = this.context.callbacks("tplEval").call(this.context, tpl, item, "onDisplay");
  1106. $li = $(this.context.callbacks("highlighter").call(this.context, li, this.context.query.text));
  1107. $li.data("item-data", item);
  1108. $ul.append($li);
  1109. }
  1110. this.show();
  1111. if (this.context.getOpt('highlightFirst')) {
  1112. return $ul.find("li:first").addClass("cur");
  1113. }
  1114. };
  1115.  
  1116. return View;
  1117.  
  1118. })();
  1119.  
  1120. var Api;
  1121.  
  1122. Api = {
  1123. load: function(at, data) {
  1124. var c;
  1125. if (c = this.controller(at)) {
  1126. return c.model.load(data);
  1127. }
  1128. },
  1129. isSelecting: function() {
  1130. var ref;
  1131. return !!((ref = this.controller()) != null ? ref.view.visible() : void 0);
  1132. },
  1133. hide: function() {
  1134. var ref;
  1135. return (ref = this.controller()) != null ? ref.view.hide() : void 0;
  1136. },
  1137. reposition: function() {
  1138. var c;
  1139. if (c = this.controller()) {
  1140. return c.view.reposition(c.rect());
  1141. }
  1142. },
  1143. setIframe: function(iframe, asRoot) {
  1144. this.setupRootElement(iframe, asRoot);
  1145. return null;
  1146. },
  1147. run: function() {
  1148. return this.dispatch();
  1149. },
  1150. destroy: function() {
  1151. this.shutdown();
  1152. return this.$inputor.data('atwho', null);
  1153. }
  1154. };
  1155.  
  1156. $.fn.atwho = function(method) {
  1157. var _args, result;
  1158. _args = arguments;
  1159. result = null;
  1160. this.filter('textarea, input, [contenteditable=""], [contenteditable=true]').each(function() {
  1161. var $this, app;
  1162. if (!(app = ($this = $(this)).data("atwho"))) {
  1163. $this.data('atwho', (app = new App(this)));
  1164. }
  1165. if (typeof method === 'object' || !method) {
  1166. return app.reg(method.at, method);
  1167. } else if (Api[method] && app) {
  1168. return result = Api[method].apply(app, Array.prototype.slice.call(_args, 1));
  1169. } else {
  1170. return $.error("Method " + method + " does not exist on jQuery.atwho");
  1171. }
  1172. });
  1173. if (result != null) {
  1174. return result;
  1175. } else {
  1176. return this;
  1177. }
  1178. };
  1179.  
  1180. $.fn.atwho["default"] = {
  1181. at: void 0,
  1182. alias: void 0,
  1183. data: null,
  1184. displayTpl: "<li>${name}</li>",
  1185. insertTpl: "${atwho-at}${name}",
  1186. headerTpl: null,
  1187. callbacks: DEFAULT_CALLBACKS,
  1188. searchKey: "name",
  1189. suffix: void 0,
  1190. hideWithoutSuffix: false,
  1191. startWithSpace: true,
  1192. acceptSpaceBar: false,
  1193. highlightFirst: true,
  1194. limit: 5,
  1195. maxLen: 20,
  1196. minLen: 0,
  1197. displayTimeout: 300,
  1198. delay: null,
  1199. spaceSelectsMatch: false,
  1200. tabSelectsMatch: true,
  1201. editableAtwhoQueryAttrs: {},
  1202. scrollDuration: 150,
  1203. suspendOnComposing: true,
  1204. lookUpOnClick: true
  1205. };
  1206.  
  1207. $.fn.atwho.debug = false;
  1208.  
  1209. }));

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址