jquery.atwho-1.5.1.js 34 KB

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